All Tests Work! Password reset implemented. Users list sorted.

This commit is contained in:
AG
2025-12-10 12:08:11 +02:00
parent bc9b685dec
commit 5597d45e48
16 changed files with 1033 additions and 39 deletions

34
server/create_admin.cjs Normal file
View File

@@ -0,0 +1,34 @@
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs');
(async () => {
const prisma = new PrismaClient();
try {
const email = 'admin@gymflow.com';
const password = 'admin123';
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.upsert({
where: { email },
update: {},
create: {
email,
password: hashedPassword,
role: 'ADMIN',
isFirstLogin: false,
profile: {
create: {
weight: 80,
height: 180,
gender: 'MALE'
}
}
}
});
console.log('Admin user created/verified:', user);
} catch (e) {
console.error('Error:', e);
} finally {
await prisma.$disconnect();
}
})();

Binary file not shown.

Binary file not shown.

25
server/promote_admin.ts Normal file
View File

@@ -0,0 +1,25 @@
import prisma from './src/lib/prisma';
(async () => {
try {
const email = process.argv[2];
if (!email) {
console.error('Please provide email');
process.exit(1);
}
await prisma.user.update({
where: { email },
data: {
role: 'ADMIN'
}
});
console.log(`User ${email} promoted to ADMIN`);
} catch (e) {
console.error('Error:', e);
process.exit(1);
} finally {
await prisma.$disconnect();
}
})();

View File

@@ -191,6 +191,9 @@ router.get('/users', async (req, res) => {
isBlocked: true,
isFirstLogin: true,
profile: true
},
orderBy: {
email: 'asc'
}
});
@@ -259,4 +262,39 @@ router.patch('/users/:id/block', async (req, res) => {
}
});
// Admin: Reset User Password
router.post('/users/:id/reset-password', 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 { newPassword } = req.body;
if (!newPassword || newPassword.length < 4) {
return res.status(400).json({ error: 'Password too short' });
}
const hashed = await bcrypt.hash(newPassword, 10);
await prisma.user.update({
where: { id },
data: {
password: hashed,
isFirstLogin: true // Force them to change it on login
}
});
res.json({ success: true });
} catch (error) {
console.error('Reset password error:', error);
res.status(500).json({ error: 'Server error' });
}
});
export default router;

View File

@@ -0,0 +1,27 @@
import Database from 'better-sqlite3';
// Use DATABASE_URL if provided, else default
// DATABASE_URL from Prisma usually starts with 'file:'
let dbPath = process.env.DATABASE_URL || './prisma/test.db';
if (dbPath.startsWith('file:')) {
dbPath = dbPath.slice(5);
}
console.log(`Resetting DB at ${dbPath}`);
try {
const db = new Database(dbPath);
// Enable Foreign Keys to ensure cascading deletes work
db.pragma('foreign_keys = ON');
// Optional: WAL mode typically used in app
// db.pragma('journal_mode = WAL');
const info = db.prepare('DELETE FROM User').run();
console.log(`Deleted ${info.changes} users.`);
db.close();
} catch (error) {
console.error('Failed to reset DB:', error);
process.exit(1);
}