// ===================================================== // INVESTMENT ACCOUNT CRUD API + AUTH (Node.js + Express + MySQL2) // JSON API — Login redirects to dashboard route // ===================================================== /* INSTALL npm init -y npm install express mysql2 bcryptjs jsonwebtoken dotenv cors helmet express-rate-limit multer npm install nodemon --save-dev */ // ===================================================== // FOLDER STRUCTURE // ===================================================== /* /config/db.js /models/userModel.js /controllers/authController.js /controllers/investmentController.js /middleware/authMiddleware.js /middleware/uploadMiddleware.js /routes/authRoutes.js /routes/investmentRoutes.js /server.js */ // ===================================================== // server.js // ===================================================== require('dotenv').config(); const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const authRoutes = require('./routes/authRoutes'); const investmentRoutes = require('./routes/investmentRoutes'); const app = express(); app.use(helmet()); app.use(cors()); app.use(express.json()); app.use('/uploads', express.static('uploads')); app.use('/api/auth', authRoutes); app.use('/api/investments', investmentRoutes); app.get('/dashboard', (req,res)=>{ res.json({message:'Investment Profile Dashboard'}); }); app.use((err,req,res,next)=>{ console.error(err); res.status(500).json({message:'Server error'}); }); app.listen(process.env.PORT || 5000, ()=> console.log('Server running') ); // ===================================================== // config/db.js // ===================================================== const mysql = require('mysql2/promise'); module.exports = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, connectionLimit: 10 }); // ===================================================== // middleware/authMiddleware.js // ===================================================== const jwt = require('jsonwebtoken'); exports.verifyToken = (req,res,next)=>{ const header = req.headers.authorization; if(!header) return res.status(401).json({message:'No token'}); try{ const token = header.split(' ')[1]; req.user = jwt.verify(token, process.env.JWT_SECRET); next(); }catch{ res.status(401).json({message:'Invalid token'}); } }; // ===================================================== // middleware/uploadMiddleware.js // ===================================================== const multer = require('multer'); const storage = multer.diskStorage({ destination: 'uploads/', filename: (req,file,cb)=>{ cb(null, Date.now() + '-' + file.originalname); } }); exports.upload = multer({storage}); // ===================================================== // models/userModel.js // ===================================================== const db = require('../config/db'); exports.createAccount = async data => { const sql = ` INSERT INTO investment_accounts (firstname, lastname, username, email, phonenumber, password_hash, total_investment_amount, account_pin, bvn, dateofbirth, profile_picture) VALUES (?,?,?,?,?,?,?,?,?,?,?)`; const [r] = await db.execute(sql,[ data.firstname, data.lastname, data.username, data.email, data.phonenumber, data.password_hash, 0, // total investment default null, // pin later null, // bvn later null, // dob later null // profile picture later ]); return r.insertId; }; exports.findByEmail = async email => { const [r] = await db.execute( 'SELECT * FROM investment_accounts WHERE email=?', [email] ); return r[0]; }; exports.findById = async id => { const [r] = await db.execute( 'SELECT * FROM investment_accounts WHERE id=?', [id] ); return r[0]; }; exports.updateProfile = async (id,data)=>{ const sql = ` UPDATE investment_accounts SET total_investment_amount=?, account_pin=?, bvn=?, dateofbirth=?, profile_picture=? WHERE id=?`; await db.execute(sql,[ data.total_investment_amount, data.account_pin, data.bvn, data.dateofbirth, data.profile_picture, id ]); }; exports.getAll = async ()=>{ const [r] = await db.execute('SELECT * FROM investment_accounts'); return r; }; exports.deleteById = async id => { await db.execute('DELETE FROM investment_accounts WHERE id=?',[id]); }; // ===================================================== // controllers/authController.js // ===================================================== const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const User = require('../models/userModel'); const sign = u => jwt.sign({id:u.id,email:u.email}, process.env.JWT_SECRET,{expiresIn:'1d'}); exports.register = async (req,res)=>{ const {firstname,lastname,username,email,phonenumber,password} = req.body; if(!email || !password) return res.status(400).json({message:'Missing fields'}); if(await User.findByEmail(email)) return res.status(409).json({message:'Email exists'}); const hash = await bcrypt.hash(password,12); const id = await User.createAccount({ firstname,lastname,username,email,phonenumber,password_hash:hash }); res.status(201).json({ message:'Account created', token: sign({id,email}), redirect:'/dashboard' }); }; exports.login = async (req,res)=>{ const {email,password} = req.body; const user = await User.findByEmail(email); if(!user) return res.status(401).json({message:'Invalid credentials'}); const ok = await bcrypt.compare(password, user.password_hash); if(!ok) return res.status(401).json({message:'Invalid credentials'}); res.json({ message:'Login success', token: sign(user), redirect:'/dashboard' }); }; // ===================================================== // controllers/investmentController.js // ===================================================== const UserModel = require('../models/userModel'); exports.getProfile = async (req,res)=>{ const user = await UserModel.findById(req.user.id); res.json(user); }; exports.updateProfile = async (req,res)=>{ const file = req.file ? req.file.filename : null; await UserModel.updateProfile(req.user.id,{ total_investment_amount: req.body.total_investment_amount, account_pin: req.body.account_pin, bvn: req.body.bvn, dateofbirth: req.body.dateofbirth, profile_picture: file }); res.json({message:'Profile updated'}); }; exports.listAll = async (req,res)=>{ res.json(await UserModel.getAll()); }; exports.remove = async (req,res)=>{ await UserModel.deleteById(req.params.id); res.json({message:'Deleted'}); }; // ===================================================== // routes/authRoutes.js // ===================================================== const express = require('express'); const rateLimit = require('express-rate-limit'); const ctrl = require('../controllers/authController'); const router = express.Router(); const limiter = rateLimit({windowMs:15*60*1000,max:100}); router.post('/register', limiter, ctrl.register); router.post('/login', limiter, ctrl.login); module.exports = router; // ===================================================== // routes/investmentRoutes.js // ===================================================== const express = require('express'); const ctrl = require('../controllers/investmentController'); const {verifyToken} = require('../middleware/authMiddleware'); const {upload} = require('../middleware/uploadMiddleware'); const router = express.Router(); router.get('/me', verifyToken, ctrl.getProfile); router.put('/me', verifyToken, upload.single('profile_picture'), ctrl.updateProfile); router.get('/', verifyToken, ctrl.listAll); router.delete('/:id', verifyToken, ctrl.remove); module.exports = router; // ===================================================== // MYSQL TABLE // ===================================================== /* CREATE TABLE investment_accounts ( id INT AUTO_INCREMENT PRIMARY KEY, firstname VARCHAR(100), lastname VARCHAR(100), username VARCHAR(100), email VARCHAR(150) UNIQUE, phonenumber VARCHAR(40), password_hash VARCHAR(255), total_investment_amount DECIMAL(15,2) DEFAULT 0, account_pin VARCHAR(20), bvn VARCHAR(30), dateofbirth DATE, profile_picture VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); */ // ===================================================== // .env // ===================================================== /* PORT=5000 DB_HOST=localhost DB_USER=root DB_PASS=yourpass DB_NAME=investdb JWT_SECRET=supersecret */ // ========================================== // FRONTEND FETCH API CLIENT // Connects to Investment Auth CRUD Backend // ========================================== // ---- CONFIG ---- const API_BASE = "http://localhost:5000/api"; // ========================================== // TOKEN STORAGE HELPERS // ========================================== function saveToken(token) { localStorage.setItem('auth_token', token); } function getToken() { return localStorage.getItem('auth_token'); } function authHeader() { return { Authorization: `Bearer ${getToken()}` }; } // ========================================== // REGISTER ACCOUNT (Create Investment Account) // form id="registerForm" // ========================================== document.getElementById('registerForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const btn = e.target.querySelector('button[type="submit"]'); btn.disabled = true; btn.innerText = "Creating..."; const formData = Object.fromEntries(new FormData(e.target)); try { const res = await fetch(`${API_BASE}/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await res.json(); if (!res.ok) throw new Error(data.message); saveToken(data.token); alert('Account created'); window.location.href = data.redirect; } catch (err) { alert(err.message); } btn.disabled = false; btn.innerText = "Register"; }); // ========================================== // LOGIN // form id="loginForm" // ========================================== document.getElementById('loginForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const btn = e.target.querySelector('button[type="submit"]'); btn.disabled = true; btn.innerText = "Logging in..."; const body = Object.fromEntries(new FormData(e.target)); try { const res = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const data = await res.json(); if (!res.ok) throw new Error(data.message); saveToken(data.token); window.location.href = data.redirect; } catch (err) { alert(err.message); } btn.disabled = false; btn.innerText = "Login"; }); // ========================================== // LOAD MY PROFILE DASHBOARD DATA // call on dashboard page load // ========================================== async function loadMyProfile() { try { const res = await fetch(`${API_BASE}/investments/me`, { headers: authHeader() }); if (res.status === 401) { alert('Session expired'); return window.location.href = '/login.html'; } const user = await res.json(); // Example render document.getElementById('dashName').innerText = user.firstname + ' ' + user.lastname; document.getElementById('dashEmail').innerText = user.email; document.getElementById('dashAmount').innerText = user.total_investment_amount; if (user.profile_picture) { document.getElementById('dashPic').src = `/uploads/${user.profile_picture}`; } } catch (err) { console.error(err); } } // ========================================== // UPDATE PROFILE (PIN, BVN, DOB, Amount, Picture) // form id="profileForm" // ========================================== document.getElementById('profileForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const btn = e.target.querySelector('button[type="submit"]'); btn.disabled = true; btn.innerText = "Updating..."; const fd = new FormData(e.target); // includes file upload try { const res = await fetch(`${API_BASE}/investments/me`, { method: 'PUT', headers: authHeader(), body: fd }); const data = await res.json(); if (!res.ok) throw new Error(data.message); alert('Profile updated'); loadMyProfile(); } catch (err) { alert(err.message); } btn.disabled = false; btn.innerText = "Save Changes"; }); // ========================================== // LIST ALL INVESTMENT ACCOUNTS (Admin table) // ========================================== async function loadAllAccounts() { const res = await fetch(`${API_BASE}/investments`, { headers: authHeader() }); const list = await res.json(); const tbody = document.getElementById('accountsTable'); tbody.innerHTML = ''; list.forEach(acc => { tbody.innerHTML += `