Files
gymflow/server/src/routes/auth.ts
2025-12-09 18:11:55 +02:00

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;