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));