From a173b8c70cb5ef3bf3eab9beef29b11fda7d3ad9 Mon Sep 17 00:00:00 2001 From: Joaquin Date: Tue, 6 May 2025 17:02:37 -0700 Subject: [PATCH 01/46] feature/app now receives exchange rates from API --- app.js | 1 + src/public/json/countries.json | 132 +++++++++++++++++++++++++++++ src/public/scripts/exchangeRate.js | 7 ++ src/public/scripts/geolocation.js | 31 +++++-- src/util/currencies.js | 0 src/views/partials/footer.ejs | 4 +- 6 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 src/public/json/countries.json create mode 100644 src/public/scripts/exchangeRate.js create mode 100644 src/util/currencies.js diff --git a/app.js b/app.js index 0310bf1..ce48655 100644 --- a/app.js +++ b/app.js @@ -40,6 +40,7 @@ async function initDatabase() { // For any collection, init here users = await getCollection(db, "users"); plans = await getCollection(db, "plans"); + currencies = await getCollection(db, "currencies"); } /*** ROUTINGS ***/ diff --git a/src/public/json/countries.json b/src/public/json/countries.json new file mode 100644 index 0000000..039a74c --- /dev/null +++ b/src/public/json/countries.json @@ -0,0 +1,132 @@ +{ + "countries": [ + { + "country": "United States", + "currency_abbreviation": "USD" + }, + { + "country": "Eurozone", + "currency_abbreviation": "EUR" + }, + { + "country": "United Kingdom", + "currency_abbreviation": "GBP" + }, + { + "country": "Japan", + "currency_abbreviation": "JPY" + }, + { + "country": "Switzerland", + "currency_abbreviation": "CHF" + }, + { + "country": "Canada", + "currency_abbreviation": "CAD" + }, + { + "country": "Australia", + "currency_abbreviation": "AUD" + }, + { + "country": "China", + "currency_abbreviation": "CNY" + }, + { + "country": "India", + "currency_abbreviation": "INR" + }, + { + "country": "Russia", + "currency_abbreviation": "RUB" + }, + { + "country": "Brazil", + "currency_abbreviation": "BRL" + }, + { + "country": "Mexico", + "currency_abbreviation": "MXN" + }, + { + "country": "South Korea", + "currency_abbreviation": "KRW" + }, + { + "country": "Indonesia", + "currency_abbreviation": "IDR" + }, + { + "country": "Bulgaria", + "currency_abbreviation": "BGN" + }, + { + "country": "Czech Republic", + "currency_abbreviation": "CZK" + }, + { + "country": "Denmark", + "currency_abbreviation": "DKK" + }, + { + "country": "Hong Kong", + "currency_abbreviation": "HKD" + }, + { + "country": "Hungary", + "currency_abbreviation": "HUF" + }, + { + "country": "Israel", + "currency_abbreviation": "ILS" + }, + { + "country": "Iceland", + "currency_abbreviation": "ISK" + }, + { + "country": "Malaysia", + "currency_abbreviation": "MYR" + }, + { + "country": "Norway", + "currency_abbreviation": "NOK" + }, + { + "country": "New Zealand", + "currency_abbreviation": "NZD" + }, + { + "country": "Philippines", + "currency_abbreviation": "PHP" + }, + { + "country": "Poland", + "currency_abbreviation": "PLN" + }, + { + "country": "Romania", + "currency_abbreviation": "RON" + }, + { + "country": "Sweden", + "currency_abbreviation": "SEK" + }, + { + "country": "Singapore", + "currency_abbreviation": "SGD" + }, + { + "country": "Thailand", + "currency_abbreviation": "THB" + }, + { + "country": "Turkey", + "currency_abbreviation": "TRY" + }, + { + "country": "South Africa", + "currency_abbreviation": "ZAR" + } + ] +} \ No newline at end of file diff --git a/src/public/scripts/exchangeRate.js b/src/public/scripts/exchangeRate.js new file mode 100644 index 0000000..61cad77 --- /dev/null +++ b/src/public/scripts/exchangeRate.js @@ -0,0 +1,7 @@ +function convert(from) { + fetch(`https://api.frankfurter.dev/v1/latest?base=${from}`) + .then((resp) => resp.json()) + .then((data) => { + console.log(data); + }); +} \ No newline at end of file diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index ebe001b..74e5652 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -1,24 +1,43 @@ +async function diffCountries() { + const res = await fetch("/static/json/countries.json"); + const data = await res.json(); + + return data.countries; +} + function getLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(success, error); } } -function success(position) { +async function success(position) { const { latitude, longitude } = position.coords; + let countryCurrency; - fetch("api/location" , { + const countries = await diffCountries(); + + const res = await fetch("api/location" , { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ latitude, longitude }) - }).then(res => res.json()) - .then(data => { - console.log(data.results[0].formatted_address); - }) + }); + + const data = await res.json(); + + countries.forEach(country => { + if (country.country === data.results[0].formatted_address) { + countryCurrency = country.currency_abbreviation; + + convert(countryCurrency); + } + }); } function error(err) { console.error("Geolocation error: ", err); } + +getLocation(); \ No newline at end of file diff --git a/src/util/currencies.js b/src/util/currencies.js new file mode 100644 index 0000000..e69de29 diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index 0c2379c..ed0882c 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -1,11 +1,11 @@ - + \ No newline at end of file From 5bbfde312720808ca1073109c587a65890c69bf7 Mon Sep 17 00:00:00 2001 From: Joaquin Paredes Date: Tue, 6 May 2025 18:13:31 -0700 Subject: [PATCH 02/46] feature/improved data gathering from API --- src/public/scripts/exchangeRate.js | 11 +++++------ src/public/scripts/geolocation.js | 2 +- src/util/currencies.js | 6 ++++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/public/scripts/exchangeRate.js b/src/public/scripts/exchangeRate.js index 61cad77..8d6e08f 100644 --- a/src/public/scripts/exchangeRate.js +++ b/src/public/scripts/exchangeRate.js @@ -1,7 +1,6 @@ -function convert(from) { - fetch(`https://api.frankfurter.dev/v1/latest?base=${from}`) - .then((resp) => resp.json()) - .then((data) => { - console.log(data); - }); +async function convert(from) { + const res = await fetch(`https://api.frankfurter.dev/v1/latest?base=${from}`); + const data = await res.json(); + + return data; } \ No newline at end of file diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 74e5652..e5c55b8 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -40,4 +40,4 @@ function error(err) { console.error("Geolocation error: ", err); } -getLocation(); \ No newline at end of file +//getLocation(); \ No newline at end of file diff --git a/src/util/currencies.js b/src/util/currencies.js index e69de29..8eb4ec3 100644 --- a/src/util/currencies.js +++ b/src/util/currencies.js @@ -0,0 +1,6 @@ +const { getCollection } = require('./src/database/connection'); + +async function saveExchangeRates() { + const { currencies } = getCollection(); + await currencies.insertMany(convert()); +} \ No newline at end of file From 1652d05fcd8af1cdba429365d8c8a9fd0c3d694f Mon Sep 17 00:00:00 2001 From: Joaquin Date: Tue, 6 May 2025 18:15:32 -0700 Subject: [PATCH 03/46] feature/push to pull --- src/public/scripts/geolocation.js | 2 +- src/util/currencies.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 74e5652..e5c55b8 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -40,4 +40,4 @@ function error(err) { console.error("Geolocation error: ", err); } -getLocation(); \ No newline at end of file +//getLocation(); \ No newline at end of file diff --git a/src/util/currencies.js b/src/util/currencies.js index e69de29..0519ecb 100644 --- a/src/util/currencies.js +++ b/src/util/currencies.js @@ -0,0 +1 @@ + \ No newline at end of file From 17f11c85df33858db943abeecd4c4166d01a37e0 Mon Sep 17 00:00:00 2001 From: Joaquin Date: Tue, 6 May 2025 21:26:45 -0700 Subject: [PATCH 04/46] feature/improved the getting of rates --- app.js | 18 +++--------- src/public/scripts/exchangeRate.js | 6 ---- src/public/scripts/geolocation.js | 37 +++++------------------- src/router/user.js | 13 +++++++++ src/{public/json => util}/countries.json | 0 src/util/currencies.js | 6 ---- src/util/exchangeRate.js | 18 ++++++++++++ src/views/home.ejs | 2 ++ src/views/partials/footer.ejs | 2 -- 9 files changed, 44 insertions(+), 58 deletions(-) delete mode 100644 src/public/scripts/exchangeRate.js rename src/{public/json => util}/countries.json (100%) delete mode 100644 src/util/currencies.js create mode 100644 src/util/exchangeRate.js diff --git a/app.js b/app.js index ce48655..f8504dd 100644 --- a/app.js +++ b/app.js @@ -6,12 +6,11 @@ const path = require('path'); const joi = require('joi'); require('dotenv').config(); - const app = express(); const port = process.env.PORT || 3000; -const mongoURI = process.env.mongoURI || "mongodb://localhost:27017/"; -const database = process.env.database || "knoldus"; // Database name +const mongoURI = process.env.mongoURI; +const database = process.env.database; // Database name const secret = process.env.secret || "123-secret-xyz"; /*** Sessions ***/ @@ -34,13 +33,14 @@ app.use(express.json()); const { connectMongo, getCollection } = require("./src/database/connection"); let users; +let rates; async function initDatabase() { const db = await connectMongo(mongoURI, database); // For any collection, init here users = await getCollection(db, "users"); plans = await getCollection(db, "plans"); - currencies = await getCollection(db, "currencies"); + rates = await getCollection(db, "rates"); } /*** ROUTINGS ***/ @@ -72,16 +72,6 @@ app.get('/aboutUs', (req, res) => { return res.status(status.Ok); }); -app.post('/api/location', async (req,res) => { - const { latitude, longitude } = req.body; - - const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&result_type=country&key=${process.env.geolocation_api}`); - - const data = await response.json(); - - res.json(data); -}); - // Initialize database and start app initDatabase().then(() => { console.log("Successfully connected to MongoDB"); diff --git a/src/public/scripts/exchangeRate.js b/src/public/scripts/exchangeRate.js deleted file mode 100644 index 8d6e08f..0000000 --- a/src/public/scripts/exchangeRate.js +++ /dev/null @@ -1,6 +0,0 @@ -async function convert(from) { - const res = await fetch(`https://api.frankfurter.dev/v1/latest?base=${from}`); - const data = await res.json(); - - return data; -} \ No newline at end of file diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index e5c55b8..e4a48e2 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -1,43 +1,20 @@ -async function diffCountries() { - const res = await fetch("/static/json/countries.json"); - const data = await res.json(); - - return data.countries; -} - function getLocation() { if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition(success, error); + navigator.geolocation.getCurrentPosition(getLatestExchange, error); } } -async function success(position) { - const { latitude, longitude } = position.coords; - let countryCurrency; - - const countries = await diffCountries(); - - const res = await fetch("api/location" , { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ latitude, longitude }) - }); - +async function getLatestExchange(position) { + let lat = position.coords.latitude; + let lon = position.coords.longitude; + const res = await fetch(`/exRates/${lat}/${lon}`); const data = await res.json(); - countries.forEach(country => { - if (country.country === data.results[0].formatted_address) { - countryCurrency = country.currency_abbreviation; - - convert(countryCurrency); - } - }); + console.log(data); } function error(err) { console.error("Geolocation error: ", err); } -//getLocation(); \ No newline at end of file +getLocation(); \ No newline at end of file diff --git a/src/router/user.js b/src/router/user.js index 9daa7cb..60bf114 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -1,4 +1,5 @@ const status = require("../util/statuses"); +const getRates = require("../util/exchangeRate"); const bcrypt = require('bcrypt'); const joi = require("joi"); const salt = 12; @@ -222,5 +223,17 @@ module.exports = (middleware, users, plans) => { }); }); + router.get("/exRates/:lat/:lon", async (req, res) => { + if (!req.session.rates || Object.keys(req.session.rates).length == 0) { + const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${req.params.lat},${req.params.lon}&result_type=country&key=${process.env.geolocation_api}`); + const data = await response.json(); + + let country = data.results[0].formatted_address; + req.session.rates = await getRates(country); + } + + return res.status(status.Ok).send({ rates: req.session.rates }); + }) + return router; }; diff --git a/src/public/json/countries.json b/src/util/countries.json similarity index 100% rename from src/public/json/countries.json rename to src/util/countries.json diff --git a/src/util/currencies.js b/src/util/currencies.js deleted file mode 100644 index 712720d..0000000 --- a/src/util/currencies.js +++ /dev/null @@ -1,6 +0,0 @@ -const { getCollection } = require('./src/database/connection'); - -async function saveExchangeRates() { - const { currencies } = getCollection(); - await currencies.insertMany(convert()); -} diff --git a/src/util/exchangeRate.js b/src/util/exchangeRate.js new file mode 100644 index 0000000..6313988 --- /dev/null +++ b/src/util/exchangeRate.js @@ -0,0 +1,18 @@ +const countries = require("./countries.json").countries; + +async function getRates(country) { + let abbr; + countries.forEach(c => { + if (c.country === country) { + abbr = c.currency_abbreviation; + } + }); + + const res = await fetch(`https://api.frankfurter.dev/v1/latest?base=${abbr}`); + const data = await res.json(); + let rates = data.rates; + + return rates; +} + +module.exports = getRates; \ No newline at end of file diff --git a/src/views/home.ejs b/src/views/home.ejs index 3637163..b3b69e7 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -11,5 +11,7 @@ <% } %> + + <%- include("./partials/navBar") %> <%- include("./partials/footer") %> diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index ed0882c..ef5e262 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -5,7 +5,5 @@ - - \ No newline at end of file From a40b6d514bd8330a78a23ca44349861a39b8ce18 Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Tue, 6 May 2025 23:33:05 -0700 Subject: [PATCH 05/46] forgot password js --- app.js | 25 ++++++- package-lock.json | 7 ++ package.json | 1 + src/auth/forgotPass.js | 55 ++++++++++++++ src/router/user.js | 150 +++++++++++++++++++-------------------- src/views/forgotPass.ejs | 25 +++++++ src/views/signup.ejs | 92 ++++++++++++------------ 7 files changed, 231 insertions(+), 124 deletions(-) create mode 100644 src/auth/forgotPass.js create mode 100644 src/views/forgotPass.ejs diff --git a/app.js b/app.js index fa59c80..8b23e80 100644 --- a/app.js +++ b/app.js @@ -71,7 +71,30 @@ app.get('/aboutUs', (req, res) => { return res.status(status.Ok); }); -app.post('/api/location', async (req,res) => { +app.get('/forgotPassword', (req, res) => { + res.render('forgotPass'); + return res.status(status.Ok); +}); + +app.get('/reset/:token', async (req, res) => { + const token = req.params.token; + + const user = await users.findOne({ + resetToken: token, + resetTokenExpires: { $gt: Date.now() }, + }); + + if (!user) { + req.session.error = 'reset link not valid or has expired'; + return res.redirect('/forgotPassword'); + } + res.render('resetPassword', { + token: token, + errMessage: req.session.error || '', + }); +}); + +app.post('/api/location', async (req, res) => { const { latitude, longitude } = req.body; const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&result_type=country&key=${process.env.geolocation_api}`); diff --git a/package-lock.json b/package-lock.json index 95750e3..a416b45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcrypt": "^5.1.1", "connect-mongo": "^5.1.0", + "crypto": "^1.0.1", "dotenv": "^16.5.0", "ejs": "^3.1.10", "express": "^5.1.0", @@ -483,6 +484,12 @@ "node": ">=6.6.0" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/package.json b/package.json index 4f1048f..08ad27d 100755 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "bcrypt": "^5.1.1", "connect-mongo": "^5.1.0", + "crypto": "^1.0.1", "dotenv": "^16.5.0", "ejs": "^3.1.10", "express": "^5.1.0", diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js new file mode 100644 index 0000000..2f079c1 --- /dev/null +++ b/src/auth/forgotPass.js @@ -0,0 +1,55 @@ +const express = require('express'); +const { server } = require('ws'); +const crypto = require('crypto'); +const nodeMail = require('nodemailer'); +require('dotenv').config(); + +const transporter = nodeMail.createTransport({ + service: 'gmail', + auth: { + user: process.env.EMAIL_USER, + pass: process.env.PASS, + } +}); + +module.exports = (users) => { + const router = express.Router(); + + + router.post('/resetPass', async (req, res) => { + const { email } = req.body; + const user = await users.findOne({ email }); + + if (!user) { + req.session.error = 'No user found' + return res.redirect('/forgotPassword'); + } + + const token = crypto.randomBytes(32).toString('hex'); + const expiration = Date.now() + 360000; + + await users.updateOne({ email }, { + $set: { resetToken: token, resetTokenExpires: expiration } + }); + + const resetUrl = `http://localhost:3000/reset/${token}`; + + const mailSend = { + from: process.env.EMAIL_USER, + to: email, + subject: 'Password reset', + text: `reset your password here ${resetUrl} \n this link will expire within 1 hour`, + + }; + + try { + await transporter.sendMail(mailSend); + req.session.reset = 'Reset link sent Check your email'; + res.redirect('/login'); + } catch (err) { + console.log('there was an error', err); + res.status(500).send('email failed to send try again'); + } + }); + return router; +} diff --git a/src/router/user.js b/src/router/user.js index 9daa7cb..39fb28b 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -5,7 +5,7 @@ const salt = 12; module.exports = (middleware, users, plans) => { const router = require("express").Router(); - + router.use(middleware); router.get('/home', async (req, res) => { @@ -22,17 +22,17 @@ module.exports = (middleware, users, plans) => { if (!req.session.email) { return res.status(status.Unauthorized).redirect('/login'); } - + try { // console.log(req.user.email); - const userPlans = await plans.find({userEmail: req.user.email }).toArray(); + const userPlans = await plans.find({ userEmail: req.user.email }).toArray(); // console.log(userPlans); res.render('plans', { user: req.user, plans: userPlans }); - + } catch (err) { console.error("Error fetching plans:", err); req.session.errMessage = "Could not load your plans. Please try again."; @@ -42,7 +42,7 @@ module.exports = (middleware, users, plans) => { router.get('/newPlan', (req, res) => { const errMessage = req.session.errMessage; - req.session.errMessage = ""; + req.session.errMessage = ""; res.render('newPlan', { user: req.user, errMessage: errMessage }); }); @@ -51,45 +51,45 @@ module.exports = (middleware, users, plans) => { return res.status(status.Unauthorized).redirect('/login'); } - const planSchema = joi.object({ - name: joi.string().min(3).max(100).required(), - retirementAge: joi.number().min(18).max(120).required(), - retirementExpenses: joi.number().min(0).required(), - retirementAssets: joi.number().min(0).required(), - retirementLiabilities: joi.number().min(0).required(), - }); - - const validationOptions = { convert: true, abortEarly: false }; - const { error, value } = planSchema.validate(req.body, validationOptions); - - if (error) { - console.error("Plan validation error:", error.details); - req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); - res.status(status.BadRequest).redirect("/newPlan"); - return; - } - const newPlan = { - userEmail: req.user.email, - name: value.name, - retirementAge: value.retirementAge, - retirementExpenses: value.retirementExpenses, - retirementAssets: value.retirementAssets, - retirementLiabilities: value.retirementLiabilities, - progress: "0%" - }; - - try{ - await plans.insertOne(newPlan); - req.session.errMessage = ""; - res.redirect('/plans'); - } - catch(err){ - console.error("Error saving plan:", err); - req.session.errMessage = "An error occurred while saving your plan. Please try again."; - res.status(status.InternalServerError).redirect("/newPlan"); - } + const planSchema = joi.object({ + name: joi.string().min(3).max(100).required(), + retirementAge: joi.number().min(18).max(120).required(), + retirementExpenses: joi.number().min(0).required(), + retirementAssets: joi.number().min(0).required(), + retirementLiabilities: joi.number().min(0).required(), }); + const validationOptions = { convert: true, abortEarly: false }; + const { error, value } = planSchema.validate(req.body, validationOptions); + + if (error) { + console.error("Plan validation error:", error.details); + req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); + res.status(status.BadRequest).redirect("/newPlan"); + return; + } + const newPlan = { + userEmail: req.user.email, + name: value.name, + retirementAge: value.retirementAge, + retirementExpenses: value.retirementExpenses, + retirementAssets: value.retirementAssets, + retirementLiabilities: value.retirementLiabilities, + progress: "0%" + }; + + try { + await plans.insertOne(newPlan); + req.session.errMessage = ""; + res.redirect('/plans'); + } + catch (err) { + console.error("Error saving plan:", err); + req.session.errMessage = "An error occurred while saving your plan. Please try again."; + res.status(status.InternalServerError).redirect("/newPlan"); + } + }); + router.get('/more', (req, res) => { res.render('more', { user: req.user }); return res.status(status.Ok); @@ -107,13 +107,13 @@ module.exports = (middleware, users, plans) => { router.get('/questionnaire', (req, res) => { const errMessage = req.session.errMessage; - req.session.errMessage = ""; + req.session.errMessage = ""; res.render('questionnaire', { user: req.user, errMessage: errMessage }); }); router.post('/questionnaire', (req, res) => { // console.log("Questionnaire POST body:", req.body); - + const questionnaireSchema = joi.object({ dob: joi.date().required(), education: joi.string().valid('primary', 'secondary', 'tertiary', 'postgraduate').required(), @@ -123,22 +123,22 @@ module.exports = (middleware, users, plans) => { assets: joi.number().min(0).required(), liabilities: joi.number().min(0).required(), }); - - const validationOptions = { convert: true, abortEarly: false }; + + const validationOptions = { convert: true, abortEarly: false }; const { error, value } = questionnaireSchema.validate(req.body, validationOptions); - - if (error) { + + if (error) { console.error("Questionnaire validation error:", error.details); - req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); - res.status(status.BadRequest).redirect("/questionnaire"); - return; + req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); + res.status(status.BadRequest).redirect("/questionnaire"); + return; } - - users.updateOne( - { email: req.session.email }, - { - $set: { - financialData: true, + + users.updateOne( + { email: req.session.email }, + { + $set: { + financialData: true, dob: value.dob, education: value.education, maritalStatus: value.maritalStatus, @@ -146,26 +146,26 @@ module.exports = (middleware, users, plans) => { expenses: value.expenses, assets: value.assets, liabilities: value.liabilities, - } + } } - ).then((result) => { + ).then((result) => { if (result.matchedCount === 0) { console.log(`User not found during questionnaire update: ${req.session.email}`); - req.session.errMessage = "User session invalid. Please log in again."; - res.status(status.NotFound).redirect("/login"); + req.session.errMessage = "User session invalid. Please log in again."; + res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.email}`); } - - req.session.errMessage = ""; - res.status(status.Ok).redirect("/home"); - - }).catch(err => { + + req.session.errMessage = ""; + res.status(status.Ok).redirect("/home"); + + }).catch(err => { console.error("Error updating questionnaire in database:", err); req.session.errMessage = "An error occurred while saving your information. Please try again."; - res.status(status.InternalServerError).redirect("/questionnaire"); + res.status(status.InternalServerError).redirect("/questionnaire"); }); }); @@ -181,7 +181,7 @@ module.exports = (middleware, users, plans) => { if (valid.err) { req.session.errMessage = "Invalid input", - res.status(status.BadRequest); + res.status(status.BadRequest); return res.redirect("/profile"); } @@ -205,20 +205,20 @@ module.exports = (middleware, users, plans) => { ).then((result) => { if (result.matchedCount === 0) { console.log(`User not found during account update: ${req.session.email}`); - req.session.errMessage = "User session invalid. Please log in again."; - res.status(status.NotFound).redirect("/login"); + req.session.errMessage = "User session invalid. Please log in again."; + res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { console.log(`User account data unchanged (already up-to-date): ${req.session.email}`); } - - req.session.errMessage = ""; - return res.status(status.Ok).redirect("/profile"); - }).catch(err => { + + req.session.errMessage = ""; + return res.status(status.Ok).redirect("/profile"); + }).catch(err => { console.error("Error updating account in database:", err); req.session.errMessage = "An error occurred while saving your information. Please try again."; - return res.status(status.InternalServerError).redirect("/profile"); + return res.status(status.InternalServerError).redirect("/profile"); }); }); diff --git a/src/views/forgotPass.ejs b/src/views/forgotPass.ejs new file mode 100644 index 0000000..cd3a15f --- /dev/null +++ b/src/views/forgotPass.ejs @@ -0,0 +1,25 @@ +<%- include("./partials/fileHeader") %> + +
+
+
+
+

Reset Password

+
+
+ + +
+
+ +
+
+
+
+
+ + <%- include("./partials/footer") %> diff --git a/src/views/signup.ejs b/src/views/signup.ejs index 1234a2b..b3c7586 100644 --- a/src/views/signup.ejs +++ b/src/views/signup.ejs @@ -1,52 +1,48 @@ <%- include("./partials/fileHeader") %> -<%- include("./partials/headerStart") %> + <%- include("./partials/headerStart") %> -
-
-
-
-

Signup

-
-
- - -
-
- - -
-
- - -
-
- - - - -
-
<%= errMessage %>
-
- -
-
- - Login -
+
+
+ +
+

Signup

+
+
+ + +
+
+ + +
+
+ + + + +
+
+ <%= errMessage %> +
+
+ +
+
+ + Login +
+
+
- -
-
+ -<%- include("./partials/footer") %> \ No newline at end of file + <%- include("./partials/footer") %> From f0b1089e15003dcc05195b1073099eeda0a18b99 Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Wed, 7 May 2025 00:52:01 -0700 Subject: [PATCH 06/46] routes for forgotPass --- app.js | 1 + src/auth/forgotPass.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 8b23e80..8ed961a 100644 --- a/app.js +++ b/app.js @@ -114,6 +114,7 @@ initDatabase().then(() => { // Import middleware & apply to user routes const middleware = require("./src/auth/middleware")(users, plans); app.use(require('./src/router/user')(middleware, users, plans)); + app.use(require('./src/auth/forgetPass')(users)); // 404 handler app.get('/*splat', (req, res) => { diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js index 2f079c1..f83b392 100644 --- a/src/auth/forgotPass.js +++ b/src/auth/forgotPass.js @@ -1,5 +1,4 @@ const express = require('express'); -const { server } = require('ws'); const crypto = require('crypto'); const nodeMail = require('nodemailer'); require('dotenv').config(); @@ -11,11 +10,10 @@ const transporter = nodeMail.createTransport({ pass: process.env.PASS, } }); - +// users info module.exports = (users) => { const router = express.Router(); - router.post('/resetPass', async (req, res) => { const { email } = req.body; const user = await users.findOne({ email }); From 617fc8d8775aff1f1ce2f0e60cd26d049a88382d Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Wed, 7 May 2025 10:49:08 -0700 Subject: [PATCH 07/46] indentation of dashboard --- app.js | 2 + src/views/dashboard.ejs | 136 ++++++++++++++++++++-------------------- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/app.js b/app.js index 8ed961a..a4f0263 100644 --- a/app.js +++ b/app.js @@ -94,6 +94,8 @@ app.get('/reset/:token', async (req, res) => { }); }); +app.post('reset') + app.post('/api/location', async (req, res) => { const { latitude, longitude } = req.body; diff --git a/src/views/dashboard.ejs b/src/views/dashboard.ejs index 3de6635..182184e 100644 --- a/src/views/dashboard.ejs +++ b/src/views/dashboard.ejs @@ -1,99 +1,99 @@ <%- include("./partials/fileHeader") %> <%- include("./partials/header") %> -
- -
- - + -
- -
-
+ +
+ +
+
- - - -
+ + + +

- Retirement goal

+ Retirement goal

- $53k

-
-
+ class="block text-right antialiased tracking-normal font-sans text-2xl font-semibold leading-snug text-blue-gray-900"> + $53k +
+

- Make this percent bar or graph or something  than last - week + Make this percent bar or graph or something  than last + week

-
+
-
+

- Retirement goal

+ Retirement goal

- $53k

-
-
+ class="block text-right antialiased tracking-normal font-sans text-2xl font-semibold leading-snug text-blue-gray-900"> + $53k +
+

- Make this percent bar or graph or something  than last - week + Make this percent bar or graph or something  than last + week

-
+
- - - - - - - - - - - - - - - - - - - - - - - -
-
+ + + + + + + + + + + + + + + + + + + + + + + +
+
-

+

Retirement goal

-

$53k

-

+

Make this percent bar or graph or something  than last week -

+

-
-
-
+
+
+
-
-
+ + - <%- include("./partials/navBar") %> - <%- include("./partials/footer") %> +<%- include("./partials/navBar") %> + <%- include("./partials/footer") %> From 088f9ea55ae60fada9cffeb1a5a0546897b431c8 Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Wed, 7 May 2025 13:03:26 -0700 Subject: [PATCH 08/46] reset pass logic --- app.js | 52 ++++++++++++++++++++++++++++++++++++---- package-lock.json | 11 ++++++++- package.json | 3 ++- src/auth/forgotPass.js | 8 +++++-- src/views/forgotPass.ejs | 3 ++- src/views/login.ejs | 4 ++-- src/views/resetPass.ejs | 36 ++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 src/views/resetPass.ejs diff --git a/app.js b/app.js index a4f0263..9da8e44 100644 --- a/app.js +++ b/app.js @@ -72,10 +72,15 @@ app.get('/aboutUs', (req, res) => { }); app.get('/forgotPassword', (req, res) => { - res.render('forgotPass'); + res.render('forgotPass', { error: req.session.error, reset: req.session.reset }); return res.status(status.Ok); }); +app.get('reset', (req, res) => { + return res.render('resetPass'); +}) + +// Reset with token given to user via email app.get('/reset/:token', async (req, res) => { const token = req.params.token; @@ -88,13 +93,52 @@ app.get('/reset/:token', async (req, res) => { req.session.error = 'reset link not valid or has expired'; return res.redirect('/forgotPassword'); } + const error = req.session.error; + req.session.error = ''; + res.render('resetPassword', { token: token, - errMessage: req.session.error || '', + errMessage: error, }); }); -app.post('reset') +app.post('/resetLink', async (req, res) => { + const { token, password, confirmPassword, } = req.body; + + if (!token || !password || !confirmPassword) { + req.session.error = 'field may be missing'; + return res.redirect(`/reset/${token}`); + } + if (password !== confirmPassword) { + req.session.error = 'passwords do not match'; + return res.redirect(`/reset/${token}`); + } + const user = await users.findOne({ + resetToken: token, + resetTokenExpires: { $gt: Date.now() }, + }); + + if (!user) { + req.session.error = 'Reset link is invalid.'; + return res.redirect('/forgotPassword'); + } + const hashPassword = await bcrypt.hash(password, 12); + + await users.updateOne( + { + email: user.email + }, + { + $set: { + password: hashPassword, + resetToken: '', + resetTokenExpires: 0, + }, + } + ); + req.session.success = 'Password has been reset'; + res.redirect('/login'); +}); app.post('/api/location', async (req, res) => { const { latitude, longitude } = req.body; @@ -112,11 +156,11 @@ initDatabase().then(() => { // Import authentication handler app.use(require("./src/auth/authentication")(users)); + app.use(require('./src/auth/forgotPass')(users)); // Import middleware & apply to user routes const middleware = require("./src/auth/middleware")(users, plans); app.use(require('./src/router/user')(middleware, users, plans)); - app.use(require('./src/auth/forgetPass')(users)); // 404 handler app.get('/*splat', (req, res) => { diff --git a/package-lock.json b/package-lock.json index a416b45..fe45a7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "express": "^5.1.0", "express-session": "^1.18.1", "joi": "^17.13.3", - "mongodb": "^6.16.0" + "mongodb": "^6.16.0", + "nodemailer": "^7.0.2" }, "devDependencies": { "nodemon": "^3.1.10" @@ -1437,6 +1438,14 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/nodemailer": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.2.tgz", + "integrity": "sha512-SYsisPeLFYli5Q+BCGSyHT5CVvezPmQjHgINV9KVvVLV1aktuoD4E0Np9Q3ND9I481qIHzUQzVT+Tl/Tw7Ivdg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", diff --git a/package.json b/package.json index 08ad27d..8f0f856 100755 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "express": "^5.1.0", "express-session": "^1.18.1", "joi": "^17.13.3", - "mongodb": "^6.16.0" + "mongodb": "^6.16.0", + "nodemailer": "^7.0.2" }, "devDependencies": { "nodemon": "^3.1.10" diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js index f83b392..8e4880c 100644 --- a/src/auth/forgotPass.js +++ b/src/auth/forgotPass.js @@ -14,9 +14,12 @@ const transporter = nodeMail.createTransport({ module.exports = (users) => { const router = express.Router(); - router.post('/resetPass', async (req, res) => { + router.post('/auth/resetPass', async (req, res) => { + console.log("we are inside of the post"); const { email } = req.body; const user = await users.findOne({ email }); + req.session.error = ''; + req.session.reset = ''; if (!user) { req.session.error = 'No user found' @@ -43,7 +46,7 @@ module.exports = (users) => { try { await transporter.sendMail(mailSend); req.session.reset = 'Reset link sent Check your email'; - res.redirect('/login'); + res.redirect('/forgotPassword'); } catch (err) { console.log('there was an error', err); res.status(500).send('email failed to send try again'); @@ -51,3 +54,4 @@ module.exports = (users) => { }); return router; } + diff --git a/src/views/forgotPass.ejs b/src/views/forgotPass.ejs index cd3a15f..8412d3a 100644 --- a/src/views/forgotPass.ejs +++ b/src/views/forgotPass.ejs @@ -2,7 +2,7 @@
-
+

Reset Password


@@ -12,6 +12,7 @@ class="border focus:border-gray-600 w-full rounded-md text-base px-2 py-1 focus:outline-none focus:ring-0 " placeholder="Enter Email" />
+
<%= error%>
@@ -41,4 +41,4 @@
-<%- include("./partials/footer") %> \ No newline at end of file +<%- include("./partials/footer") %> diff --git a/src/views/resetPass.ejs b/src/views/resetPass.ejs new file mode 100644 index 0000000..d923d50 --- /dev/null +++ b/src/views/resetPass.ejs @@ -0,0 +1,36 @@ +<%- include("./partials/fileHeader") %> + +
+
+ +
+

Reset Password

+
+ +
+ + +
+
+ + +
+ <% if (typeof errMessage !== 'undefined') { %> +
+ <%= errMessage %> +
+ <% } %> +
+ +
+
+ +
+
+<%- include("./partials/footer") %> From 2ad43139defd3a5f1c311f8281db7c3a1dd23679 Mon Sep 17 00:00:00 2001 From: Joaquin Date: Wed, 7 May 2025 13:06:22 -0700 Subject: [PATCH 09/46] merged dev and added some challenge 1 functionality --- .vscode/settings.json | 4 + app.js | 2 - src/public/scripts/geolocation.js | 4 +- src/public/scripts/header.js | 0 src/public/svgs/assets.svg | 4 + src/public/svgs/dashboard.svg | 3 + src/public/svgs/flags/AUD.svg | 8 + src/public/svgs/flags/BRL.svg | 45 ++++ src/public/svgs/flags/CAD.svg | 4 + src/public/svgs/flags/CNY.svg | 11 + src/public/svgs/flags/GBP.svg | 7 + src/public/svgs/flags/INR.svg | 25 ++ src/public/svgs/flags/JPY.svg | 11 + src/public/svgs/flags/MXN.svg | 382 +++++++++++++++++++++++++++++ src/public/svgs/flags/RUB.svg | 5 + src/public/svgs/flags/USD.svg | 9 + src/public/svgs/more.svg | 3 + src/public/svgs/plans.svg | 3 + src/router/user.js | 8 +- src/util/exchangeRate.js | 2 +- src/views/dashboard.ejs | 184 +++++++------- src/views/home.ejs | 16 -- src/views/partials/fileHeader.ejs | 2 +- src/views/partials/footer.ejs | 2 +- src/views/partials/header.ejs | 28 ++- src/views/partials/headerStart.ejs | 2 +- src/views/partials/navBar.ejs | 17 +- 27 files changed, 656 insertions(+), 135 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/public/scripts/header.js create mode 100644 src/public/svgs/assets.svg create mode 100644 src/public/svgs/dashboard.svg create mode 100644 src/public/svgs/flags/AUD.svg create mode 100644 src/public/svgs/flags/BRL.svg create mode 100644 src/public/svgs/flags/CAD.svg create mode 100644 src/public/svgs/flags/CNY.svg create mode 100644 src/public/svgs/flags/GBP.svg create mode 100644 src/public/svgs/flags/INR.svg create mode 100644 src/public/svgs/flags/JPY.svg create mode 100644 src/public/svgs/flags/MXN.svg create mode 100644 src/public/svgs/flags/RUB.svg create mode 100644 src/public/svgs/flags/USD.svg create mode 100644 src/public/svgs/more.svg create mode 100644 src/public/svgs/plans.svg delete mode 100644 src/views/home.ejs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c8a7ed4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.fontFamily": "Monocraft", + "editor.fontLigatures": true +} \ No newline at end of file diff --git a/app.js b/app.js index fc5dbb2..1945cac 100644 --- a/app.js +++ b/app.js @@ -33,14 +33,12 @@ app.use(express.json()); const { connectMongo, getCollection } = require("./src/database/connection"); let users; -let rates; async function initDatabase() { const db = await connectMongo(mongoURI, database); // For any collection, init here users = await getCollection(db, "users"); plans = await getCollection(db, "plans"); - rates = await getCollection(db, "rates"); } /*** ROUTINGS ***/ diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index e4a48e2..4bdf42a 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -15,6 +15,4 @@ async function getLatestExchange(position) { function error(err) { console.error("Geolocation error: ", err); -} - -getLocation(); \ No newline at end of file +} \ No newline at end of file diff --git a/src/public/scripts/header.js b/src/public/scripts/header.js new file mode 100644 index 0000000..e69de29 diff --git a/src/public/svgs/assets.svg b/src/public/svgs/assets.svg new file mode 100644 index 0000000..824466d --- /dev/null +++ b/src/public/svgs/assets.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/src/public/svgs/dashboard.svg b/src/public/svgs/dashboard.svg new file mode 100644 index 0000000..e10e231 --- /dev/null +++ b/src/public/svgs/dashboard.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/public/svgs/flags/AUD.svg b/src/public/svgs/flags/AUD.svg new file mode 100644 index 0000000..96e8076 --- /dev/null +++ b/src/public/svgs/flags/AUD.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/public/svgs/flags/BRL.svg b/src/public/svgs/flags/BRL.svg new file mode 100644 index 0000000..fe1d416 --- /dev/null +++ b/src/public/svgs/flags/BRL.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/public/svgs/flags/CAD.svg b/src/public/svgs/flags/CAD.svg new file mode 100644 index 0000000..c9b23b4 --- /dev/null +++ b/src/public/svgs/flags/CAD.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/public/svgs/flags/CNY.svg b/src/public/svgs/flags/CNY.svg new file mode 100644 index 0000000..10d3489 --- /dev/null +++ b/src/public/svgs/flags/CNY.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/public/svgs/flags/GBP.svg b/src/public/svgs/flags/GBP.svg new file mode 100644 index 0000000..7991383 --- /dev/null +++ b/src/public/svgs/flags/GBP.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/public/svgs/flags/INR.svg b/src/public/svgs/flags/INR.svg new file mode 100644 index 0000000..bc47d74 --- /dev/null +++ b/src/public/svgs/flags/INR.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/public/svgs/flags/JPY.svg b/src/public/svgs/flags/JPY.svg new file mode 100644 index 0000000..cc1c181 --- /dev/null +++ b/src/public/svgs/flags/JPY.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/public/svgs/flags/MXN.svg b/src/public/svgs/flags/MXN.svg new file mode 100644 index 0000000..5a67d62 --- /dev/null +++ b/src/public/svgs/flags/MXN.svg @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/public/svgs/flags/RUB.svg b/src/public/svgs/flags/RUB.svg new file mode 100644 index 0000000..cf24301 --- /dev/null +++ b/src/public/svgs/flags/RUB.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/public/svgs/flags/USD.svg b/src/public/svgs/flags/USD.svg new file mode 100644 index 0000000..9cfd0c9 --- /dev/null +++ b/src/public/svgs/flags/USD.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/public/svgs/more.svg b/src/public/svgs/more.svg new file mode 100644 index 0000000..d0b697f --- /dev/null +++ b/src/public/svgs/more.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/public/svgs/plans.svg b/src/public/svgs/plans.svg new file mode 100644 index 0000000..70fb205 --- /dev/null +++ b/src/public/svgs/plans.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/router/user.js b/src/router/user.js index e5cb2fa..35ef92b 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -11,7 +11,7 @@ module.exports = (middleware, users, plans) => { router.use(middleware); router.get('/home', async (req, res) => { - res.render('dashboard', { user: req.user }); + res.render('dashboard', { user: req.user, location: req.session.country, countriesRates: req.session.rates }); return res.status(status.Ok); }); @@ -270,8 +270,10 @@ module.exports = (middleware, users, plans) => { const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${req.params.lat},${req.params.lon}&result_type=country&key=${process.env.geolocation_api}`); const data = await response.json(); - let country = data.results[0].formatted_address; - req.session.rates = await getRates(country); + country = data.results[0].formatted_address; + let results = await getRates(country); + req.session.rates = results.exRates; + req.session.country = results.abbreviation; } return res.status(status.Ok).send({ rates: req.session.rates }); diff --git a/src/util/exchangeRate.js b/src/util/exchangeRate.js index 6313988..2f5658d 100644 --- a/src/util/exchangeRate.js +++ b/src/util/exchangeRate.js @@ -12,7 +12,7 @@ async function getRates(country) { const data = await res.json(); let rates = data.rates; - return rates; + return { exRates: rates, abbreviation: abbr }; } module.exports = getRates; \ No newline at end of file diff --git a/src/views/dashboard.ejs b/src/views/dashboard.ejs index e4c9b36..be866a1 100644 --- a/src/views/dashboard.ejs +++ b/src/views/dashboard.ejs @@ -1,100 +1,98 @@ <%- include("./partials/fileHeader") %> - <%- include("./partials/header") %> +<%- include("./partials/header") %> -
-

Welcome: <%= user.name %>

- -
- - - Create new plan - -
- -
-
- -
- - - -
-

- Retirement goal

-

- $53k

-
-
-

- Make this percent bar or graph or something  than last - week -

-
+
+

Welcome: <%= user.name %>

+ +
+ + Create new plan +
+ +
+
+ +
+ + + +
+

+ Retirement goal

+

+ $53k

-
-
-

- Retirement goal

-

- $53k

-
-
-

- Make this percent bar or graph or something  than last - week -

-
+
+

+ Make this percent bar or graph or something  than lastweek +

+
+
+
+

+ Retirement goal

+

+ $53k

+
+
+

+ Make this percent bar or graph or something  than last + week +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
-

- Retirement goal

-

- $53k

-
-
-

- Make this percent bar or graph or something  than last - week -

-
-
-
-
+
+
+

+ Retirement goal

+

+ $53k

+
+
+

+ Make this percent bar or graph or something  than last + week +

+
+
+
-
-
-
+
+
+
+
- <%- include("./partials/navBar") %> - <%- include("./partials/footer") %> + + + +<%- include("./partials/navBar") %> +<%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/home.ejs b/src/views/home.ejs deleted file mode 100644 index 8969d97..0000000 --- a/src/views/home.ejs +++ /dev/null @@ -1,16 +0,0 @@ -<%- include("./partials/fileHeader") %> -<%- include("./partials/header") %> - -
- Welcome: <%= user.email %> - The Dashboard Page - <% if(user.authenticated) { %> - <%= user.email %> - <%= user.errMessage %> - <% } %> -
- - - -<%- include("./partials/navBar") %> -<%- include("./partials/footer") %> diff --git a/src/views/partials/fileHeader.ejs b/src/views/partials/fileHeader.ejs index 35b60ed..b15b69c 100644 --- a/src/views/partials/fileHeader.ejs +++ b/src/views/partials/fileHeader.ejs @@ -7,4 +7,4 @@ RCalculator - \ No newline at end of file + \ No newline at end of file diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index d72156e..110cbe0 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -1,6 +1,6 @@
-
+ + + + + + - \ No newline at end of file + From 73a510252be06bf2982747a7ae174daedec486ca Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Thu, 8 May 2025 23:30:13 -0700 Subject: [PATCH 24/46] update+fix/asset icons + load geolocation scripts --- src/auth/middleware.js | 7 +-- src/public/scripts/assetManager.js | 59 +++++++++++++++++++-- src/public/svgs/icons/Bike.svg | 1 + src/public/svgs/icons/Book.svg | 1 + src/public/svgs/icons/Camera.svg | 1 + src/public/svgs/icons/Car.svg | 1 + src/public/svgs/icons/Coins.svg | 1 + src/public/svgs/icons/Console.svg | 1 + src/public/svgs/icons/Desktop.svg | 1 + src/public/svgs/icons/Device.svg | 1 + src/public/svgs/icons/Electronic.svg | 1 + src/public/svgs/icons/Home.svg | 1 + src/public/svgs/icons/Laptop.svg | 1 + src/public/svgs/icons/Money.svg | 1 + src/public/svgs/icons/Motorcycle.svg | 1 + src/public/svgs/icons/Other.svg | 1 + src/public/svgs/icons/Phone.svg | 1 + src/public/svgs/icons/Stock.svg | 1 + src/router/user.js | 18 +++++-- src/views/assets.ejs | 79 ++++++++++++++++++++++------ src/views/dashboard.ejs | 1 + src/views/more.ejs | 1 + src/views/newPlan.ejs | 2 + src/views/partials/footer.ejs | 9 ---- src/views/partials/scriptLoader.ejs | 8 +++ src/views/planDetail.ejs | 1 + src/views/plans.ejs | 1 + src/views/profile.ejs | 1 + src/views/questionnaire.ejs | 2 + 29 files changed, 170 insertions(+), 35 deletions(-) create mode 100644 src/public/svgs/icons/Bike.svg create mode 100644 src/public/svgs/icons/Book.svg create mode 100644 src/public/svgs/icons/Camera.svg create mode 100644 src/public/svgs/icons/Car.svg create mode 100644 src/public/svgs/icons/Coins.svg create mode 100644 src/public/svgs/icons/Console.svg create mode 100644 src/public/svgs/icons/Desktop.svg create mode 100644 src/public/svgs/icons/Device.svg create mode 100644 src/public/svgs/icons/Electronic.svg create mode 100644 src/public/svgs/icons/Home.svg create mode 100644 src/public/svgs/icons/Laptop.svg create mode 100644 src/public/svgs/icons/Money.svg create mode 100644 src/public/svgs/icons/Motorcycle.svg create mode 100644 src/public/svgs/icons/Other.svg create mode 100644 src/public/svgs/icons/Phone.svg create mode 100644 src/public/svgs/icons/Stock.svg create mode 100644 src/views/partials/scriptLoader.ejs diff --git a/src/auth/middleware.js b/src/auth/middleware.js index 3c12f61..5c24807 100644 --- a/src/auth/middleware.js +++ b/src/auth/middleware.js @@ -1,7 +1,7 @@ const status = require("../util/statuses"); const session = require("express-session"); -// Get all user routes names +// Get all names of user routes let userRouter = require("../router/user")((req, res, next) => next(), null, null, null); userRouter.stack.shift(); const userRoutes = userRouter.stack.map((layer) => layer.route.path.split("/")[1]); @@ -12,8 +12,9 @@ const userRoutes = userRouter.stack.map((layer) => layer.route.path.split("/")[1 */ const createMiddleware = (users) => { return async (req, res, next) => { - // Redirect not found pages to 404 page - if (!userRoutes.includes(req.url.split("/")[1])) { + // Check if incoming request route exists in user routes + // redirect to 404 if not + if (!userRoutes.includes(req.url.split("/")[1].split("?")[0])) { return res.status(status.NotFound).redirect("/notFound"); } diff --git a/src/public/scripts/assetManager.js b/src/public/scripts/assetManager.js index 86ddd08..16bbc37 100644 --- a/src/public/scripts/assetManager.js +++ b/src/public/scripts/assetManager.js @@ -8,6 +8,12 @@ const assetForms = [ * resetAll asset creation forms */ function resetAll() { + document.getElementById("dropdown-icon-button").innerHTML = ` + Other Other + + `; document.getElementById("create-other-asset-form").reset(); document.getElementById("create-saving-asset-form").reset(); document.getElementById("create-stock-asset-form").reset(); @@ -40,6 +46,7 @@ function resetRadio() { const assetKeys = { other: [ "name", + "dropdown-icon-button", "value", "description", "purchaseDate", @@ -59,8 +66,17 @@ const assetKeys = { /** * lockAsset prevents edits to asset view modal * @param {string} assetId + * @param {string} icon to defualt to */ -function lockAsset(assetId) { +function lockAsset(assetId, icon) { + let dropdown = document.getElementById(`dropdown-icon-button-${assetId}`); + if (dropdown) dropdown.innerHTML = ` + ${icon} ${icon} + + `; + document.getElementById(`${assetId}-form`).reset(); const type = document.getElementById(`type-${assetId}`).value; @@ -74,14 +90,15 @@ function lockAsset(assetId) { document.getElementById(`save-${assetId}`).classList.add("cursor-not-allowed"); document.getElementById(`edit-${assetId}`).innerHTML = "Edit"; - document.getElementById(`edit-${assetId}`).onclick = () => { unlockAsset(assetId) }; + document.getElementById(`edit-${assetId}`).onclick = () => { unlockAsset(assetId, icon) }; } /** * unlockAsset allows edits to asset view modal * @param {string} assetId + * @param {string} icon to defualt to */ -function unlockAsset(assetId) { +function unlockAsset(assetId, icon) { const type = document.getElementById(`type-${assetId}`).value; assetKeys[type].forEach((key) => { @@ -92,8 +109,40 @@ function unlockAsset(assetId) { document.getElementById(`save-${assetId}`).classList.remove("cursor-not-allowed"); document.getElementById(`save-${assetId}`).classList.add("cursor-pointer"); - document.getElementById(`edit-${assetId}`).innerHTML = "Cancel changes"; - document.getElementById(`edit-${assetId}`).onclick = () => { lockAsset(assetId) }; + document.getElementById(`edit-${assetId}`).innerHTML = "Cancel"; + document.getElementById(`edit-${assetId}`).onclick = () => { lockAsset(assetId, icon) }; +} + +/** + * autoOpenCreate checks if popup param + * in url to auto open create popup + */ +function autoOpenCreate() { + const query = window.location.search; + const params = new URLSearchParams(query); + + if (params.has("popup")) { + document.getElementById('create-asset-modal').showModal(); + } +} + +/** + * selectIcon updated selected icon while creating + * or modifying assets. + * @param {button element} selectedIcon + * @param {string} assetId + */ +function selectIcon(selectedIcon, assetId="") { + document.getElementById(`dropdown-icon-button${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value; + document.getElementById(`icon${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value + + document.getElementById(`dropdown-icon-button${assetId != "" ? "-" : ""}${assetId}`).innerHTML = selectedIcon.innerHTML + + ` + + `; } resetRadio(); +autoOpenCreate(); diff --git a/src/public/svgs/icons/Bike.svg b/src/public/svgs/icons/Bike.svg new file mode 100644 index 0000000..c31ed29 --- /dev/null +++ b/src/public/svgs/icons/Bike.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Book.svg b/src/public/svgs/icons/Book.svg new file mode 100644 index 0000000..d1cd59b --- /dev/null +++ b/src/public/svgs/icons/Book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Camera.svg b/src/public/svgs/icons/Camera.svg new file mode 100644 index 0000000..4fda7d9 --- /dev/null +++ b/src/public/svgs/icons/Camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Car.svg b/src/public/svgs/icons/Car.svg new file mode 100644 index 0000000..63014de --- /dev/null +++ b/src/public/svgs/icons/Car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Coins.svg b/src/public/svgs/icons/Coins.svg new file mode 100644 index 0000000..17fee69 --- /dev/null +++ b/src/public/svgs/icons/Coins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Console.svg b/src/public/svgs/icons/Console.svg new file mode 100644 index 0000000..932964d --- /dev/null +++ b/src/public/svgs/icons/Console.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Desktop.svg b/src/public/svgs/icons/Desktop.svg new file mode 100644 index 0000000..2ad4fbd --- /dev/null +++ b/src/public/svgs/icons/Desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Device.svg b/src/public/svgs/icons/Device.svg new file mode 100644 index 0000000..ea62d89 --- /dev/null +++ b/src/public/svgs/icons/Device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Electronic.svg b/src/public/svgs/icons/Electronic.svg new file mode 100644 index 0000000..7f34f13 --- /dev/null +++ b/src/public/svgs/icons/Electronic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Home.svg b/src/public/svgs/icons/Home.svg new file mode 100644 index 0000000..230751a --- /dev/null +++ b/src/public/svgs/icons/Home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Laptop.svg b/src/public/svgs/icons/Laptop.svg new file mode 100644 index 0000000..eeb9595 --- /dev/null +++ b/src/public/svgs/icons/Laptop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Money.svg b/src/public/svgs/icons/Money.svg new file mode 100644 index 0000000..1476d84 --- /dev/null +++ b/src/public/svgs/icons/Money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Motorcycle.svg b/src/public/svgs/icons/Motorcycle.svg new file mode 100644 index 0000000..bd3776e --- /dev/null +++ b/src/public/svgs/icons/Motorcycle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Other.svg b/src/public/svgs/icons/Other.svg new file mode 100644 index 0000000..31bfd17 --- /dev/null +++ b/src/public/svgs/icons/Other.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Phone.svg b/src/public/svgs/icons/Phone.svg new file mode 100644 index 0000000..b34a759 --- /dev/null +++ b/src/public/svgs/icons/Phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/public/svgs/icons/Stock.svg b/src/public/svgs/icons/Stock.svg new file mode 100644 index 0000000..3caf5b6 --- /dev/null +++ b/src/public/svgs/icons/Stock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/user.js b/src/router/user.js index 084ea03..dc8b232 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -4,7 +4,9 @@ const status = require("../util/statuses"); const ObjectId = require('mongodb').ObjectId; const session = require("express-session"); const bcrypt = require("bcrypt"); +const path = require("path"); const joi = require("joi"); +const fs = require("fs"); const salt = 12; /** @@ -20,6 +22,7 @@ const getAssetSchema = (type) => { case "other": assetSchema = joi.object({ type: joi.string().valid("other", "stock", "saving").required(), + icon: joi.string().alphanum().required(), name: joi.string().alphanum().min(3).max(30).required(), value: joi.number().min(0).required(), purchaseDate: joi.date().required(), @@ -60,7 +63,11 @@ const getAssetSchema = (type) => { */ module.exports = (middleware, users, plans, assets) => { const router = require("express").Router(); - + + // Create list of icon filenames + let icons = fs.readdirSync(path.join(__dirname, "../public/svgs/icons")); + icons = icons.map((icon) => icon.split(".")[0]); + router.use(middleware); router.get('/home', async (req, res) => { @@ -83,7 +90,9 @@ module.exports = (middleware, users, plans, assets) => { user: req.session.user, errMessage: req.session.errMessage, assets: userAssets, - geoData: req.session.geoData }); + geoData: req.session.geoData, + icons: icons, + }); return res.status(status.Ok); }); @@ -388,6 +397,7 @@ module.exports = (middleware, users, plans, assets) => { newAsset.name = `${newAsset.ticker} Stock`; } newAsset.value = parseFloat(newAsset.value); + newAsset.icon = type == "stock" ? "Stock" : type == "saving" ? "Coins" : newAsset.icon; assets.insertOne(newAsset, (err, _) => { if (err) { @@ -433,7 +443,8 @@ module.exports = (middleware, users, plans, assets) => { update.name = `${update.ticker} Stock`; } update.value = parseFloat(update.value); - delete update.id + delete update.id; + delete update.userId; assets.updateOne( { "_id": new ObjectId(req.body.id) }, @@ -524,6 +535,7 @@ module.exports = (middleware, users, plans, assets) => { return res.status(status.InternalServerError).redirect("/profile"); }); }); + router.get("/exRates/:lat/:lon", async (req, res) => { if (!req.session.geoData.country) { const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${req.params.lat},${req.params.lon}&result_type=country&key=${process.env.GEOLOCATION_API}`); diff --git a/src/views/assets.ejs b/src/views/assets.ejs index cec34b0..0b6eae4 100644 --- a/src/views/assets.ejs +++ b/src/views/assets.ejs @@ -2,7 +2,6 @@ <%- include("./partials/header") %>
-
@@ -38,7 +37,31 @@
- + + + + +
+ + +
@@ -178,7 +201,9 @@ + +
+ <% } else { %> + + <% } %> + +

<%= String(asset.type).charAt(0).toUpperCase() + String(asset.type).slice(1); %> Asset

+
+ <% if (asset.type != "stock") { %> @@ -328,16 +381,12 @@ <%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/dashboard.ejs b/src/views/dashboard.ejs index 11979d3..519832d 100644 --- a/src/views/dashboard.ejs +++ b/src/views/dashboard.ejs @@ -91,4 +91,5 @@
<%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/more.ejs b/src/views/more.ejs index 59e3acf..934700e 100644 --- a/src/views/more.ejs +++ b/src/views/more.ejs @@ -6,5 +6,6 @@ <%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/newPlan.ejs b/src/views/newPlan.ejs index a05dd1a..5370290 100644 --- a/src/views/newPlan.ejs +++ b/src/views/newPlan.ejs @@ -37,4 +37,6 @@ +<%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index 89cc09b..9ee0b91 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -7,14 +7,5 @@ - - - - - diff --git a/src/views/partials/scriptLoader.ejs b/src/views/partials/scriptLoader.ejs new file mode 100644 index 0000000..afe997b --- /dev/null +++ b/src/views/partials/scriptLoader.ejs @@ -0,0 +1,8 @@ + + + + diff --git a/src/views/planDetail.ejs b/src/views/planDetail.ejs index 49c34f9..e75b46f 100644 --- a/src/views/planDetail.ejs +++ b/src/views/planDetail.ejs @@ -71,4 +71,5 @@ <%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> diff --git a/src/views/plans.ejs b/src/views/plans.ejs index 0b1b68b..5926a22 100644 --- a/src/views/plans.ejs +++ b/src/views/plans.ejs @@ -21,4 +21,5 @@ <%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> diff --git a/src/views/profile.ejs b/src/views/profile.ejs index b1a45f3..b8a8c3d 100644 --- a/src/views/profile.ejs +++ b/src/views/profile.ejs @@ -138,4 +138,5 @@ <%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file diff --git a/src/views/questionnaire.ejs b/src/views/questionnaire.ejs index 993a8c1..84420e6 100644 --- a/src/views/questionnaire.ejs +++ b/src/views/questionnaire.ejs @@ -68,4 +68,6 @@ +<%- include("./partials/navBar") %> +<%- include("./partials/scriptLoader") %> <%- include("./partials/footer") %> \ No newline at end of file From 540e191d27586f49ab8d74a9fb853e2e7213ee25 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Fri, 9 May 2025 09:49:14 -0700 Subject: [PATCH 25/46] refactor/remove debug statements --- src/auth/authentication.js | 2 +- src/public/scripts/geolocation.js | 2 -- src/router/user.js | 7 +------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/auth/authentication.js b/src/auth/authentication.js index a540751..727f4e7 100644 --- a/src/auth/authentication.js +++ b/src/auth/authentication.js @@ -84,7 +84,7 @@ module.exports = (users) => { financialData: false, }).then((results, err) => { if (err) { - console.error(err); + console.error("Error creating user on signup: ", err); res.session.errMessage = "Internal server error"; return res.status(status.InternalServerError).redirect("/signup"); } diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 51096a6..7fce450 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -37,8 +37,6 @@ function update(data) { dropdown.appendChild(listItem); }); - - // console.log(data); } function switchButton(clickedButton) { diff --git a/src/router/user.js b/src/router/user.js index dc8b232..8816990 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -98,9 +98,7 @@ module.exports = (middleware, users, plans, assets) => { router.get('/plans', async (req, res) => { try { - // console.log(new ObjectId(req.session.user._id)); const userPlansFromDB = await plans.find({userId: new ObjectId(req.session.user._id) }).toArray(); - // console.log(userPlansFromDB); // Use a for...of loop for proper async/await behavior in series for updates for (const plan of userPlansFromDB) { @@ -142,7 +140,6 @@ module.exports = (middleware, users, plans, assets) => { req.session.errMessage = "Plan not found or you do not have permission to view it."; return res.status(status.NotFound).redirect('/plans'); } - // console.log("Found plan:", plan); // The plan.progress should be up-to-date from the database as it was updated in the /plans route // or when assets/plans are modified. If an immediate recalculation for this specific view is absolutely needed, @@ -254,9 +251,7 @@ module.exports = (middleware, users, plans, assets) => { }); }); - router.post('/questionnaire', (req, res) => { - // console.log("Questionnaire POST body:", req.body); - + router.post('/questionnaire', (req, res) => { const questionnaireSchema = joi.object({ dob: joi.date().required(), education: joi.string().valid('primary', 'secondary', 'tertiary', 'postgraduate').required(), From 1587f363f7affb78d3a6802d9fd6e663b362d6ed Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Fri, 9 May 2025 09:50:38 -0700 Subject: [PATCH 26/46] fixing reset link session --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index d776381..91045f6 100644 --- a/app.js +++ b/app.js @@ -98,7 +98,7 @@ app.get('/reset/:token', async (req, res) => { return res.redirect('/forgotPassword'); } const error = req.session.error; - req.session.error = ''; + delete req.session.error; res.render('resetPass', { token: token, From 0a9c176274f0d65cc1cf40754c568f7e31cbca6c Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Fri, 9 May 2025 10:00:51 -0700 Subject: [PATCH 27/46] fix/default asset icon creation --- src/views/assets.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/assets.ejs b/src/views/assets.ejs index 0b6eae4..764e6bd 100644 --- a/src/views/assets.ejs +++ b/src/views/assets.ejs @@ -38,7 +38,7 @@ - +
From 1f742d41c3afee5978feba9e2f6379fca68127db Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Fri, 9 May 2025 10:23:34 -0700 Subject: [PATCH 28/46] fix/signup using existing email --- src/auth/authentication.js | 7 +++++++ src/views/signup.ejs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/auth/authentication.js b/src/auth/authentication.js index 727f4e7..d0a1985 100644 --- a/src/auth/authentication.js +++ b/src/auth/authentication.js @@ -69,6 +69,13 @@ module.exports = (users) => { return res.redirect("/signup"); } + let exists = await users.findOne({ email: req.body.email }).then((exists) => exists); + if (exists) { + req.session.errMessage = "Email already in use"; + res.status(status.BadRequest); + return res.redirect("/signup"); + } + if (req.body.password != req.body.repassword) { req.session.errMessage = "Passwords must match"; res.status(status.BadRequest); diff --git a/src/views/signup.ejs b/src/views/signup.ejs index 840e839..ac2d8ac 100644 --- a/src/views/signup.ejs +++ b/src/views/signup.ejs @@ -15,7 +15,7 @@
-
From 99b36d33705cc0ec84560f57afbe1b554bbe8c6b Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Fri, 9 May 2025 10:26:18 -0700 Subject: [PATCH 29/46] fixing conflict with dashboard.ejs --- src/views/dashboard.ejs | 199 ++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 99 deletions(-) diff --git a/src/views/dashboard.ejs b/src/views/dashboard.ejs index d36b979..86539d0 100644 --- a/src/views/dashboard.ejs +++ b/src/views/dashboard.ejs @@ -1,115 +1,116 @@ <%- include("./partials/fileHeader") %> <%- include("./partials/header") %> -
- - - -
-
- -
- - - -
-

- Retirement goal

-

- $53k

+
+ +
-
- -
- - - -
-

- Retirement goal

-

- $53k

+ + + + + +
+
+ +
+ + + +
+

+ Retirement goal

+

+ $53k

+
+
+

+ Make this percent bar or graph or + something +  than lastweek +

+
-
-

- Make this percent bar or graph or something -  than lastweek -

-
-
-
-
-

- Retirement goal

-

- $53k

-
-
-

- Make this percent bar or graph or something -  than last - week -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - -
+
+

+ Retirement goal

+

+ $53k

+
+
+

+ Make this percent bar or graph or + something +  than last + week +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
-
-

- Retirement goal

-

- $53k

-
-
-

- Make this percent bar or graph or something -  than last - week -

+
+
+

+ Retirement goal

+

+ $53k

+
+
+

+ Make this percent bar or + graph or something +  than last + week +

+
-
-
-
+
+
-<%- include("./partials/navBar") %> -<%- include("./partials/scriptLoader") %> -<%- include("./partials/footer") %> + <%- include("./partials/navBar") %> + <%- include("./partials/scriptLoader") %> + <%- include("./partials/footer") %> From 69fd776c0fb456ed053c2b3df6e12bd766dfecf4 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Fri, 9 May 2025 10:29:20 -0700 Subject: [PATCH 30/46] fix/enhanced user input validation --- src/auth/authentication.js | 8 ++++---- src/router/user.js | 6 +++--- src/views/profile.ejs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/auth/authentication.js b/src/auth/authentication.js index d0a1985..d65e9c6 100644 --- a/src/auth/authentication.js +++ b/src/auth/authentication.js @@ -20,7 +20,7 @@ module.exports = (users) => { const credentialSchema = joi.object({ email: joi.string().email().required(), - password: joi.string().max(20).required(), + password: joi.string().alphanum().max(20).required(), }); const valid = credentialSchema.validate(req.body); @@ -56,9 +56,9 @@ module.exports = (users) => { router.post("/signup", async (req, res) => { const userSchema = joi.object({ email: joi.string().email().required(), - name: joi.string().alphanum().max(20).required(), - password: joi.string().max(20).min(8).required(), - repassword: joi.string().max(20).min(8).required(), + name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20).required(), + password: joi.string().alphanum().max(20).min(8).required(), + repassword: joi.string().alphanum().max(20).min(8).required(), }); const valid = userSchema.validate(req.body); diff --git a/src/router/user.js b/src/router/user.js index c8f67f6..9286457 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -311,9 +311,9 @@ module.exports = (middleware, users, plans, assets) => { router.post("/updateAccount", async (req, res) => { const accountSchema = joi.object({ email: joi.string().email(), - name: joi.string().alphanum().max(20), - password: joi.string().max(20).min(8), - repassword: joi.string().max(20).min(8), + name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20), + password: joi.string().alphanum().max(20).min(8), + repassword: joi.string().alphanum().max(20).min(8), }); const valid = accountSchema.validate(req.body); diff --git a/src/views/profile.ejs b/src/views/profile.ejs index b8a8c3d..8d6e3b5 100644 --- a/src/views/profile.ejs +++ b/src/views/profile.ejs @@ -56,7 +56,7 @@ - + From 22850ab2a2adab64fcd726f4d24825526d205294 Mon Sep 17 00:00:00 2001 From: Joaquin Date: Fri, 9 May 2025 10:55:02 -0700 Subject: [PATCH 31/46] fix/removed redundant code and fixed UI challenge 1 problem --- app.js | 10 ---------- src/views/partials/header.ejs | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app.js b/app.js index ff449a4..bc48457 100644 --- a/app.js +++ b/app.js @@ -154,16 +154,6 @@ app.post('/resetLink', async (req, res) => { res.redirect('/login'); }); -app.post('/api/location', async (req, res) => { - const { latitude, longitude } = req.body; - - const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&result_type=country&key=${process.env.geolocation_api}`); - - const data = await response.json(); - - res.json(data); -}); - // 404 handler - keep the actual notFound route please // REALLY DONT DELETE THIS app.get('/notFound', (req, res) => { diff --git a/src/views/partials/header.ejs b/src/views/partials/header.ejs index f48549f..90410ca 100644 --- a/src/views/partials/header.ejs +++ b/src/views/partials/header.ejs @@ -22,7 +22,7 @@

-

+

-

+

$1.00 = $<%= (1 * geoData.toCurrencyRates["USD"]).toFixed(2) %>

From 07824e6355032c29a60d922ba65fc62fd644d4a3 Mon Sep 17 00:00:00 2001 From: knighthawk4227 Date: Fri, 9 May 2025 11:02:31 -0700 Subject: [PATCH 32/46] merge cnflicts with dev --- app.js | 4 ++-- src/auth/forgotPass.js | 2 +- src/views/forgotPass.ejs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index ff449a4..37ba38d 100644 --- a/app.js +++ b/app.js @@ -77,8 +77,8 @@ app.get('/aboutUs', (req, res) => { app.get('/forgotPassword', (req, res) => { const error = req.session.error; const reset = req.session.reset; - req.session.reset = ''; - req.session.error = ''; + delete req.session.reset; + delete req.session.error; res.render('forgotPass', { error: error, reset: reset }); return res.status(status.Ok); }); diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js index 62260bf..a369dfb 100644 --- a/src/auth/forgotPass.js +++ b/src/auth/forgotPass.js @@ -8,7 +8,7 @@ const transporter = nodeMail.createTransport({ service: 'gmail', auth: { user: process.env.EMAIL_USER, - pass: process.env.PASS, + pass: process.env.EMAIL_PASS, } }); // users info diff --git a/src/views/forgotPass.ejs b/src/views/forgotPass.ejs index 811c32d..d2d749f 100644 --- a/src/views/forgotPass.ejs +++ b/src/views/forgotPass.ejs @@ -6,7 +6,7 @@

Reset Password


- <% if (reset==='' ) { %> + <% if (!reset ) { %>
<%= error%>
- <% if (reset==='' ) { %> + <% if (!reset) { %>
+ + diff --git a/src/views/partials/scriptLoader.ejs b/src/views/partials/scriptLoader.ejs index afe997b..2ed92a3 100644 --- a/src/views/partials/scriptLoader.ejs +++ b/src/views/partials/scriptLoader.ejs @@ -1,5 +1,4 @@ - - + + diff --git a/src/views/planDetail.ejs b/src/views/planDetail.ejs index e75b46f..33d81ea 100644 --- a/src/views/planDetail.ejs +++ b/src/views/planDetail.ejs @@ -62,7 +62,7 @@

- USE YOUR HEAD MOCK + <%= suggestions %>

From 37712881237aded2aa6e1caaf0ccb6d4dad6c5cf Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Wed, 14 May 2025 09:34:32 -0700 Subject: [PATCH 40/46] fix/login+signup+logout session bugs --- app.js | 2 -- src/auth/authentication.js | 44 ++++++++++++++++++++++++++++++-------- src/auth/forgotPass.js | 5 +++-- src/auth/middleware.js | 42 ++++++++++++++++++++++++++++++------ src/router/user.js | 43 ++++++++++++++++++------------------- 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/app.js b/app.js index 56eb793..741673f 100644 --- a/app.js +++ b/app.js @@ -83,7 +83,6 @@ app.get('/forgotPassword', (req, res) => { return res.status(status.Ok); }); - // Reset with token given to user via email app.get('/reset/:token', async (req, res) => { const token = req.params.token; @@ -106,7 +105,6 @@ app.get('/reset/:token', async (req, res) => { }); }); - // 404 handler - keep the actual notFound route please // REALLY DONT DELETE THIS app.get('/notFound', (req, res) => { diff --git a/src/auth/authentication.js b/src/auth/authentication.js index d65e9c6..d32f0a8 100644 --- a/src/auth/authentication.js +++ b/src/auth/authentication.js @@ -12,10 +12,18 @@ module.exports = (users) => { const router = require("express").Router(); router.get("/logout", (req, res) => { - req.session.destroy(); - return res.redirect('/login'); + req.session.destroy((err) => { + if (err) { + console.error("Failed to destroy session: ", err); + res.status(status.InternalServerError); + return res.redirect("back"); + } + + res.status(status.Ok); + return res.redirect('/login'); + }); }); - + router.post("/login", async (req, res) => { const credentialSchema = joi.object({ @@ -46,10 +54,19 @@ module.exports = (users) => { req.session.authenticated = true; req.session.userId = user._id; - req.session.email = req.body.email; req.session.errMessage = ""; - res.redirect("/home"); - return res.status(status.Ok); + + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + req.session.errMessage = "Failed to save session. Please try again."; + res.status(status.InternalServerError); + return res.redirect("/login"); + } + + res.status(status.Ok); + return res.redirect("/home"); + }); }); }); @@ -97,11 +114,20 @@ module.exports = (users) => { } req.session.authenticated = true; - req.session.email = req.body.email; req.session.userId = results.insertedId; - req.session.errMessage = ""; - return res.status(status.Ok).redirect("/home"); + + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + req.session.errMessage = "Please login"; + res.status(status.Ok); + return res.redirect("/login"); + } + + res.status(status.Ok); + return res.redirect("/home"); + }); }); }); diff --git a/src/auth/forgotPass.js b/src/auth/forgotPass.js index 698a500..e0022c3 100644 --- a/src/auth/forgotPass.js +++ b/src/auth/forgotPass.js @@ -4,6 +4,7 @@ const joi = require('joi'); const nodeMail = require('nodemailer'); const bcrypt = require('bcrypt'); require('dotenv').config(); + const PORT = process.env.PORT; const transporter = nodeMail.createTransport({ @@ -13,7 +14,7 @@ const transporter = nodeMail.createTransport({ pass: process.env.EMAIL_PASS, } }); -// users info + module.exports = (users) => { const router = express.Router(); @@ -73,7 +74,7 @@ module.exports = (users) => { }); const valid = passwordSchema.validate({ password, confirmPassword }); if (valid.error) { - console.log("houston we have a problem"); + console.log("houston we have a problem"); // nice req.session.error = 'Invalid input'; res.status(status.BadRequest); return res.redirect(`/reset/${token}`); diff --git a/src/auth/middleware.js b/src/auth/middleware.js index 5c24807..b641877 100644 --- a/src/auth/middleware.js +++ b/src/auth/middleware.js @@ -1,5 +1,6 @@ const status = require("../util/statuses"); const session = require("express-session"); +const ObjectId = require("mongodb").ObjectId; // Get all names of user routes let userRouter = require("../router/user")((req, res, next) => next(), null, null, null); @@ -18,26 +19,53 @@ const createMiddleware = (users) => { return res.status(status.NotFound).redirect("/notFound"); } - if (!req.session.authenticated || !req.session.email) { + if (!req.session.authenticated || !req.session.userId) { req.session.errMessage = "Please login to view that resource"; res.redirect("/login"); return res.status(status.Unauthorized); } if (!req.session.user) { - let user = await users.findOne({ "email": req.session.email }).then((user) => user); + let user = await users.findOne({ _id: new ObjectId(req.session.userId) }).then((user) => user); if (!user) { - req.session.errMessage = "User not found"; - res.redirect("/login"); - return res.status(status.Unauthorized); + return req.session.destroy((err) => { + if (err) { + console.error("Failed to destroy session: ", err); + req.session.errMessage = "Failed to logout. Please try again"; + res.status(status.InternalServerError); + return res.redirect("/home"); + } + + req.session.errMessage = "User not found"; + res.status(status.NotFound); + res.redirect("/login"); + }); } - + req.session.user = user; + return req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + + return req.session.destroy((err) => { + req.session.errMessage = "An error occured, please login again."; + + if (err) { + console.error("Failed to destroy session: ", err); + } + + res.status(status.InternalServerError); + res.redirect("/login"); + }); + } + + next(); + }); } next(); }; -} +}; module.exports = createMiddleware; diff --git a/src/router/user.js b/src/router/user.js index 16c51f5..da956c2 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -86,7 +86,7 @@ module.exports = (middleware, users, plans, assets) => { }); router.get('/assets', async (req, res) => { - let userAssets = await assets.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); res.render('assets', { user: req.session.user, errMessage: req.session.errMessage, @@ -99,16 +99,16 @@ module.exports = (middleware, users, plans, assets) => { router.get('/plans', async (req, res) => { try { - const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray(); // Use a for...of loop for proper async/await behavior in series for updates for (const plan of userPlansFromDB) { - const percentage = await calculatePlanProgress(plan, assets, req.session.user._id); + const percentage = await calculatePlanProgress(plan, assets, req.session.userId); await updatePlanProgressInDB(plan._id, percentage, plans); // Pass the 'plans' collection } // Re-fetch plans to get updated progress for rendering - const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); + const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray(); res.render('plans', { user: req.session.user, @@ -126,18 +126,17 @@ module.exports = (middleware, users, plans, assets) => { try { const planId = req.params.id; - let userAssets = await assets.find({ userId: new ObjectId(req.session.user._id) }).toArray(); - + let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); if (!ObjectId.isValid(planId)) { req.session.errMessage = "Invalid plan ID format."; return res.status(status.BadRequest).redirect('/plans'); } - const plan = await plans.findOne({ userId: new ObjectId(req.session.user._id), _id: new ObjectId(planId) }); + const plan = await plans.findOne({ userId: new ObjectId(req.session.userId), _id: new ObjectId(planId) }); if (!plan) { - console.log(`Plan not found with ID: ${planId} for user: ${req.session.user.email}`); + console.log(`Plan not found with ID: ${planId} for user: ${req.session.userId}`); req.session.errMessage = "Plan not found or you do not have permission to view it."; return res.status(status.NotFound).redirect('/plans'); } @@ -197,7 +196,7 @@ module.exports = (middleware, users, plans, assets) => { return; } const newPlan = { - userId: new ObjectId(req.session.user._id), + userId: new ObjectId(req.session.userId), name: value.name, retirementAge: value.retirementAge, retirementExpenses: value.retirementExpenses, @@ -207,7 +206,7 @@ module.exports = (middleware, users, plans, assets) => { }; try { - await plans.insertOne({ userId: new ObjectId(req.session.user._id), ...newPlan }); + await plans.insertOne({ userId: new ObjectId(req.session.userId), ...newPlan }); req.session.errMessage = ""; res.redirect('/plans'); } @@ -280,7 +279,7 @@ module.exports = (middleware, users, plans, assets) => { } users.updateOne( - { _id: new ObjectId(req.session.user._id) }, + { _id: new ObjectId(req.session.userId) }, { $set: { financialData: true, @@ -295,13 +294,13 @@ module.exports = (middleware, users, plans, assets) => { } ).then((result) => { if (result.matchedCount === 0) { - console.log(`User not found during questionnaire update: ${req.session.user.email}`); + console.log(`User not found during questionnaire update: ${req.session.userId}`); req.session.errMessage = "User session invalid. Please log in again."; res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { - console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.user.email}`); + console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.userId}`); } req.session.user.financialData = true; @@ -333,7 +332,7 @@ module.exports = (middleware, users, plans, assets) => { if (valid.err) { req.session.errMessage = "Invalid input", - res.status(status.BadRequest); + res.status(status.BadRequest); return res.redirect("/profile"); } @@ -351,17 +350,17 @@ module.exports = (middleware, users, plans, assets) => { } users.updateOne( - { email: req.session.email }, + { _id: new ObjectId(req.session.userId) }, { $set: update } ).then((result) => { if (result.matchedCount === 0) { - console.log(`User not found during account update: ${req.session.email}`); + console.log(`User not found during account update: ${req.session.userId}`); req.session.errMessage = "User session invalid. Please log in again."; res.status(status.NotFound).redirect("/login"); return; } if (result.modifiedCount === 0 && result.matchedCount === 1) { - console.log(`User account data unchanged (already up-to-date): ${req.session.email}`); + console.log(`User account data unchanged (already up-to-date): ${req.session.userId}`); } req.session.errMessage = ""; @@ -393,7 +392,7 @@ module.exports = (middleware, users, plans, assets) => { } let newAsset = { - userId: new ObjectId(req.session.user._id), + userId: new ObjectId(req.session.userId), ...req.body, updatedAt: new Date(), }; @@ -437,7 +436,7 @@ module.exports = (middleware, users, plans, assets) => { return res.redirect("/assets"); } - if (req.body.userId != req.session.user._id) { + if (req.body.userId != req.session.userId) { req.session.errMessage = "Cannot change asset owner", res.status(status.BadRequest); return res.redirect("/assets"); @@ -507,20 +506,20 @@ module.exports = (middleware, users, plans, assets) => { router.post("/deleteUser", (req, res) => { // not as critical if results aren't as expected only if crashing - assets.deleteMany({ userId: new ObjectId(req.session.user._id) }).catch((err) => { + assets.deleteMany({ userId: new ObjectId(req.session.userId) }).catch((err) => { console.error("Error deleting user assets: ", err); req.session.errMessage = "An error occured while deleting your account. Please try again."; return res.status(status.InternalServerError).redirect("/profile"); }); - plans.deleteMany({ userId: new ObjectId(req.session.user._id) }).catch((err) => { + plans.deleteMany({ userId: new ObjectId(req.session.userId) }).catch((err) => { console.error("Error deleting user assets: ", err); req.session.errMessage = "An error occured while deleting your account. Please try again."; return res.status(status.InternalServerError).redirect("/profile"); }); users.deleteOne( - { _id: new ObjectId(req.session.user._id) }, + { _id: new ObjectId(req.session.userId) }, ).then((result) => { if (result.deletedCount === 0) { console.error(`User not found: ${req.body.id}`); From 5e2ac9421258a21e1bd43f106c8b2c7a5a8d5c6e Mon Sep 17 00:00:00 2001 From: Joaquin Date: Wed, 14 May 2025 09:37:09 -0700 Subject: [PATCH 41/46] fully implemented error handling for challenge 1 --- src/public/scripts/geolocation.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 8dad938..7a96166 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -69,10 +69,18 @@ async function getLatestExchange(position) { let lon = position.coords.longitude; const res = await fetch(`/exRates/${lat}/${lon}`); const data = await res.json(); - + update(data); } function error(err) { - console.error("Geolocation error: ", err); + const data = { + data: { + message: "error" + } + } + + console.log(data); + + update(data); } \ No newline at end of file From c12bbe8336874635f6215dea9c28ee4c5689186a Mon Sep 17 00:00:00 2001 From: Joaquin Date: Wed, 14 May 2025 09:40:06 -0700 Subject: [PATCH 42/46] removed debugging code --- src/public/scripts/geolocation.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/public/scripts/geolocation.js b/src/public/scripts/geolocation.js index 7a96166..94c9c66 100644 --- a/src/public/scripts/geolocation.js +++ b/src/public/scripts/geolocation.js @@ -80,7 +80,5 @@ function error(err) { } } - console.log(data); - update(data); } \ No newline at end of file From 50e2d95fdad062ee1b2537db0f17a556d52178e0 Mon Sep 17 00:00:00 2001 From: nicoagostini Date: Wed, 14 May 2025 11:21:10 -0700 Subject: [PATCH 43/46] feature/AI feature updated to accept user input --- src/router/user.js | 22 +++++---------- src/util/suggestions.js | 6 ++-- src/views/partials/footer.ejs | 53 ++++++++++++++++++++++++----------- 3 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/router/user.js b/src/router/user.js index 16c51f5..3bddc91 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -101,18 +101,16 @@ module.exports = (middleware, users, plans, assets) => { try { const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); - // Use a for...of loop for proper async/await behavior in series for updates for (const plan of userPlansFromDB) { const percentage = await calculatePlanProgress(plan, assets, req.session.user._id); - await updatePlanProgressInDB(plan._id, percentage, plans); // Pass the 'plans' collection + await updatePlanProgressInDB(plan._id, percentage, plans); } - // Re-fetch plans to get updated progress for rendering const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray(); res.render('plans', { user: req.session.user, - plans: updatedUserPlans, // Send the most up-to-date plans + plans: updatedUserPlans, geoData: req.session.geoData }); } catch (err) { @@ -142,16 +140,9 @@ module.exports = (middleware, users, plans, assets) => { return res.status(status.NotFound).redirect('/plans'); } - // The plan.progress should be up-to-date from the database as it was updated in the /plans route - // or when assets/plans are modified. If an immediate recalculation for this specific view is absolutely needed, - // (e.g., if assets were modified without an immediate plan progress update elsewhere), - // you could do it here: - // const currentProgress = await calculatePlanProgress(plan, assets, req.session.user._id); - // plan.progress = currentProgress; // This would only update the 'plan' object for this render, not in DB - res.render('planDetail', { user: req.session.user, - plan: plan, // This plan object will have the progress from the database + plan: plan, geoData: req.session.geoData, assets: userAssets, suggestions: await suggestions.generateSuggestions(), @@ -218,9 +209,10 @@ module.exports = (middleware, users, plans, assets) => { } }); - router.get('/pun', async (req, res) => { - const pun = await suggestions.generatePun(); - return res.status(status.Ok).json({ pun }); + router.post('/fact', async (req, res) => { + const factInput = req.body.fact; + const fact = await suggestions.generateFact(factInput); + return res.status(status.Ok).json({ fact }); }); router.get('/more', (req, res) => { diff --git a/src/util/suggestions.js b/src/util/suggestions.js index 2961014..2bfb957 100644 --- a/src/util/suggestions.js +++ b/src/util/suggestions.js @@ -2,10 +2,10 @@ const { GoogleGenAI } = require("@google/genai"); const ai = new GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }); -async function generatePun() { +async function generateFact(factInput) { const response = await ai.models.generateContent({ model: "gemini-2.0-flash", - contents: "Return a funny pun about investments, answer the pun only, no additional text.", + contents: `Generate a concise investment fact about "${factInput}". If "${factInput}" is not directly investment-related, provide a general, useful investment fact instead. Deliver only the fact itself, with no extra text or explanation.`, }); return response.text; } @@ -19,6 +19,6 @@ async function generateSuggestions() { } module.exports = { - generatePun, + generateFact, generateSuggestions }; diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index 2c04272..0f3bc65 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -1,7 +1,14 @@ -
- +
+
+
@@ -13,21 +20,35 @@ - - + \ No newline at end of file From 73d61d76de68d0bddac2496100212cfa6be5e8ae Mon Sep 17 00:00:00 2001 From: nicoagostini Date: Wed, 14 May 2025 11:22:28 -0700 Subject: [PATCH 44/46] feature/AI feature updated to accept user input --- src/views/partials/footer.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index 0f3bc65..f6d1596 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -2,7 +2,7 @@ From 9ae23ce25e7bae2be5e4ba8b33e75ebc91ad1596 Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Wed, 14 May 2025 12:12:48 -0700 Subject: [PATCH 45/46] fix account details not updating + update personal info --- src/auth/middleware.js | 5 +- src/public/scripts/profile.js | 16 +++- src/router/user.js | 131 +++++++++++++++++++++++++++++--- src/views/profile.ejs | 139 ++++++++++++++++++++++------------ 4 files changed, 226 insertions(+), 65 deletions(-) diff --git a/src/auth/middleware.js b/src/auth/middleware.js index b641877..05f5d5e 100644 --- a/src/auth/middleware.js +++ b/src/auth/middleware.js @@ -24,10 +24,9 @@ const createMiddleware = (users) => { res.redirect("/login"); return res.status(status.Unauthorized); } - + if (!req.session.user) { let user = await users.findOne({ _id: new ObjectId(req.session.userId) }).then((user) => user); - if (!user) { return req.session.destroy((err) => { if (err) { @@ -49,7 +48,7 @@ const createMiddleware = (users) => { console.error("Failed to save session: ", err); return req.session.destroy((err) => { - req.session.errMessage = "An error occured, please login again."; + req.session.errMessage = "Failed to save session, please login again."; if (err) { console.error("Failed to destroy session: ", err); diff --git a/src/public/scripts/profile.js b/src/public/scripts/profile.js index d803378..79f4db0 100644 --- a/src/public/scripts/profile.js +++ b/src/public/scripts/profile.js @@ -11,6 +11,7 @@ function lockAccount() { document.getElementById("password").disabled = true; document.getElementById("repassword").disabled = true; document.getElementById("save-account").classList.add("cursor-not-allowed"); + document.getElementById("save-account").classList.remove("cursor-pointer"); document.getElementById("edit-account").innerHTML = "Edit"; document.getElementById("edit-account").onclick = unlockAccount; @@ -30,7 +31,12 @@ function lockPersonal() { document.getElementById("ms-married").disabled = true; document.getElementById("ms-divorced").disabled = true; document.getElementById("ms-widowed").disabled = true; + document.getElementById("income").disabled = true; + document.getElementById("expenses").disabled = true; + document.getElementById("assets").disabled = true; + document.getElementById("liabilities").disabled = true; document.getElementById("save-personal").classList.add("cursor-not-allowed"); + document.getElementById("save-personal").classList.remove("cursor-pointer"); document.getElementById("edit-personal").innerHTML = "Edit"; document.getElementById("edit-personal").onclick = unlockPersonal; @@ -42,11 +48,12 @@ function lockPersonal() { */ function unlockAccount() { document.getElementById("save-account").disabled = false; - // document.getElementById("email").disabled = false; + document.getElementById("email").disabled = false; document.getElementById("name").disabled = false; document.getElementById("password").disabled = false; document.getElementById("repassword").disabled = false; document.getElementById("save-account").classList.remove("cursor-not-allowed"); + document.getElementById("save-account").classList.add("cursor-pointer"); document.getElementById("edit-account").innerHTML = "Cancel changes"; document.getElementById("edit-account").onclick = lockAccount; @@ -64,7 +71,12 @@ function unlockPersonal() { document.getElementById("ms-married").disabled = false; document.getElementById("ms-divorced").disabled = false; document.getElementById("ms-widowed").disabled = false; + document.getElementById("income").disabled = false; + document.getElementById("expenses").disabled = false; + document.getElementById("assets").disabled = false; + document.getElementById("liabilities").disabled = false; document.getElementById("save-personal").classList.remove("cursor-not-allowed"); + document.getElementById("save-personal").classList.add("cursor-pointer"); document.getElementById("edit-personal").innerHTML = "Cancel changes"; document.getElementById("edit-personal").onclick = lockPersonal; @@ -72,4 +84,4 @@ function unlockPersonal() { // On page load, ensure forms are locked and reset lockAccount(); -// lockPersonal(); +lockPersonal(); diff --git a/src/router/user.js b/src/router/user.js index da956c2..41c91ff 100644 --- a/src/router/user.js +++ b/src/router/user.js @@ -271,10 +271,12 @@ module.exports = (middleware, users, plans, assets) => { const validationOptions = { convert: true, abortEarly: false }; const { error, value } = questionnaireSchema.validate(req.body, validationOptions); + let referrer = req.get('Referrer') || "/home"; if (error) { console.error("Questionnaire validation error:", error.details); req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); - res.status(status.BadRequest).redirect("/questionnaire"); + let redirect = referrer.includes("?profile") ? "/questionnaire?profile" : "/questionnaire"; + res.status(status.BadRequest).redirect(redirect); return; } @@ -303,20 +305,34 @@ module.exports = (middleware, users, plans, assets) => { console.log(`User questionnaire data unchanged (already up-to-date): ${req.session.userId}`); } - req.session.user.financialData = true; req.session.errMessage = ""; - - req.session.save(err => { + req.session.user = null; // set user to null so middleware updates user + req.session.save((err) => { if (err) { - res.status(status.InternalServerError).redirect("/plans"); + console.error("Failed to save session: ", err); + + return req.session.destroy((err) => { + req.session.errMessage = "Failed to save session, please login again."; + + if (err) { + console.error("Failed to destroy session: ", err); + } + + res.status(status.InternalServerError); + return res.redirect("/login"); + }); } - res.status(status.Ok).redirect("/plans"); + + let redirect = referrer.includes("?profile") ? "/profile" : + referrer != "/home" ? "/plans" : referrer; + return res.status(status.Ok).redirect(redirect); }); }).catch(err => { console.error("Error updating questionnaire in database:", err); req.session.errMessage = "An error occurred while saving your information. Please try again."; - res.status(status.InternalServerError).redirect("/questionnaire"); + let redirect = referrer.includes("?profile") ? "/questionnaire?profile" : "/questionnaire"; + res.status(status.InternalServerError).redirect(redirect); }); }); @@ -337,6 +353,7 @@ module.exports = (middleware, users, plans, assets) => { } let update = { + email: req.body.email, name: req.body.name, }; @@ -364,7 +381,25 @@ module.exports = (middleware, users, plans, assets) => { } req.session.errMessage = ""; - return res.status(status.Ok).redirect("/profile"); + req.session.user = null; // set user to null so middleware updates user + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + + return req.session.destroy((err) => { + req.session.errMessage = "Failed to save session, please login again."; + + if (err) { + console.error("Failed to destroy session: ", err); + } + + res.status(status.InternalServerError); + return res.redirect("/login"); + }); + } + + return res.status(status.Ok).redirect("/profile"); + }); }).catch(err => { console.error("Error updating account in database:", err); req.session.errMessage = "An error occurred while saving your information. Please try again."; @@ -372,7 +407,81 @@ module.exports = (middleware, users, plans, assets) => { }); }); - router.post("/createAsset", async (req, res) => { + router.post("/updatePersonal", (req, res) => { + const questionnaireSchema = joi.object({ + dob: joi.date().required(), + education: joi.string().valid('primary', 'secondary', 'tertiary', 'postgraduate').required(), + maritalStatus: joi.string().valid('single', 'married', 'divorced', 'widowed').required(), + income: joi.number().min(0).required(), + expenses: joi.number().min(0).required(), + assets: joi.number().min(0).required(), + liabilities: joi.number().min(0).required(), + }); + + const validationOptions = { convert: true, abortEarly: false }; + const { error, value } = questionnaireSchema.validate(req.body, validationOptions); + + if (error) { + console.error("Personal info validation error:", error.details); + req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', '); + res.status(status.BadRequest).redirect("/profile"); + return; + } + + users.updateOne( + { _id: new ObjectId(req.session.userId) }, + { + $set: { + financialData: true, + dob: value.dob, + education: value.education, + maritalStatus: value.maritalStatus, + income: value.income, + expenses: value.expenses, + assets: value.assets, + liabilities: value.liabilities, + } + } + ).then((result) => { + if (result.matchedCount === 0) { + console.log(`User not found during personal info update: ${req.session.userId}`); + req.session.errMessage = "User session invalid. Please log in again."; + res.status(status.NotFound).redirect("/login"); + return; + } + if (result.modifiedCount === 0 && result.matchedCount === 1) { + console.log(`User personal info unchanged (already up-to-date): ${req.session.userId}`); + } + + req.session.errMessage = ""; + req.session.user = null; // set user to null so middleware updates user + req.session.save((err) => { + if (err) { + console.error("Failed to save session: ", err); + + return req.session.destroy((err) => { + req.session.errMessage = "Failed to save session, please login again."; + + if (err) { + console.error("Failed to destroy session: ", err); + } + + res.status(status.InternalServerError); + return res.redirect("/login"); + }); + } + + return res.status(status.Ok).redirect("/profile"); + }); + + }).catch(err => { + console.error("Error updating personal info in database:", err); + req.session.errMessage = "An error occurred while saving your information. Please try again."; + res.status(status.InternalServerError).redirect("/profile"); + }); + }); + + router.post("/createAsset", (req, res) => { // Create asset, each asset has different data structure based on type const type = req.body.type; const assetSchema = getAssetSchema(type); @@ -387,7 +496,7 @@ module.exports = (middleware, users, plans, assets) => { if (valid.err) { req.session.errMessage = "Invalid input", - res.status(status.BadRequest); + res.status(status.BadRequest); return res.redirect("/assets"); } @@ -418,7 +527,7 @@ module.exports = (middleware, users, plans, assets) => { return res.status(status.Ok).redirect("/assets"); }); - router.post("/updateAsset", async (req, res) => { + router.post("/updateAsset", (req, res) => { const type = req.body.type; const assetSchema = getAssetSchema(type); diff --git a/src/views/profile.ejs b/src/views/profile.ejs index 8d6e3b5..3ce06c9 100644 --- a/src/views/profile.ejs +++ b/src/views/profile.ejs @@ -73,6 +73,96 @@ + +
+
+

Personal information

+ <% if (user.financialData) { %> + + <% } %> +
+ + <% if (user.financialData) { %> +
+
+ + + <% let month = typeof(user.dob) == "object" ? user.dob.getMonth() : user.dob.split('-')[1] - 1; %> + <% let day = typeof(user.dob) == "object" ? user.dob.getDate() + 1 : user.dob.split('-')[2].split('T')[0];%> + <% let d = new Date(parseInt(year), parseInt(month), parseInt(day)); %> + value="<%= d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2); %>" + > +
+
+ + +
+
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ <% } else { %> + + <% } %> + +
+ +
- -
From 4a02b75a4dd2065d9174bbdfee98275c044f902f Mon Sep 17 00:00:00 2001 From: nicoagostini Date: Wed, 14 May 2025 12:53:58 -0700 Subject: [PATCH 46/46] fix/Footer scripts moved to a dedicated file --- src/public/scripts/footer.js | 27 +++++++++++++++++++++++++++ src/views/partials/footer.ejs | 30 +----------------------------- 2 files changed, 28 insertions(+), 29 deletions(-) create mode 100644 src/public/scripts/footer.js diff --git a/src/public/scripts/footer.js b/src/public/scripts/footer.js new file mode 100644 index 0000000..7bdc130 --- /dev/null +++ b/src/public/scripts/footer.js @@ -0,0 +1,27 @@ +const factButton = document.getElementById("factButton"); +const factMenu = document.getElementById("factMenu"); +const factInput = document.getElementById("factInput"); +const factSubmitButton = document.getElementById("factSubmitButton"); + +factButton.addEventListener("click", () => { + factMenu.classList.toggle("hidden"); +}); +factSubmitButton.addEventListener("click", () => { + fetch("/fact", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + fact: factInput.value, + }), + }) + .then(response => response.json()) + .then(data => { + Swal.fire('Here is a fact!', data.fact, 'success'); + }) + .catch(error => { + console.error("Error fetching fact:", error); + Swal.fire('Error fetching fact', 'Please try again later', 'error'); + }); +}); \ No newline at end of file diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs index f6d1596..9f828cf 100644 --- a/src/views/partials/footer.ejs +++ b/src/views/partials/footer.ejs @@ -21,34 +21,6 @@ - + \ No newline at end of file