Files
obc-terrassement/lib/admin.ts
Claude 1d0bd349fd feat: secure admin panel with Supabase auth + course management CRUD
- Replace ADMIN_SECRET query param with proper Supabase auth + is_admin flag
- Add admin layout with auth check (redirects non-admin to /)
- Add AdminShell component with sidebar navigation (Dashboard, Candidatures, Cours)
- Add admin dashboard with stats (candidatures, users, modules)
- Add admin candidatures page with filters and approve/reject
- Add admin course management page (create, edit, delete, publish/unpublish)
- Add API routes: GET/POST /api/admin/modules, GET/PUT/DELETE /api/admin/modules/[id]
- Add verifyAdmin() helper for API route protection
- Update database types with is_admin on profiles

https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
2026-02-10 13:25:58 +00:00

41 lines
1.2 KiB
TypeScript

import { createClient, createAdminClient } from "@/lib/supabase/server";
import type { Profile } from "@/types/database.types";
// Vérifie que l'utilisateur connecté est admin
// Utilisé dans les API routes admin
export async function verifyAdmin(): Promise<{ admin: Profile } | { error: string; status: number }> {
const supabase = await createClient();
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();
if (authError || !user) {
return { error: "Non authentifié.", status: 401 };
}
// Utiliser le client admin pour lire le profil (pas de RLS)
const adminClient = createAdminClient();
const { data: profile, error: profileError } = await adminClient
.from("profiles")
.select("*")
.eq("id", user.id)
.single();
if (profileError || !profile) {
return { error: "Profil introuvable.", status: 404 };
}
if (!(profile as Profile).is_admin) {
return { error: "Accès refusé.", status: 403 };
}
return { admin: profile as Profile };
}
// Helper pour répondre avec erreur si non-admin
export function isAdminError(result: { admin: Profile } | { error: string; status: number }): result is { error: string; status: number } {
return "error" in result;
}