diff options
-rw-r--r-- | src/server.js | 293 | ||||
-rw-r--r-- | src/templates.js | 23 | ||||
-rw-r--r-- | src/templates/expected.html | 38 | ||||
-rw-r--r-- | src/templates/goals.html | 50 | ||||
-rw-r--r-- | src/templates/ledger.html | 51 | ||||
-rw-r--r-- | src/templates/summary.html | 101 |
6 files changed, 401 insertions, 155 deletions
diff --git a/src/server.js b/src/server.js index ffbc373..487fe0c 100644 --- a/src/server.js +++ b/src/server.js @@ -17,7 +17,7 @@ function listen(port) { server.listen(port, () => console.info(`Listening: http://localhost:${port} `)); } -function hashWithSalt(password, salt){ +function hashWithSalt(password, salt) { var hash = crypto.createHmac('sha512', salt); hash.update(password); return hash.digest("base64"); @@ -49,31 +49,83 @@ function setUpRoutes(models, jwtFunctions, database, templates) { } next(); }) - + // Route logging server.use(function (req, res, next) { console.debug(new Date(), req.method, req.originalUrl); next() }) - server.get('/', (req, res) => res.sendFile(__dirname + "/index.html")) - server.get('/login', (req, res) => res.sendFile(__dirname + "/login.html")) - - server.get('/summary', async (req, res) => { - var ledger = await database.query("SELECT * FROM transactions WHERE username = '" + res.locals.user.username + "' ORDER BY `when` DESC", { type: database.QueryTypes.SELECT }) + server.use('/static', express.static(path.join(__dirname, '/static'))) + server.get('/', (req, res) => res.redirect("/ledger")) + server.get('/login', (req, res) => res.sendFile(path.join(__dirname, "/login.html"))) + server.get('/ledger', async (req, res) => { + var ledger = await database.query(`SELECT * FROM transactions WHERE username = '${res.locals.user.username}' ORDER BY \`when\` DESC`, { type: database.QueryTypes.SELECT }) ledger.forEach((element, i) => { element.when = element.when.toString().substring(0, 10); - element.index = i+1 + element.index = i + 1 }); - var name = res.locals.user.username - let body = templates["summary"]({ name, ledger }) + let name = res.locals.user.username + let body = templates["ledger"]({ name, ledger }) res.status(200).send(body) }) + server.get('/goals', async (req, res) => { + let goals = await database.query(`SELECT * FROM goals WHERE username = '${res.locals.user.username}' ORDER BY \`name\` DESC`, { type: database.QueryTypes.SELECT }) + goals.forEach((element, i) => { + element.remaining = element.total - element.amount; + element.index = i + 1 + }); + let name = res.locals.user.username + let body = templates["goals"]({ name, goals }) + res.status(200).send(body) + }) + server.get(`/expected`, async (req, res, next) => { + try { + let expecteds = await database.query(`SELECT * FROM expecteds WHERE username = '${res.locals.user.username}' ORDER BY \`name\` DESC`, { type: database.QueryTypes.SELECT }) + expecteds.forEach((element, i) => { + element.index = i + 1 + }); + let name = res.locals.user.username + let body = templates["expected"]({ name, expecteds }) + res.status(200).send(body); + } catch (e) { + console.log(e) + res.status(400).send(e.message); + } + }) + server.get(`/summary`, async (req, res, next) => { + try { + let data = { + week: { + out: await database.query(`SELECT year(\`when\`) as y, week(\`when\`) as w, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount > 0 group by year(\`when\`), WEEK(\`when\`);`, { type: database.QueryTypes.SELECT }), + in: await database.query(`SELECT year(\`when\`)as y, week(\`when\`) as w, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount < 0 group by year(\`when\`), WEEK(\`when\`);`, { type: database.QueryTypes.SELECT }), + net: await database.query(`SELECT year(\`when\`) as y, week(\`when\`) as w, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' group by year(\`when\`), WEEK(\`when\`);`, { type: database.QueryTypes.SELECT }), + }, + month: { + out: await database.query(`SELECT year(\`when\`) as y, month(\`when\`) as m, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount > 0 group by year(\`when\`), month(\`when\`);`, { type: database.QueryTypes.SELECT }), + in: await database.query(`SELECT year(\`when\`) as y, month(\`when\`) as m, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount < 0 group by year(\`when\`), month(\`when\`);`, { type: database.QueryTypes.SELECT }), + net: await database.query(`SELECT year(\`when\`) as y, month(\`when\`) as m, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' group by year(\`when\`), month(\`when\`);`, { type: database.QueryTypes.SELECT }), + }, + year: { + out: await database.query(`SELECT year(\`when\`) as y, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount > 0 group by year(\`when\`);`, { type: database.QueryTypes.SELECT }), + in: await database.query(`SELECT year(\`when\`) as y, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' and amount < 0 group by year(\`when\`);`, { type: database.QueryTypes.SELECT }), + net: await database.query(`SELECT year(\`when\`) as y, sum(amount) as s FROM transactions where username = '${res.locals.user.username}' group by year(\`when\`);`, { type: database.QueryTypes.SELECT }), + }, + name: res.locals.user.username + }; + data = formatSummary(data) + let body = templates["summary"](data) + res.status(200).send(body); + + } catch (e) { + console.log(e) + res.status(400).send(e.message); + } + }) - server.use('/static', express.static(path.join(__dirname, '/static'))) server.post('/login', async (req, res, next) => { - const user = await models.users.findOne({ where: { username: req.body.username} }) + const user = await models.users.findOne({ where: { username: req.body.username } }) const hash = hashWithSalt(req.body.password, user.salt) if (user.password == hash) { const token = jwtFunctions.sign(user.username); @@ -85,147 +137,166 @@ function setUpRoutes(models, jwtFunctions, database, templates) { res.redirect('/login'); } }) - - server.get(`/transaction`, async (req, res, next) => { - try { - var result = await database.query("SELECT * FROM transactions WHERE username = '" + res.locals.user.username + "' ORDER BY `when` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); - next(); - } catch (e) { - console.log(e) - res.status(400).send(e.message); - } - }) server.post(`/transaction`, async (req, res, next) => { try { let item = req.body; console.log(item); item.username = res.locals.user.username - if(!item.when){ + if (!item.when) { item.when = new Date().toLocaleDateString(); } await models.transaction.create(item); - res.redirect("/summary") - } catch (e) { - console.log(e); - res.status(400).send(e.message); - } - }) - server.delete(`/transaction`, async (req, res, next) => { - try { - let id = req.body.id; - console.log(`Deleting ${id}`); - await models.transaction.destroy({ where: { id: id, username: res.locals.user.username } }); - var result = await database.query("SELECT * FROM transactions WHERE username = '" + res.locals.user.username + "' ORDER BY `when` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); + res.redirect("/ledger") } catch (e) { console.log(e); res.status(400).send(e.message); } }) - server.put(`/transaction`, async (req, res, next) => { + server.post(`/allocate`, async (req, res, next) => { try { - let id = req.body.id; - let update = req.body.update; - console.log(`Updating ${id}`); - var toUpdate = await models.transaction.findOne({ where: { id: id, username:res.locals.user.username } }); - console.log(toUpdate) - console.log(update) + let amount = req.body.amount; + var toUpdate = await models.goals.findOne({ where: { name: req.body.name, username: res.locals.user.username } }); + var update = { amount: toUpdate.amount + amount } await toUpdate.update(update); - var result = await database.query("SELECT * FROM transactions WHERE username = '" + res.locals.user.username + "' ORDER BY `when` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); + res.redirect("/goals") } catch (e) { console.log(e); res.status(400).send(e.message); } }) - - server.get(`/goals`, async (req, res, next) => { - try { - var result = await database.query("SELECT * FROM goals WHERE username = '" + res.locals.user.username + "' ORDER BY `name` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); - next(); - } catch (e) { - console.log(e) - res.status(400).send(e.message); - } - }) server.post(`/goals`, async (req, res, next) => { try { let item = req.body; - console.log(item); item.username = res.locals.user.username + item.amount = 0; await models.goals.create(item); - var result = await database.query("SELECT * FROM goals WHERE username = '" + res.locals.user.username + "' ORDER BY `name` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); - } catch (e) { - console.log(e); - res.status(400).send(e.message); - } - }) - server.post(`/allocate`, async (req, res, next) => { - try { - let name = req.body.name; - let amount = req.body.amount; - var toUpdate = await models.goals.findOne({ where: { name: name, username:res.locals.user.username } }); - var update = {amount: toUpdate.amount + amount} - await toUpdate.update(update); - var result = await await database.query("SELECT * FROM goals WHERE username = '" + res.locals.user.username + "' ORDER BY `name` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); + res.redirect("/goals") } catch (e) { console.log(e); res.status(400).send(e.message); } }) - server.get(`/expected`, async (req, res, next) => { - try { - var result = await database.query("SELECT * FROM expecteds WHERE username = '" + res.locals.user.username + "' ORDER BY `name` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); - next(); - } catch (e) { - console.log(e) - res.status(400).send(e.message); - } - }) server.post(`/expected`, async (req, res, next) => { try { let item = req.body; - console.log(item); item.username = res.locals.user.username await models.expected.create(item); - var result = await database.query("SELECT * FROM expecteds WHERE username = '" + res.locals.user.username + "' ORDER BY `name` DESC", { type: database.QueryTypes.SELECT }) - res.status(200).send(result); + res.redirect("/expected") } catch (e) { console.log(e); res.status(400).send(e.message); } }) - server.get(`/summary`, async (req, res, next) => { - try { - res.status(200).send({ - week: { - out: await database.query("SELECT year(`when`) as y, week(`when`) as w, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount > 0 group by year(`when`), WEEK(`when`);", { type: database.QueryTypes.SELECT }), - in: await database.query("SELECT year(`when`)as y, week(`when`) as w, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount < 0 group by year(`when`), WEEK(`when`);", { type: database.QueryTypes.SELECT }), - net: await database.query("SELECT year(`when`) as y, week(`when`) as w, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' group by year(`when`), WEEK(`when`);", { type: database.QueryTypes.SELECT }), - }, - month: { - out: await database.query("SELECT year(`when`) as y, month(`when`) as m, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount > 0 group by year(`when`), month(`when`);", { type: database.QueryTypes.SELECT }), - in: await database.query("SELECT year(`when`) as y, month(`when`) as m, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount < 0 group by year(`when`), month(`when`);", { type: database.QueryTypes.SELECT }), - net: await database.query("SELECT year(`when`) as y, month(`when`) as m, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' group by year(`when`), month(`when`);", { type: database.QueryTypes.SELECT }), - }, - year: { - out: await database.query("SELECT year(`when`) as y, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount > 0 group by year(`when`);", { type: database.QueryTypes.SELECT }), - in: await database.query("SELECT year(`when`) as y, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' and amount < 0 group by year(`when`);", { type: database.QueryTypes.SELECT }), - net: await database.query("SELECT year(`when`) as y, sum(amount) as s FROM transactions where username = '" + res.locals.user.username + "' group by year(`when`);", { type: database.QueryTypes.SELECT }), - }, - username: res.locals.user.username - }); - next(); - } catch (e) { - console.log(e) - res.status(400).send(e.message); +} + +var findOrCreateWeek = function (summary, el) { + var item = summary.week.find(el2 => { + return el.y == el2.y && el.w == el2.w + }) + if (!item) { + item = { y: el.y, w: el.w, in: 0, out: 0, net: 0 } + summary.week.push(item); + } + return item +} +var findOrCreateMonth = function (summary, el) { + var item = summary.month.find(el2 => { + return el.y == el2.y && el.m == el2.m + }) + if (!item) { + item = { y: el.y, m: el.m, in: 0, out: 0, net: 0 } + summary.month.push(item); + } + return item +} +var findOrCreateYear = function (summary, el) { + var item = summary.year.find(el2 => { + return el.y == el2.y + }) + if (!item) { + item = { y: el.y, in: 0, out: 0, net: 0 } + summary.year.push(item); + } + return item +} + +function formatSummary(response) { + let summary = {} + summary.week = []; + summary.month = []; + summary.year = []; + + response.week.in.forEach(el => { + findOrCreateWeek(summary, el).in = Math.abs(el.s) + }) + response.week.out.forEach(el => { + findOrCreateWeek(summary, el).out = Math.abs(el.s) + }) + response.week.net.forEach(el => { + var item = findOrCreateWeek(summary, el); + item.net = el.s + // Note we flip these since income is negative + item.classes = "" + if(el.s > 0){ + item.classes += "net-negative" + } + if(el.s < 0){ + item.classes += "net-positive" } }) + + response.month.in.forEach(el => { + findOrCreateMonth(summary, el).in = Math.abs(el.s) + }) + response.month.out.forEach(el => { + findOrCreateMonth(summary, el).out = Math.abs(el.s) + }) + response.month.net.forEach(el => { + var item = findOrCreateMonth(summary, el); + item.net = el.s + // Note we flip these since income is negative + item.classes = "" + if(el.s > 0){ + item.classes += "net-negative" + } + if(el.s < 0){ + item.classes += "net-positive" + } + }) + + response.year.in.forEach(el => { + findOrCreateYear(summary, el).in = Math.abs(el.s) + }) + response.year.out.forEach(el => { + findOrCreateYear(summary, el).out = Math.abs(el.s) + }) + response.year.net.forEach(el => { + var item = findOrCreateYear(summary, el); + item.net = el.s + // Note we flip these since income is negative + item.classes = "" + if(el.s > 0){ + item.classes += "net-negative" + } + if(el.s < 0){ + item.classes += "net-positive" + } + }) + + summary.week.sort(function (a, b) { + if (a.y == b.y) { return a.w - b.w; } + return a.y - b.y; + }) + summary.month.sort(function (a, b) { + if (a.y == b.y) { return a.m - b.m; } + return a.y - b.y; + }) + summary.year.sort(function (a, b) { + return a.y - b.y; + }) + + summary.name = response.name + return summary } module.exports = { diff --git a/src/templates.js b/src/templates.js index 2c02905..6e3b541 100644 --- a/src/templates.js +++ b/src/templates.js @@ -2,21 +2,20 @@ const fs = require('fs'); const path = require('path'); const handlebars = require("handlebars"); +function loadTemplate(templates, name, filepath){ + const templateContent = fs.readFileSync(filepath).toString() + templates[name] = handlebars.compile(templateContent); +} + function setUpTemplates(){ let templates = {}; - { - const templateContent = fs.readFileSync(path.join(__dirname, 'templates/login.html')).toString() - templates["login"] = handlebars.compile(templateContent); - } - { - const templateContent = fs.readFileSync(path.join(__dirname, 'templates/index.html')).toString() - // templates["index"] = handlebars.compile(templateContent); - } - { - const templateContent = fs.readFileSync(path.join(__dirname, 'templates/summary.html')).toString() - templates["summary"] = handlebars.compile(templateContent); - } + loadTemplate(templates, "index", path.join(__dirname, 'templates/index.html')) + loadTemplate(templates, "login", path.join(__dirname, 'templates/login.html')) + loadTemplate(templates, "ledger", path.join(__dirname, 'templates/ledger.html')) + loadTemplate(templates, "goals", path.join(__dirname, 'templates/goals.html')) + loadTemplate(templates, "expected", path.join(__dirname, 'templates/expected.html')) + loadTemplate(templates, "summary", path.join(__dirname, 'templates/summary.html')) return templates } diff --git a/src/templates/expected.html b/src/templates/expected.html new file mode 100644 index 0000000..15b9d7c --- /dev/null +++ b/src/templates/expected.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>{{name}}'s Budget</title> + <meta charset="UTF-8"> + <link rel="stylesheet" type="text/css" href="static/styles.css"> +</head> + +<body> + <h1>{{name}}'s Budget</h1> + <div> + <a href="/ledger"><button>Ledger</button></a> + <a href="/summary"><button>Summary</button></a> + <a href="/goals"><button>Goals</button></a> + <a href="/expected"><button class="bold">Expected</button></a> + </div> + + <form method="post" action="/expected"> + <input name="name" placeholder="name" type="text"> + <input name="total" placeholder="total" type="number"> + <input name="days" placeholder="days" type="number"> + <input type="submit" value="Add"> + </form> + <table> + <tr><th></th><th>Name</th><th>Total</th><th>Period</th></tr> + {{#each expecteds}} + <tr> + <td class="table-index">{{this.index}}</td> + <td>{{this.name}}</td> + <td>{{this.total}}</td> + <td>{{this.days}}</td> + </tr> + {{/each}} + </table> +</body> +</html> + diff --git a/src/templates/goals.html b/src/templates/goals.html new file mode 100644 index 0000000..b617a6e --- /dev/null +++ b/src/templates/goals.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>{{name}}'s Budget</title> + <meta charset="UTF-8"> + <link rel="stylesheet" type="text/css" href="static/styles.css"> +</head> + +<body> + <h1>{{name}}'s Budget</h1> + <div> + <a href="/ledger"><button>Ledger</button></a> + <a href="/summary"><button>Summary</button></a> + <a href="/goals"><button class="bold">Goals</button></a> + <a href="/expected"><button>Expected</button></a> + </div> + + <form method="post" action="/goals"> + <input name="name" placeholder="name"> + <input name="total" placeholder="total" type="number" step="0.01"> + <input type="submit" value="Add"> + </form> + <form method="post" action="/allocate"> + <span>Allocate funds</span> + <select name="name"> + {{#each goals}} + <option value="{{this.name}}"> + {{ this.name }} + </option> + {{/each}} + </select> + <input name="amount" placeholder="amount" type="number"> + <input type="submit" value="Add"> + </form> + <table> + <tr><th></th><th>Name</th><th>Amount</th><th>Total</th><th>Remaining</th></tr> + {{#each goals}} + <tr> + <td class="table-index">{{this.index}}</td> + <td>{{this.name}}</td> + <td>{{this.amount}}</td> + <td>{{this.total}}</td> + <td>{{this.remaining}}</td> + </tr> + {{/each}} + </table> +</body> +</html> + diff --git a/src/templates/ledger.html b/src/templates/ledger.html new file mode 100644 index 0000000..0ac9b0e --- /dev/null +++ b/src/templates/ledger.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <title>{{name}}'s Budget</title> + <meta charset="UTF-8"> + <link rel="stylesheet" type="text/css" href="static/styles.css"> +</head> + +<body> + <h1>{{name}}'s Budget</h1> + <div> + <a href="/ledger"><button class="bold">Ledger</button></a> + <a href="/summary"><button>Summary</button></a> + <a href="/goals"><button>Goals</button></a> + <a href="/expected"><button>Expected</button></a> + </div> + <form method="post" action="/transaction"> + <input id="datePicker" name="when" placeholder="date" type="date"> + <input name="where" placeholder="where" type="text"> + <input name="amount" placeholder="amount" type="number" step="0.01"> + <input name="category" placeholder="category" type="text"> + <input name="subcategory" placeholder="tags (csv)" type="text"> + <input type="submit" value="Add"> + </form> + <table> + <tr> + <th></th> + <th>When</th> + <th>Where</th> + <th>Amount</th> + <th>Category</th> + <th>Tags</th> + </tr> + {{#each ledger}} + <tr> + <td>{{this.index}}</td> + <td>{{this.when}}</td> + <td>{{this.where}}</td> + <td>{{this.amount}}</td> + <td>{{this.category}}</td> + <td>{{this.subcategory}}</td> + </tr> + {{/each}} + </table> + <script> + document.getElementById('datePicker').value = new Date().toLocaleDateString(); + </script> +</body> +</html> + diff --git a/src/templates/summary.html b/src/templates/summary.html index 5bf828e..bffd46b 100644 --- a/src/templates/summary.html +++ b/src/templates/summary.html @@ -9,37 +9,74 @@ <body> <h1>{{name}}'s Budget</h1> - <form method="post" action="/transaction"> - <input id="datePicker" name="when" placeholder="date" type="date"> - <input name="where" placeholder="where" type="text"> - <input name="amount" placeholder="amount" type="number" step="0.01"> - <input name="category" placeholder="category" type="text"> - <input name="subcategory" placeholder="tags (csv)" type="text"> - <input type="submit" value="Add"> - </form> - <table> - <tr> - <th></th> - <th>When</th> - <th>Where</th> - <th>Amount</th> - <th>Category</th> - <th>Tags</th> - </tr> - {{#each ledger}} - <tr> - <td>{{this.index}}</td> - <td>{{this.when}}</td> - <td>{{this.where}}</td> - <td>{{this.amount}}</td> - <td>{{this.category}}</td> - <td>{{this.subcategory}}</td> - </tr> - {{/each}} - </table> - <script> - document.getElementById('datePicker').value = new Date().toLocaleDateString(); - </script> + <div> + <a href="/ledger"><button>Ledger</button></a> + <a href="/summary"><button class="bold">Summary</button></a> + <a href="/goals"><button>Goals</button></a> + <a href="/expected"><button>Expected</button></a> + </div> + + <div class="summary-panel"> + <h2>Weekly</h2> + <table class="table"> + <tr> + <th>Year</th> + <th>Week</th> + <th>In</th> + <th>Out</th> + <th>Net</th> + </tr> + {{#each week}} + <tr> + <td>{{this.y}}</td> + <td>{{this.w}}</td> + <td>{{this.in}}</td> + <td>{{this.out}}</td> + <td class="{{this.classes}}">{{this.net}}</td> + </tr> + {{/each}} + </table> + </div> + <div class="summary-panel"> + <h2>Monthly</h2> + <table class="table"> + <tr> + <th>Year</th> + <th>Month</th> + <th>In</th> + <th>Out</th> + <th>Net</th> + </tr> + {{#each month}} + <tr> + <td>{{this.y}}</td> + <td>{{this.m}}</td> + <td>{{this.in}}</td> + <td>{{this.out}}</td> + <td class="{{this.classes}}">{{this.net}}</td> + </tr> + {{/each}} + </table> + </div> + <div class="summary-panel"> + <h2>Yearly</h2> + <table class="table"> + <tr> + <th>Year</th> + <th>In</th> + <th>Out</th> + <th>Net</th> + </tr> + {{#each year}} + <tr> + <td>{{this.y}}</td> + <td>{{this.in}}</td> + <td>{{this.out}}</td> + <td class="{{this.classes}}">{{this.net}}</td> + </tr> + {{/each}} + </table> + </div> </body> -</html> +</html>
\ No newline at end of file |