Files for Todo_admin added
This commit is contained in:
32
Backend/middleware/auth.js
Normal file
32
Backend/middleware/auth.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import User from '../models/User.js';
|
||||
|
||||
export const authenticateToken = async (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'Access token required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findById(decoded.userId).select('-password');
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: 'Invalid token' });
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(403).json({ message: 'Invalid or expired token' });
|
||||
}
|
||||
};
|
||||
|
||||
export const requireAdmin = async (req, res, next) => {
|
||||
if (req.user.role !== 'admin') {
|
||||
return res.status(403).json({ message: 'Admin access required' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
55
Backend/models/Todo.js
Normal file
55
Backend/models/Todo.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const todoSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 200
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 1000
|
||||
},
|
||||
completed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
priority: {
|
||||
type: String,
|
||||
enum: ['low', 'medium', 'high'],
|
||||
default: 'medium'
|
||||
},
|
||||
dueDate: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
assignedBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: false
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'in-progress', 'submitted', 'completed'],
|
||||
default: 'pending'
|
||||
},
|
||||
submittedAt: {
|
||||
type: Date,
|
||||
required: false
|
||||
},
|
||||
completedAt: {
|
||||
type: Date,
|
||||
required: false
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
export default mongoose.model('Todo', todoSchema);
|
||||
47
Backend/models/User.js
Normal file
47
Backend/models/User.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import mongoose from 'mongoose';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
trim: true,
|
||||
minlength: 3,
|
||||
maxlength: 30
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
trim: true,
|
||||
lowercase: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
minlength: 6
|
||||
},
|
||||
role: {
|
||||
type: String, enum: ['user', 'admin'], default: 'user' }
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
userSchema.pre('save', async function(next) {
|
||||
if (!this.isModified('password')) return next();
|
||||
|
||||
try {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
this.password = await bcrypt.hash(this.password, salt);
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
userSchema.methods.comparePassword = async function(candidatePassword) {
|
||||
return bcrypt.compare(candidatePassword, this.password);
|
||||
};
|
||||
|
||||
export default mongoose.model('User', userSchema);
|
||||
1587
Backend/package-lock.json
generated
Normal file
1587
Backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
Backend/package.json
Normal file
23
Backend/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "todo-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend for Todo App with Authentication",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test-db": "node test-connection.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^8.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
103
Backend/routes/auth.js
Normal file
103
Backend/routes/auth.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import express from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import User from '../models/User.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Register
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
// Validation
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({
|
||||
message: 'Username, email, and password are required'
|
||||
});
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({
|
||||
message: 'Password must be at least 6 characters long'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await User.findOne({
|
||||
$or: [{ email }, { username }]
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
message: 'User with this email or username already exists'
|
||||
});
|
||||
}
|
||||
|
||||
// Create new user
|
||||
const user = new User({ username, email, password });
|
||||
await user.save();
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
message: 'User created successfully',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user by email
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isMatch = await user.comparePassword(password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
229
Backend/routes/todos.js
Normal file
229
Backend/routes/todos.js
Normal file
@@ -0,0 +1,229 @@
|
||||
import express from 'express';
|
||||
import Todo from '../models/Todo.js';
|
||||
import User from '../models/User.js';
|
||||
import { requireAdmin } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get todos - different behavior for admin vs user
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { date, userId } = req.query;
|
||||
let query = {};
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
// Admin can see all todos or filter by userId
|
||||
if (userId) {
|
||||
query.userId = userId;
|
||||
}
|
||||
} else {
|
||||
// Regular users only see their assigned tasks
|
||||
query.userId = req.user._id;
|
||||
}
|
||||
|
||||
if (date) {
|
||||
const startDate = new Date(date);
|
||||
const endDate = new Date(date);
|
||||
endDate.setDate(endDate.getDate() + 1);
|
||||
|
||||
query.dueDate = {
|
||||
$gte: startDate,
|
||||
$lt: endDate
|
||||
};
|
||||
}
|
||||
|
||||
const todos = await Todo.find(query)
|
||||
.populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email')
|
||||
.sort({ createdAt: -1 });
|
||||
|
||||
res.json(todos);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Create new todo - only admins can assign tasks to others
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { title, description, priority, dueDate, userId } = req.body;
|
||||
|
||||
let todoData = {
|
||||
title,
|
||||
description,
|
||||
priority,
|
||||
dueDate: new Date(dueDate)
|
||||
};
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
// Admin can assign tasks to any user
|
||||
todoData.userId = userId || req.user._id;
|
||||
todoData.assignedBy = req.user._id;
|
||||
} else {
|
||||
// Regular users can only create tasks for themselves
|
||||
todoData.userId = req.user._id;
|
||||
}
|
||||
|
||||
const todo = new Todo(todoData);
|
||||
await todo.save();
|
||||
|
||||
const populatedTodo = await Todo.findById(todo._id)
|
||||
.populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email');
|
||||
|
||||
res.status(201).json(populatedTodo);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Update todo - different permissions for admin vs user
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updates = req.body;
|
||||
|
||||
console.log('Update request:', { id, updates, userRole: req.user.role });
|
||||
|
||||
let query = { _id: id };
|
||||
let finalUpdates = { ...updates };
|
||||
|
||||
if (req.user.role === 'admin') {
|
||||
// Admin can update any todo
|
||||
console.log('Admin updating todo');
|
||||
} else {
|
||||
// Regular users can only update their own todos
|
||||
query.userId = req.user._id;
|
||||
console.log('User updating own todo');
|
||||
|
||||
// Users can only update status and submit tasks
|
||||
const allowedUpdates = ['status'];
|
||||
const filteredUpdates = {};
|
||||
|
||||
allowedUpdates.forEach(field => {
|
||||
if (updates[field] !== undefined) {
|
||||
filteredUpdates[field] = updates[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Handle task submission
|
||||
if (updates.status === 'submitted') {
|
||||
filteredUpdates.submittedAt = new Date();
|
||||
}
|
||||
|
||||
finalUpdates = filteredUpdates;
|
||||
console.log('Filtered updates for user:', finalUpdates);
|
||||
}
|
||||
|
||||
const todo = await Todo.findOneAndUpdate(query, finalUpdates, { new: true })
|
||||
.populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email');
|
||||
|
||||
if (!todo) {
|
||||
console.log('Todo not found with query:', query);
|
||||
return res.status(404).json({ message: 'Todo not found or access denied' });
|
||||
}
|
||||
|
||||
console.log('Todo updated successfully:', todo);
|
||||
res.json(todo);
|
||||
} catch (error) {
|
||||
console.error('Update todo error:', error);
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete todo - only admins can delete
|
||||
router.delete('/:id', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const todo = await Todo.findByIdAndDelete(id);
|
||||
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: 'Todo not found' });
|
||||
}
|
||||
|
||||
res.json({ message: 'Todo deleted successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin routes for user management
|
||||
router.get('/admin/users', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const users = await User.find({ role: 'user' }).select('-password');
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin route to get all todos with user details
|
||||
router.get('/admin/all-todos', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const todos = await Todo.find()
|
||||
.populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email')
|
||||
.sort({ createdAt: -1 });
|
||||
|
||||
res.json(todos);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin route to assign task to user
|
||||
router.post('/admin/assign', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { title, description, priority, dueDate, userId } = req.body;
|
||||
|
||||
const todo = new Todo({
|
||||
title,
|
||||
description,
|
||||
priority,
|
||||
dueDate: new Date(dueDate),
|
||||
userId,
|
||||
assignedBy: req.user._id
|
||||
});
|
||||
|
||||
await todo.save();
|
||||
|
||||
const populatedTodo = await Todo.findById(todo._id)
|
||||
.populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email');
|
||||
|
||||
res.status(201).json(populatedTodo);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Admin route to mark task as completed
|
||||
router.put('/admin/complete/:id', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const todo = await Todo.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
status: 'completed',
|
||||
completedAt: new Date()
|
||||
},
|
||||
{ new: true }
|
||||
).populate('userId', 'username email')
|
||||
.populate('assignedBy', 'username email');
|
||||
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: 'Todo not found' });
|
||||
}
|
||||
|
||||
res.json(todo);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Server error', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
export default router;
|
||||
48
Backend/server.js
Normal file
48
Backend/server.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import todoRoutes from './routes/todos.js';
|
||||
import { authenticateToken } from './middleware/auth.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Root route
|
||||
app.get('/', (req, res) => {
|
||||
res.json({ message: 'Todo App Backend API is running!' });
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/todos', authenticateToken, todoRoutes);
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Error:', err);
|
||||
res.status(500).json({ message: 'Internal server error', error: err.message });
|
||||
});
|
||||
|
||||
// MongoDB connection
|
||||
mongoose.connect(process.env.MONGO_URL)
|
||||
.then(() => {
|
||||
console.log('✅ Connected to MongoDB');
|
||||
console.log('Database:', process.env.MONGO_URL);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('❌ MongoDB connection error:', err.message);
|
||||
console.log('Make sure MongoDB is running on your system');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on port ${PORT}`);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user