MONGODB_URI=mongodb://localhost:27017/adaseads JWT_SECRET=adase_super_secret_2026 ADMIN_EMAIL=shares@cashchat.se ADMIN_PASSWORD_HASH=$2a$10$… (we’ll generate below) PORT=3000 project/ ├── server.js # Express backend, API routes, MongoDB models ├── admin.html # Admin dashboard (login, approve requests, view users) ├── index.html # Campaign page with wallet & investment panel ├── package.json └── .env # Environment variables (DB URL, JWT secret, admin creds) require(‘dotenv’).config(); const express = require(‘express’); const mongoose = require(‘mongoose’); const bcrypt = require(‘bcryptjs’); const jwt = require(‘jsonwebtoken’); const cors = require(‘cors’); const app = express(); app.use(cors()); app.use(express.json()); // ———- MongoDB Models ———- const userSchema = new mongoose.Schema({ name: String, email: { type: String, unique: true }, password: String, walletBalance: { type: Number, default: 0 }, createdAt: { type: Date, default: Date.now } }); const transactionSchema = new mongoose.Schema({ userId: mongoose.Schema.Types.ObjectId, type: { type: String, enum: [‘topup’,’withdraw’,’investment’,’transfer_in’,’transfer_out’] }, amount: Number, status: { type: String, enum: [‘pending’,’approved’,’declined’], default: ‘pending’ }, description: String, adminNote: String, createdAt: { type: Date, default: Date.now } }); const topupRequestSchema = new mongoose.Schema({ userId: mongoose.Schema.Types.ObjectId, amount: Number, status: { type: String, enum: [‘pending’,’approved’,’declined’], default: ‘pending’ }, adminNote: String, userPaymentDetails: String, createdAt: { type: Date, default: Date.now } }); const withdrawRequestSchema = new mongoose.Schema({ userId: mongoose.Schema.Types.ObjectId, amount: Number, status: { type: String, enum: [‘pending’,’approved’,’declined’], default: ‘pending’ }, adminNote: String, userPaymentDetails: String, createdAt: { type: Date, default: Date.now } }); const transferRequestSchema = new mongoose.Schema({ fromUserId: mongoose.Schema.Types.ObjectId, toUserEmail: String, amount: Number, status: { type: String, enum: [‘pending’,’approved’,’declined’], default: ‘pending’ }, adminNote: String, createdAt: { type: Date, default: Date.now } }); const investmentSchema = new mongoose.Schema({ userId: mongoose.Schema.Types.ObjectId, principal: Number, interestRate: Number, discountPercent: Number, maturityMonths: Number, certificateNumber: String, issuedAt: { type: Date, default: Date.now } }); const User = mongoose.model(‘User’, userSchema); const Transaction = mongoose.model(‘Transaction’, transactionSchema); const TopupRequest = mongoose.model(‘TopupRequest’, topupRequestSchema); const WithdrawRequest = mongoose.model(‘WithdrawRequest’, withdrawRequestSchema); const TransferRequest = mongoose.model(‘TransferRequest’, transferRequestSchema); const Investment = mongoose.model(‘Investment’, investmentSchema); // ———- Middleware ———- const auth = (req, res, next) => { const token = req.header(‘x-auth-token’); if (!token) return res.status(401).json({ msg: ‘No token, authorization denied’ }); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded.user; next(); } catch (e) { res.status(401).json({ msg: ‘Token is not valid’ }); } }; const adminAuth = (req, res, next) => { const token = req.header(‘x-admin-token’); if (!token) return res.status(401).json({ msg: ‘Admin token missing’ }); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); if (decoded.user.email !== process.env.ADMIN_EMAIL) throw new Error(); req.admin = decoded.user; next(); } catch (e) { res.status(401).json({ msg: ‘Admin authorization failed’ }); } }; // ———- User Registration & Login ———- app.post(‘/api/users/register’, async (req, res) => { const { name, email, password } = req.body; try { let user = await User.findOne({ email }); if (user) return res.status(400).json({ msg: ‘User already exists’ }); user = new User({ name, email, password }); const salt = await bcrypt.genSalt(10); user.password = await bcrypt.hash(password, salt); await user.save(); const payload = { user: { id: user.id, email: user.email } }; jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: ‘7d’ }, (err, token) => { if (err) throw err; res.json({ token, user: { id: user.id, name: user.name, email: user.email, walletBalance: user.walletBalance } }); }); } catch (err) { console.error(err); res.status(500).send(‘Server error’); } }); app.post(‘/api/users/login’, async (req, res) => { const { email, password } = req.body; try { let user = await User.findOne({ email }); if (!user) return res.status(400).json({ msg: ‘Invalid credentials’ }); const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ msg: ‘Invalid credentials’ }); const payload = { user: { id: user.id, email: user.email } }; jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: ‘7d’ }, (err, token) => { if (err) throw err; res.json({ token, user: { id: user.id, name: user.name, email: user.email, walletBalance: user.walletBalance } }); }); } catch (err) { console.error(err); res.status(500).send(‘Server error’); } }); app.get(‘/api/users/me’, auth, async (req, res) => { try { const user = await User.findById(req.user.id).select(‘-password’); res.json(user); } catch (err) { res.status(500).send(‘Server error’); } }); // ———- Wallet Operations (User) ———- app.post(‘/api/wallet/topup’, auth, async (req, res) => { const { amount } = req.body; if (amount <= 0) return res.status(400).json({ msg: 'Invalid amount' }); try { const req = new TopupRequest({ userId: req.user.id, amount }); await req.save(); res.json({ msg: 'Top-up request submitted', requestId: req._id }); } catch (err) { res.status(500).send('Server error'); } }); app.post('/api/wallet/withdraw', auth, async (req, res) => { const { amount } = req.body; if (amount <= 0) return res.status(400).json({ msg: 'Invalid amount' }); try { const user = await User.findById(req.user.id); if (user.walletBalance < amount) return res.status(400).json({ msg: 'Insufficient balance' }); const req = new WithdrawRequest({ userId: req.user.id, amount }); await req.save(); res.json({ msg: 'Withdrawal request submitted', requestId: req._id }); } catch (err) { res.status(500).send('Server error'); } }); app.post('/api/wallet/transfer', auth, async (req, res) => { const { toEmail, amount } = req.body; if (amount <= 0) return res.status(400).json({ msg: 'Invalid amount' }); try { const fromUser = await User.findById(req.user.id); if (fromUser.walletBalance < amount) return res.status(400).json({ msg: 'Insufficient balance' }); const toUser = await User.findOne({ email: toEmail }); if (!toUser) return res.status(400).json({ msg: 'Recipient not found' }); const req = new TransferRequest({ fromUserId: req.user.id, toUserEmail: toEmail, amount }); await req.save(); res.json({ msg: 'Transfer request submitted for admin approval', requestId: req._id }); } catch (err) { res.status(500).send('Server error'); } }); // ---------- Investment (Buy Convertible Note) ---------- app.post('/api/invest', auth, async (req, res) => { const { principal } = req.body; if (principal < 100000) return res.status(400).json({ msg: 'Minimum investment is UGX 100,000' }); try { const user = await User.findById(req.user.id); if (user.walletBalance < principal) return res.status(400).json({ msg: 'Insufficient wallet balance' }); user.walletBalance -= principal; await user.save(); const certNumber = 'CN-' + new Date().getFullYear() + '-' + Math.random().toString(36).substr(2, 8).toUpperCase(); const investment = new Investment({ userId: user.id, principal, interestRate: 17, discountPercent: 20, maturityMonths: 36, certificateNumber: certNumber }); await investment.save(); await Transaction.create({ userId: user.id, type: 'investment', amount: principal, status: 'approved', description: `Convertible note purchase - ${certNumber}` }); res.json({ msg: 'Investment successful', certificateNumber: certNumber, newBalance: user.walletBalance }); } catch (err) { res.status(500).send('Server error'); } }); app.get('/api/investments', auth, async (req, res) => { try { const investments = await Investment.find({ userId: req.user.id }).sort({ issuedAt: -1 }); res.json(investments); } catch (err) { res.status(500).send(‘Server error’); } }); // ———- Transaction History ———- app.get(‘/api/transactions’, auth, async (req, res) => { try { const txns = await Transaction.find({ userId: req.user.id }).sort({ createdAt: -1 }); res.json(txns); } catch (err) { res.status(500).send(‘Server error’); } }); // ———- Admin Routes ———- app.post(‘/api/admin/login’, async (req, res) => { const { email, password } = req.body; if (email !== process.env.ADMIN_EMAIL) return res.status(400).json({ msg: ‘Invalid credentials’ }); const isMatch = await bcrypt.compare(password, process.env.ADMIN_PASSWORD_HASH); if (!isMatch) return res.status(400).json({ msg: ‘Invalid credentials’ }); const payload = { user: { id: ‘admin’, email: email } }; jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: ‘1d’ }, (err, token) => { if (err) throw err; res.json({ token }); }); }); app.get(‘/api/admin/requests’, adminAuth, async (req, res) => { try { const topups = await TopupRequest.find().populate(‘userId’,’name email’).sort({ createdAt: -1 }); const withdraws = await WithdrawRequest.find().populate(‘userId’,’name email’).sort({ createdAt: -1 }); const transfers = await TransferRequest.find().populate(‘fromUserId’,’name email’).sort({ createdAt: -1 }); res.json({ topups, withdraws, transfers }); } catch (err) { res.status(500).send(‘Server error’); } }); app.post(‘/api/admin/topup/:id’, adminAuth, async (req, res) => { const { status, adminNote, userPaymentDetailsRequest } = req.body; // status: ‘approved’ or ‘declined’ try { const topup = await TopupRequest.findById(req.params.id); if (!topup) return res.status(404).json({ msg: ‘Not found’ }); if (status === ‘approved’) { const user = await User.findById(topup.userId); user.walletBalance += topup.amount; await user.save(); await Transaction.create({ userId: user.id, type: ‘topup’, amount: topup.amount, status: ‘approved’, description: ‘Top-up approved’, adminNote }); } topup.status = status; topup.adminNote = adminNote || ”; await topup.save(); // If admin requests payment details, we can send a notification to user (implement via socket or email later) res.json({ msg: `Top-up ${status}`, request: topup }); } catch (err) { res.status(500).send(‘Server error’); } }); app.post(‘/api/admin/withdraw/:id’, adminAuth, async (req, res) => { const { status, adminNote } = req.body; try { const withdraw = await WithdrawRequest.findById(req.params.id); if (!withdraw) return res.status(404).json({ msg: ‘Not found’ }); if (status === ‘approved’) { const user = await User.findById(withdraw.userId); if (user.walletBalance < withdraw.amount) return res.status(400).json({ msg: 'Insufficient balance' }); user.walletBalance -= withdraw.amount; await user.save(); await Transaction.create({ userId: user.id, type: 'withdraw', amount: withdraw.amount, status: 'approved', description: 'Withdrawal approved', adminNote }); } withdraw.status = status; withdraw.adminNote = adminNote || ''; await withdraw.save(); res.json({ msg: `Withdrawal ${status}` }); } catch (err) { res.status(500).send('Server error'); } }); app.post('/api/admin/transfer/:id', adminAuth, async (req, res) => { const { status, adminNote } = req.body; try { const transfer = await TransferRequest.findById(req.params.id); if (!transfer) return res.status(404).json({ msg: ‘Not found’ }); if (status === ‘approved’) { const fromUser = await User.findById(transfer.fromUserId); const toUser = await User.findOne({ email: transfer.toUserEmail }); if (!fromUser || !toUser) return res.status(400).json({ msg: ‘User not found’ }); if (fromUser.walletBalance < transfer.amount) return res.status(400).json({ msg: 'Insufficient balance' }); fromUser.walletBalance -= transfer.amount; toUser.walletBalance += transfer.amount; await fromUser.save(); await toUser.save(); await Transaction.create({ userId: fromUser.id, type: 'transfer_out', amount: transfer.amount, status: 'approved', description: `Transfer to ${transfer.toUserEmail}` }); await Transaction.create({ userId: toUser.id, type: 'transfer_in', amount: transfer.amount, status: 'approved', description: `Transfer from ${fromUser.email}` }); } transfer.status = status; transfer.adminNote = adminNote || ''; await transfer.save(); res.json({ msg: `Transfer ${status}` }); } catch (err) { res.status(500).send('Server error'); } }); // Admin can send a popup message to a user about payment details (via a simple endpoint that the frontend polls) // We'll store a notification per user const notificationSchema = new mongoose.Schema({ userId: mongoose.Schema.Types.ObjectId, message: String, read: { type: Boolean, default: false }, createdAt: { type: Date, default: Date.now } }); const Notification = mongoose.model('Notification', notificationSchema); app.post('/api/admin/notify/:userId', adminAuth, async (req, res) => { const { message } = req.body; await Notification.create({ userId: req.params.userId, message }); res.json({ msg: ‘Notification sent’ }); }); app.get(‘/api/notifications’, auth, async (req, res) => { const notifs = await Notification.find({ userId: req.user.id, read: false }).sort({ createdAt: -1 }); res.json(notifs); }); app.post(‘/api/notifications/read/:id’, auth, async (req, res) => { await Notification.findByIdAndUpdate(req.params.id, { read: true }); res.json({ msg: ‘Marked read’ }); }); // ———- DB Connect & Start ———- mongoose.connect(process.env.MONGODB_URI) .then(() => { console.log(‘MongoDB connected’); app.listen(process.env.PORT, () => console.log(`Server running on port ${process.env.PORT}`)); }) .catch(err => console.error(err));

Pages: 1 2

Start