263 lines
7.7 KiB
TypeScript
263 lines
7.7 KiB
TypeScript
import express from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import bcrypt from 'bcryptjs';
|
|
import prisma from '../lib/prisma';
|
|
import { validate } from '../middleware/validate';
|
|
import { loginSchema, registerSchema, changePasswordSchema, updateProfileSchema } from '../schemas/auth';
|
|
|
|
const router = express.Router();
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'secret';
|
|
|
|
// Get Current User
|
|
router.get('/me', async (req, res) => {
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: decoded.userId },
|
|
include: { profile: true }
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
const { password: _, ...userSafe } = user;
|
|
res.json({ success: true, user: userSafe });
|
|
} catch (error) {
|
|
res.status(401).json({ error: 'Invalid token' });
|
|
}
|
|
});
|
|
|
|
// Login
|
|
router.post('/login', validate(loginSchema), async (req, res) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { email },
|
|
include: { profile: true }
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(400).json({ error: 'Invalid credentials' });
|
|
}
|
|
|
|
if (user.isBlocked) {
|
|
return res.status(403).json({ error: 'Account is blocked' });
|
|
}
|
|
|
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
if (!isMatch) {
|
|
return res.status(400).json({ error: 'Invalid credentials' });
|
|
}
|
|
|
|
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
|
|
const { password: _, ...userSafe } = user;
|
|
|
|
res.json({ success: true, user: userSafe, token });
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Register
|
|
router.post('/register', validate(registerSchema), async (req, res) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
|
|
// Check if user already exists
|
|
const existingUser = await prisma.user.findUnique({ where: { email } });
|
|
if (existingUser) {
|
|
return res.status(400).json({ error: 'User already exists' });
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email,
|
|
password: hashedPassword,
|
|
role: 'USER',
|
|
profile: {
|
|
create: {
|
|
weight: 70
|
|
}
|
|
}
|
|
},
|
|
include: { profile: true }
|
|
});
|
|
|
|
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
|
|
const { password: _, ...userSafe } = user;
|
|
|
|
res.json({ success: true, user: userSafe, token });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Change Password
|
|
router.post('/change-password', validate(changePasswordSchema), async (req, res) => {
|
|
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const { userId, newPassword } = req.body;
|
|
|
|
// Verify token
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
if (decoded.userId !== userId) {
|
|
return res.status(403).json({ error: 'Forbidden' });
|
|
}
|
|
|
|
const hashed = await bcrypt.hash(newPassword, 10);
|
|
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
password: hashed,
|
|
isFirstLogin: false
|
|
}
|
|
});
|
|
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Update Profile
|
|
router.patch('/profile', validate(updateProfileSchema), async (req, res) => {
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
// const { userId, profile } = req.body;
|
|
|
|
|
|
// Convert birthDate from timestamp to Date object if needed
|
|
if (req.body.birthDate) {
|
|
// Handle both number (timestamp) and string (ISO)
|
|
req.body.birthDate = new Date(req.body.birthDate);
|
|
}
|
|
|
|
// Verify token
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
const userId = decoded.userId;
|
|
|
|
// Update or create profile
|
|
await prisma.userProfile.upsert({
|
|
where: { userId: userId },
|
|
update: {
|
|
...req.body
|
|
},
|
|
create: {
|
|
userId: userId,
|
|
...req.body
|
|
}
|
|
});
|
|
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
|
|
// Admin: Get All Users
|
|
router.get('/users', async (req, res) => {
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
if (decoded.role !== 'ADMIN') {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
|
|
const users = await prisma.user.findMany({
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
role: true,
|
|
isBlocked: true,
|
|
isFirstLogin: true,
|
|
profile: true
|
|
}
|
|
});
|
|
|
|
res.json({ success: true, users });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Admin: Delete User
|
|
router.delete('/users/:id', async (req, res) => {
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
if (decoded.role !== 'ADMIN') {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
|
|
// Prevent deleting self
|
|
if (id === decoded.userId) {
|
|
return res.status(400).json({ error: 'Cannot delete yourself' });
|
|
}
|
|
|
|
await prisma.user.delete({ where: { id } });
|
|
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Admin: Toggle Block User
|
|
router.patch('/users/:id/block', async (req, res) => {
|
|
try {
|
|
const token = req.headers.authorization?.split(' ')[1];
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
|
if (decoded.role !== 'ADMIN') {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
|
|
const { id } = req.params;
|
|
const { block } = req.body;
|
|
|
|
// Prevent blocking self
|
|
if (id === decoded.userId) {
|
|
return res.status(400).json({ error: 'Cannot block yourself' });
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id },
|
|
data: { isBlocked: block }
|
|
});
|
|
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
export default router;
|