mirror of
https://github.com/Alvin-Zilverstand/Challenge_15_Magazijn_App_Maken.git
synced 2026-03-06 11:06:34 +01:00
init
This commit is contained in:
86
routes/auth.js
Normal file
86
routes/auth.js
Normal file
@@ -0,0 +1,86 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
const router = express.Router();
|
||||
|
||||
// Register new student
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password, email } = req.body;
|
||||
|
||||
// Check if email already exists
|
||||
const existingEmail = await User.findOne({ email });
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({ message: 'Email already registered' });
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
const existingUsername = await User.findOne({ username });
|
||||
if (existingUsername) {
|
||||
return res.status(400).json({ message: 'Username already taken' });
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create new student user
|
||||
const user = new User({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
email,
|
||||
role: 'student' // Force role to be student
|
||||
});
|
||||
|
||||
await user.save();
|
||||
|
||||
// Create and send token
|
||||
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '24h' });
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Student account created successfully',
|
||||
token,
|
||||
role: user.role,
|
||||
username: user.username
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
message: 'Invalid email format. Email must be in the format: 123456@vistacollege.nl'
|
||||
});
|
||||
}
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
const user = await User.findOne({ username: username.toLowerCase() });
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!passwordMatch) {
|
||||
return res.status(401).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '24h' });
|
||||
|
||||
res.json({
|
||||
token,
|
||||
role: user.role,
|
||||
username: user.username
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
117
routes/items.js
Normal file
117
routes/items.js
Normal file
@@ -0,0 +1,117 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const { auth, adminOnly } = require('../middleware/auth');
|
||||
const Item = require('../models/Item');
|
||||
const router = express.Router();
|
||||
|
||||
// Configure multer for image upload
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, 'public/images/items/');
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, 'item-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Not an image! Please upload an image.'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get all items
|
||||
router.get('/', auth, async (req, res) => {
|
||||
try {
|
||||
const items = await Item.find();
|
||||
res.json(items);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get single item by ID
|
||||
router.get('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const item = await Item.findById(req.params.id);
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'Item not found' });
|
||||
}
|
||||
res.json(item);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Add new item (admin only)
|
||||
router.post('/', auth, adminOnly, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
const itemData = {
|
||||
name: req.body.name,
|
||||
description: req.body.description,
|
||||
location: req.body.location,
|
||||
quantity: parseInt(req.body.quantity)
|
||||
};
|
||||
|
||||
// If an image was uploaded, set the imageUrl
|
||||
if (req.file) {
|
||||
itemData.imageUrl = `/images/items/${req.file.filename}`;
|
||||
}
|
||||
|
||||
const item = new Item(itemData);
|
||||
await item.save();
|
||||
res.status(201).json(item);
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Update item (admin only)
|
||||
router.put('/:id', auth, adminOnly, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
const updateData = {
|
||||
name: req.body.name,
|
||||
description: req.body.description,
|
||||
location: req.body.location,
|
||||
quantity: parseInt(req.body.quantity)
|
||||
};
|
||||
|
||||
// If an image was uploaded, set the new imageUrl
|
||||
if (req.file) {
|
||||
updateData.imageUrl = `/images/items/${req.file.filename}`;
|
||||
} else if (req.body.imageUrl !== undefined) {
|
||||
// If imageUrl is explicitly provided (including empty string for removal)
|
||||
updateData.imageUrl = req.body.imageUrl || '/images/default-item.png';
|
||||
}
|
||||
|
||||
const item = await Item.findByIdAndUpdate(req.params.id, updateData, { new: true });
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'Item not found' });
|
||||
}
|
||||
res.json(item);
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete item (admin only)
|
||||
router.delete('/:id', auth, adminOnly, async (req, res) => {
|
||||
try {
|
||||
const item = await Item.findByIdAndDelete(req.params.id);
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'Item not found' });
|
||||
}
|
||||
res.json({ message: 'Item deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
208
routes/reservations.js
Normal file
208
routes/reservations.js
Normal file
@@ -0,0 +1,208 @@
|
||||
const express = require('express');
|
||||
const { auth, adminOnly } = require('../middleware/auth');
|
||||
const Reservation = require('../models/Reservation');
|
||||
const Item = require('../models/Item');
|
||||
const router = express.Router();
|
||||
|
||||
// Get all reservations (admin only)
|
||||
router.get('/', auth, adminOnly, async (req, res) => {
|
||||
try {
|
||||
const reservations = await Reservation.find({ status: { $ne: 'ARCHIVED' } })
|
||||
.populate('userId', 'username')
|
||||
.populate('itemId')
|
||||
.exec();
|
||||
|
||||
if (!reservations) {
|
||||
return res.json([]); // Return empty array if no reservations
|
||||
}
|
||||
|
||||
const formattedReservations = reservations
|
||||
.filter(reservation => reservation.userId && reservation.itemId) // Filter out any invalid references
|
||||
.map(reservation => ({
|
||||
_id: reservation._id,
|
||||
studentName: reservation.userId.username,
|
||||
itemName: reservation.itemId.name,
|
||||
location: reservation.itemId.location,
|
||||
status: reservation.status,
|
||||
quantity: reservation.quantity || 1,
|
||||
reservedDate: reservation.reservedDate
|
||||
}));
|
||||
|
||||
res.json(formattedReservations);
|
||||
} catch (error) {
|
||||
console.error('Error fetching reservations:', error);
|
||||
res.status(500).json({
|
||||
message: 'Failed to load reservations',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get user's reservations
|
||||
router.get('/my', auth, async (req, res) => {
|
||||
try {
|
||||
const reservations = await Reservation.find({
|
||||
userId: req.user._id,
|
||||
status: { $ne: 'ARCHIVED' }
|
||||
}).populate('itemId');
|
||||
|
||||
// Filter out reservations with deleted items and format the response
|
||||
const formattedReservations = reservations
|
||||
.filter(reservation => reservation.itemId) // Only include reservations with valid items
|
||||
.map(reservation => ({
|
||||
_id: reservation._id,
|
||||
itemName: reservation.itemId.name,
|
||||
location: reservation.itemId.location,
|
||||
status: reservation.status,
|
||||
quantity: reservation.quantity || 1,
|
||||
reservedDate: reservation.reservedDate
|
||||
}));
|
||||
|
||||
res.json(formattedReservations);
|
||||
} catch (error) {
|
||||
console.error('Error fetching user reservations:', error);
|
||||
res.status(500).json({
|
||||
message: 'Failed to load reservations',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create reservation
|
||||
router.post('/', auth, async (req, res) => {
|
||||
try {
|
||||
const item = await Item.findById(req.body.itemId);
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'Item not found' });
|
||||
}
|
||||
|
||||
const requestedQuantity = parseInt(req.body.quantity) || 1;
|
||||
if (item.quantity < item.reserved + requestedQuantity) {
|
||||
return res.status(400).json({ message: 'Requested quantity is not available' });
|
||||
}
|
||||
|
||||
const reservation = new Reservation({
|
||||
itemId: req.body.itemId,
|
||||
userId: req.user._id,
|
||||
quantity: requestedQuantity
|
||||
});
|
||||
|
||||
await reservation.save();
|
||||
item.reserved += requestedQuantity;
|
||||
await item.save();
|
||||
|
||||
res.status(201).json(reservation);
|
||||
} catch (error) {
|
||||
res.status(400).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Update reservation status (admin can update any, students can only mark as returned)
|
||||
router.patch('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const reservation = await Reservation.findById(req.params.id).populate('userId');
|
||||
if (!reservation) {
|
||||
return res.status(404).json({ message: 'Reservation not found' });
|
||||
}
|
||||
|
||||
// Check authorization
|
||||
const isAdmin = req.user.role === 'admin';
|
||||
const isOwner = reservation.userId._id.toString() === req.user._id.toString();
|
||||
const isReturning = req.body.status === 'RETURNED';
|
||||
|
||||
if (!isAdmin && (!isOwner || !isReturning)) {
|
||||
return res.status(403).json({
|
||||
message: 'Not authorized. Students can only return their own items.'
|
||||
});
|
||||
}
|
||||
|
||||
// Additional validation for students
|
||||
if (!isAdmin && isReturning && reservation.status !== 'APPROVED') {
|
||||
return res.status(400).json({
|
||||
message: 'Can only return approved items'
|
||||
});
|
||||
}
|
||||
|
||||
const item = await Item.findById(reservation.itemId);
|
||||
if (!item) {
|
||||
return res.status(404).json({ message: 'Item not found' });
|
||||
}
|
||||
|
||||
const oldStatus = reservation.status;
|
||||
const newStatus = req.body.status;
|
||||
reservation.status = newStatus;
|
||||
|
||||
// Include quantity in the update if provided
|
||||
if (req.body.quantity !== undefined) {
|
||||
reservation.quantity = req.body.quantity;
|
||||
}
|
||||
|
||||
await reservation.save();
|
||||
|
||||
// Update item reserved count based on status change
|
||||
if (oldStatus === 'PENDING' && newStatus === 'REJECTED') {
|
||||
item.reserved = Math.max(0, item.reserved - (reservation.quantity || 1));
|
||||
await item.save();
|
||||
} else if (oldStatus === 'APPROVED' && newStatus === 'RETURNED') {
|
||||
item.reserved = Math.max(0, item.reserved - (reservation.quantity || 1));
|
||||
await item.save();
|
||||
}
|
||||
|
||||
res.json(reservation);
|
||||
} catch (error) {
|
||||
console.error('Error updating reservation:', error);
|
||||
res.status(400).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete reservation (admin can delete any, students can only delete their own)
|
||||
router.delete('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const reservation = await Reservation.findById(req.params.id).populate('userId');
|
||||
if (!reservation) {
|
||||
return res.status(404).json({ message: 'Reservation not found' });
|
||||
}
|
||||
|
||||
// Check if user is authorized to delete this reservation
|
||||
if (req.user.role !== 'admin' && reservation.userId._id.toString() !== req.user._id.toString()) {
|
||||
return res.status(403).json({ message: 'Not authorized to delete this reservation' });
|
||||
}
|
||||
|
||||
// Update item's reserved count
|
||||
const item = await Item.findById(reservation.itemId);
|
||||
if (item) {
|
||||
item.reserved = Math.max(0, item.reserved - 1); // Ensure it doesn't go below 0
|
||||
await item.save();
|
||||
}
|
||||
|
||||
await Reservation.findByIdAndDelete(req.params.id);
|
||||
res.json({ message: 'Reservation deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Archive reservation (admin only)
|
||||
router.patch('/:id/archive', auth, adminOnly, async (req, res) => {
|
||||
try {
|
||||
const reservation = await Reservation.findById(req.params.id);
|
||||
if (!reservation) {
|
||||
return res.status(404).json({ message: 'Reservation not found' });
|
||||
}
|
||||
|
||||
// Only allow archiving of returned reservations
|
||||
if (reservation.status !== 'RETURNED') {
|
||||
return res.status(400).json({ message: 'Can only archive returned reservations' });
|
||||
}
|
||||
|
||||
reservation.status = 'ARCHIVED';
|
||||
await reservation.save();
|
||||
|
||||
res.json({ message: 'Reservation archived successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error archiving reservation:', error);
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
43
routes/upload.js
Normal file
43
routes/upload.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const { auth, adminOnly } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// Configure multer for image upload
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, 'public/images/items/');
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, 'item-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Not an image! Please upload an image.'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload image route
|
||||
router.post('/', auth, adminOnly, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'No file uploaded' });
|
||||
}
|
||||
|
||||
const imageUrl = `/images/items/${req.file.filename}`;
|
||||
res.json({ imageUrl });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user