diff --git a/backend/src/routes/admin.routes.ts b/backend/src/routes/admin.routes.ts index f59a0d1..340f7fa 100644 --- a/backend/src/routes/admin.routes.ts +++ b/backend/src/routes/admin.routes.ts @@ -99,7 +99,7 @@ router.get("/videos", async (req: Request, res: Response): Promise => { 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 => { })(), })); - 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 => { + 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 => { + 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 // --------------------------------------------------------------------------- diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index e57c965..d671aec 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -41,21 +41,32 @@ interface AdminUser { } | null; } +interface Stats { + totalUsers: number; + totalVideos: number; + activeSubscriptions: number; +} + // ── Tabs ───────────────────────────────────────────────────────────────────── -type Tab = 'videos' | 'users' | 'plans'; +type Tab = 'videos' | 'users' | 'plans' | 'system'; export default function AdminPage() { const { user, loading: authLoading } = useAuthContext(); + const isSuperAdmin = user?.roles?.includes('super_admin') ?? false; const [tab, setTab] = useState('videos'); if (authLoading) return null; if (!user?.roles?.some((r) => r === 'admin' || r === 'super_admin')) return ; + const tabs: Tab[] = isSuperAdmin + ? ['videos', 'users', 'plans', 'system'] + : ['videos', 'users', 'plans']; + return (
- {(['videos', 'users', 'plans'] as Tab[]).map((t) => ( + {tabs.map((t) => (
{tab === 'videos' && } - {tab === 'users' && } + {tab === 'users' && } {tab === 'plans' && } + {tab === 'system' && isSuperAdmin && }
); } @@ -166,7 +178,6 @@ function VideosTab() { return (
- {/* Formulaire création */}

Nouvelle vidéo

@@ -219,8 +230,7 @@ function VideosTab() {