feat: admin/superadmin — fix response shape, ban/unban, stats tab, role restriction
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 24s

This commit is contained in:
2026-03-15 02:30:11 +01:00
parent d69281a2e0
commit 61d8a5257d
2 changed files with 168 additions and 17 deletions

View File

@@ -99,7 +99,7 @@ router.get("/videos", async (req: Request, res: Response): Promise<void> => {
skip: (rawPage - 1) * rawLimit,
take: rawLimit,
});
res.json({ success: true, data: videos, total, page: rawPage, limit: rawLimit });
res.json({ success: true, data: { videos }, total, page: rawPage, limit: rawLimit });
} catch (err) {
logger.error("GET /admin/videos — failed to list videos", { err });
res.status(500).json({ success: false, error: "INTERNAL_ERROR" });
@@ -262,13 +262,43 @@ router.get("/users", async (req: Request, res: Response): Promise<void> => {
})(),
}));
res.json({ success: true, data, total, page: rawPage, limit: rawLimit });
res.json({ success: true, data: { users: data }, total, page: rawPage, limit: rawLimit });
} catch (err) {
logger.error("GET /admin/users — failed to list users", { err });
res.status(500).json({ success: false, error: "INTERNAL_ERROR" });
}
});
/**
* PATCH /api/admin/users/:id
* Met à jour isActive (ban / unban) d'un utilisateur.
*/
router.patch("/users/:id", async (req: Request, res: Response): Promise<void> => {
const { isActive } = req.body as { isActive?: boolean };
if (typeof isActive !== "boolean") {
res.status(400).json({ success: false, error: "INVALID_BODY" });
return;
}
try {
const repo = AppDataSource.getRepository(User);
const user = await repo.findOne({ where: { id: req.params.id } });
if (!user) {
res.status(404).json({ success: false, error: "NOT_FOUND" });
return;
}
user.isActive = isActive;
await repo.save(user);
res.json({ success: true, data: { userId: user.id, isActive: user.isActive } });
} catch (err) {
logger.error("PATCH /admin/users/:id — failed to update user", { err, id: req.params.id });
res.status(500).json({ success: false, error: "INTERNAL_ERROR" });
}
});
/**
* PATCH /api/admin/users/:id/roles
* Assigne des rôles à un utilisateur (remplace les rôles existants).
@@ -332,6 +362,28 @@ router.patch("/users/:id/roles", async (req: Request, res: Response): Promise<vo
}
});
// ---------------------------------------------------------------------------
// STATS (super_admin)
// ---------------------------------------------------------------------------
/**
* GET /api/admin/stats
* Métriques globales de la plateforme.
*/
router.get("/stats", async (_req: Request, res: Response): Promise<void> => {
try {
const [totalUsers, totalVideos, activeSubscriptions] = await Promise.all([
AppDataSource.getRepository(User).count(),
AppDataSource.getRepository(Video).count({ where: { isPublished: true } }),
AppDataSource.getRepository(UserSubscription).count({ where: { status: "active" } }),
]);
res.json({ success: true, data: { totalUsers, totalVideos, activeSubscriptions } });
} catch (err) {
logger.error("GET /admin/stats — failed", { err });
res.status(500).json({ success: false, error: "INTERNAL_ERROR" });
}
});
// ---------------------------------------------------------------------------
// SUBSCRIPTION PLANS
// ---------------------------------------------------------------------------