Skip to content

Commit f0f5e68

Browse files
Initial commit
0 parents  commit f0f5e68

23 files changed

+2873
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
.env
3+
node_modules

app.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const express = require('express');
2+
const mongoose = require('mongoose');
3+
const routes = require('./routes/index');
4+
require('dotenv').config();
5+
6+
const app = express();
7+
const port = process.env.PORT || 3000;
8+
9+
app.use(express.json());
10+
app.use('/api', routes);
11+
12+
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
13+
.then(() => console.log('MongoDB connected'))
14+
.catch(err => console.log(err));
15+
16+
app.listen(port, () => {
17+
console.log(`Server is running on port ${port}`);
18+
});

config/database.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const mongoose = require('mongoose');
2+
require('dotenv').config();
3+
4+
const connectDB = async () => {
5+
try {
6+
await mongoose.connect(process.env.MONGODB_URI, {
7+
useNewUrlParser: true,
8+
useUnifiedTopology: true
9+
});
10+
console.log('MongoDB connected');
11+
} catch (err) {
12+
console.error(err.message);
13+
process.exit(1);
14+
}
15+
};
16+
17+
module.exports = connectDB;

controllers/authController.js

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
const bcrypt = require('bcrypt');
2+
const speakeasy = require('speakeasy');
3+
const emailSender = require('../utils/emailSender');
4+
const jwtHelper = require('../utils/jwtHelper');
5+
const User = require('../models/User');
6+
7+
// Temporary storage for OTPs during registration
8+
const otpStore = {};
9+
10+
// Step 1: Register and send OTP
11+
exports.register = async (req, res) => {
12+
try {
13+
const { name, email, password, role, club } = req.body;
14+
15+
// Check if user already exists
16+
const existingUser = await User.findOne({ email });
17+
if (existingUser) {
18+
return res.status(400).json({ error: 'Email already registered' });
19+
}
20+
21+
// Generate OTP
22+
const otp = speakeasy.totp({
23+
secret: process.env.OTP_SECRET, // Use a strong secret
24+
encoding: 'base32',
25+
});
26+
27+
// Store OTP temporarily
28+
otpStore[email] = {
29+
name,
30+
password,
31+
role,
32+
club,
33+
otp,
34+
expiresAt: Date.now() + 5 * 60 * 1000, // OTP valid for 5 minutes
35+
};
36+
37+
// Send OTP via email
38+
await emailSender.sendEmail(
39+
email,
40+
'Verify Your Email',
41+
`Your OTP for registration is: ${otp}. It is valid for 5 minutes.`
42+
);
43+
44+
res.status(200).json({ message: 'OTP sent to your email for verification' });
45+
} catch (error) {
46+
res.status(500).json({ error: error.message });
47+
}
48+
};
49+
50+
// Step 2: Verify OTP and create user
51+
exports.verifyOtp = async (req, res) => {
52+
try {
53+
const { email, otp } = req.body;
54+
55+
const storedOtp = otpStore[email];
56+
if (!storedOtp) {
57+
return res.status(400).json({ error: 'OTP not found or expired' });
58+
}
59+
60+
// Check if OTP matches and has not expired
61+
if (storedOtp.otp !== otp || storedOtp.expiresAt < Date.now()) {
62+
return res.status(400).json({ error: 'Invalid or expired OTP' });
63+
}
64+
65+
// Hash the password and save the user
66+
const hashedPassword = await bcrypt.hash(storedOtp.password, 10);
67+
const newUser = new User({
68+
name: storedOtp.name,
69+
email,
70+
password: hashedPassword,
71+
role: storedOtp.role,
72+
club: storedOtp.club,
73+
});
74+
await newUser.save();
75+
76+
// Clear OTP after successful verification
77+
delete otpStore[email];
78+
79+
res.status(201).json({ message: 'User registered and verified successfully', user: newUser });
80+
} catch (error) {
81+
res.status(500).json({ error: error.message });
82+
}
83+
};
84+
85+
// Login with 2FA
86+
exports.login = async (req, res) => {
87+
try {
88+
const { email, password } = req.body;
89+
const user = await User.findOne({ email });
90+
91+
if (!user || !(await bcrypt.compare(password, user.password))) {
92+
return res.status(400).json({ error: 'Invalid email or password' });
93+
}
94+
95+
const token = jwtHelper.signToken({ id: user._id, role: user.role, club: user.club });
96+
res.status(200).json({ message: 'Login successful', token });
97+
} catch (error) {
98+
res.status(500).json({ error: error.message });
99+
}
100+
};
101+
102+
exports.logout = (req, res) => {
103+
res.status(200).json({ message: 'Logout successful' });
104+
};
105+
106+
exports.validateToken = (req, res) => {
107+
try {
108+
const token = req.header('Authorization')?.replace('Bearer ', '');
109+
const decoded = jwtHelper.verifyToken(token);
110+
res.status(200).json({ valid: true, user: decoded });
111+
} catch (error) {
112+
res.status(401).json({ valid: false, error: 'Invalid or expired token' });
113+
}
114+
};

controllers/inventoryController.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const Inventory = require('../models/inventoryModel');
2+
3+
exports.addInventory = async (req, res) => {
4+
try {
5+
const { name, description, quantity } = req.body;
6+
const inventory = new Inventory({ name, description, quantity, addedBy: req.user._id });
7+
await inventory.save();
8+
res.status(201).json({ message: 'Inventory item added', inventory });
9+
} catch (error) {
10+
res.status(500).json({ error: error.message });
11+
}
12+
};
13+
14+
exports.updateInventory = async (req, res) => {
15+
try {
16+
const { id } = req.params;
17+
const updates = req.body;
18+
const inventory = await Inventory.findByIdAndUpdate(id, updates, { new: true });
19+
if (!inventory) return res.status(404).json({ error: 'Item not found' });
20+
res.status(200).json({ message: 'Inventory updated', inventory });
21+
} catch (error) {
22+
res.status(500).json({ error: error.message });
23+
}
24+
};
25+
26+
exports.deleteInventory = async (req, res) => {
27+
try {
28+
const { id } = req.params;
29+
const inventory = await Inventory.findByIdAndDelete(id);
30+
if (!inventory) return res.status(404).json({ error: 'Item not found' });
31+
res.status(200).json({ message: 'Inventory deleted', inventory });
32+
} catch (error) {
33+
res.status(500).json({ error: error.message });
34+
}
35+
};
36+
37+
exports.getAllInventory = async (req, res) => {
38+
try {
39+
const inventory = await Inventory.find();
40+
res.status(200).json({ inventory });
41+
} catch (error) {
42+
res.status(500).json({ error: error.message });
43+
}
44+
};

controllers/requestController.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const Request = require('../models/requestModel');
2+
3+
exports.createRequest = async (req, res) => {
4+
try {
5+
const { items } = req.body;
6+
const request = new Request({ club: req.user.club, coordinator: req.user._id, items });
7+
await request.save();
8+
res.status(201).json({ message: 'Request created', request });
9+
} catch (error) {
10+
res.status(500).json({ error: error.message });
11+
}
12+
};
13+
14+
exports.cancelRequest = async (req, res) => {
15+
try {
16+
const { id } = req.params;
17+
const request = await Request.findByIdAndUpdate(id, { status: 'canceled' }, { new: true });
18+
if (!request) return res.status(404).json({ error: 'Request not found' });
19+
res.status(200).json({ message: 'Request canceled', request });
20+
} catch (error) {
21+
res.status(500).json({ error: error.message });
22+
}
23+
};
24+
25+
exports.getAllRequests = async (req, res) => {
26+
try {
27+
const requests = await Request.find().populate('coordinator', 'name email');
28+
res.status(200).json({ requests });
29+
} catch (error) {
30+
res.status(500).json({ error: error.message });
31+
}
32+
};
33+
34+
exports.getMyRequests = async (req, res) => {
35+
try {
36+
const requests = await Request.find({ club: req.user.club }).populate('coordinator', 'name email');
37+
res.status(200).json({ requests });
38+
} catch (error) {
39+
res.status(500).json({ error: error.message });
40+
}
41+
};
42+
43+
exports.getRequestsByClub = async (req, res) => {
44+
try {
45+
const { clubName } = req.params;
46+
const requests = await Request.find({ club: clubName }).populate('coordinator', 'name email');
47+
res.status(200).json({ requests });
48+
} catch (error) {
49+
res.status(500).json({ error: error.message });
50+
}
51+
};
52+
53+
exports.getRequestsByItem = async (req, res) => {
54+
try {
55+
const { itemId } = req.params;
56+
const requests = await Request.find({ 'items.inventoryId': itemId }).populate('coordinator', 'name email');
57+
res.status(200).json({ requests });
58+
} catch (error) {
59+
res.status(500).json({ error: error.message });
60+
}
61+
};
62+
63+
exports.updateRequestStatus = async (req, res) => {
64+
try {
65+
const { id } = req.params;
66+
const { status } = req.body;
67+
const validStatuses = ['approved', 'rejected'];
68+
if (!validStatuses.includes(status)) {
69+
return res.status(400).json({ error: 'Invalid status' });
70+
}
71+
const request = await Request.findByIdAndUpdate(id, { status }, { new: true });
72+
if (!request) return res.status(404).json({ error: 'Request not found' });
73+
res.status(200).json({ message: 'Request status updated', request });
74+
} catch (error) {
75+
res.status(500).json({ error: error.message });
76+
}
77+
};

db.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
const mongoose = require('mongoose');
3+
4+
//mongodb uri
5+
const MONGO_URI = process.env.MONGO_URI;
6+
7+
const connectDb = async () => {
8+
// Check if MONGO_URI is defined
9+
if (!MONGO_URI) {
10+
console.error('MongoDB URI is not defined. Please add MONGO_URI in your .env file.');
11+
process.exit(1); // Exit the process with failure code
12+
}
13+
14+
try {
15+
await mongoose.connect(MONGO_URI);
16+
console.log('Mongoose connected to MongoDB Atlas');
17+
} catch (err) {
18+
console.error(`Failed to connect to MongoDB: ${err.message}`);
19+
process.exit(1); // Exit the process if there's an error
20+
}
21+
};
22+
23+
module.exports = connectDb;

middlewares/authMiddleware.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const jwtHelper = require('../utils/jwtHelper');
2+
const User = require('../models/User');
3+
4+
// Middleware to check if the user is authenticated
5+
const authenticate = async (req, res, next) => {
6+
try {
7+
const token = req.header('Authorization')?.replace('Bearer ', '');
8+
if (!token) {
9+
return res.status(401).json({ error: 'Access denied. No token provided.' });
10+
}
11+
12+
const decoded = jwtHelper.verifyToken(token);
13+
const user = await User.findById(decoded.id);
14+
if (!user) {
15+
return res.status(401).json({ error: 'Invalid user.' });
16+
}
17+
18+
req.user = user; // Attach user to request object
19+
next();
20+
} catch (error) {
21+
res.status(401).json({ error: 'Unauthorized access.' });
22+
}
23+
};
24+
25+
// Middleware to check for specific roles (e.g., 'core' or 'club')
26+
const authorize = (role) => (req, res, next) => {
27+
if (req.user.role !== role) {
28+
return res.status(403).json({ error: 'Forbidden: Insufficient permissions.' });
29+
}
30+
next();
31+
};
32+
33+
module.exports = { authenticate, authorize };

models/User.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const mongoose = require('mongoose');
2+
3+
const userSchema = new mongoose.Schema(
4+
{
5+
name: {
6+
type: String,
7+
required: true,
8+
},
9+
email: {
10+
type: String,
11+
required: true,
12+
unique: true,
13+
},
14+
password: {
15+
type: String,
16+
required: true,
17+
},
18+
role: {
19+
type: String,
20+
enum: ['core', 'club'],
21+
required: true,
22+
},
23+
club: {
24+
type: String, // Optional, only for 'club' users
25+
default: null,
26+
},
27+
},
28+
{ timestamps: true }
29+
);
30+
31+
module.exports = mongoose.model('User', userSchema);

models/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const mongoose = require('mongoose');
2+
3+
const sampleSchema = new mongoose.Schema({
4+
name: {
5+
type: String,
6+
required: true
7+
},
8+
value: {
9+
type: Number,
10+
required: true
11+
}
12+
});
13+
14+
module.exports = mongoose.model('SampleModel', sampleSchema);

0 commit comments

Comments
 (0)