Prompt-to-PR : Ajouter un tableau de données administrateur
SOP pour ajouter un tableau de données administrateur paginé côté serveur, triable et recherchable dans Next.js en utilisant TanStack Table et une requête PostgreSQL — sans magie d'ORM.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptPostgreSQLTailwind
Les tableaux d’administration sont une tâche de codage IA courante qui produit fréquemment des implémentations entièrement côté client et non sécurisées. Ce playbook maintient la pagination et le tri côté serveur et protège explicitement la route.
1. Exigence
Ajoutez une page /admin/users accessible uniquement aux utilisateurs ayant role = "admin". Le tableau est paginé côté serveur (25 lignes), triable par colonne et recherchable par email. Les données proviennent d’une table PostgreSQL users via une requête paramétrée — pas de SQL non paramétré.
2. Premier prompt
Add an admin users table to this Next.js 15 App Router project.
Requirements:1. Create `src/app/admin/users/page.tsx` — an async Server Component. - Protect it: check the session (using the existing auth helper at src/lib/auth.ts). If no session or session.user.role !== "admin", call notFound() or redirect to /. - Read `page` (default 1), `sort` (default "createdAt"), `order` ("asc"|"desc", default "desc"), and `q` (search string) from `searchParams`. - Fetch data by calling a function `getUsers({ page, sort, order, q })` from `src/lib/queries/users.ts`. - Pass results to `<UsersTable>` client component.2. Create `src/lib/queries/users.ts` exporting `getUsers`. - Use parameterized SQL via the existing db client (Drizzle or postgres.js, whichever is in this project). - Allowed sort columns whitelist: ["email","name","createdAt","role"]. Reject any other sort value by defaulting to "createdAt". - Return `{ rows: User[], total: number }`. - Page size: 25.3. Create `src/components/admin/UsersTable.tsx` — a Client Component using TanStack Table v8. - Columns: email, name, role, createdAt (formatted), actions (Edit link). - Column headers are clickable to sort — update URL search params via useRouter/useSearchParams (do not manage sort state locally). - Render a search input that debounces 300 ms before updating the URL. - Render a pagination row: Previous / Next and "Page N of M".4. Install `@tanstack/react-table` if not already present.5. Do not implement the Edit action — leave it as a placeholder link.3. Modifications de fichiers attendues
package.json (@tanstack/react-table if missing)src/app/admin/users/page.tsx (new — protected Server Component)src/lib/queries/users.ts (new — parameterized query)src/components/admin/UsersTable.tsx (new — TanStack Table client component)4. Liste de vérification
- La page vérifie
session.user.role === "admin"côté serveur — pas de garde côté client uniquement. getUsersliste blanche des colonnes de tri autorisées avant interpolation dans SQL — aucune entrée utilisateur brute dans les noms de colonnes.- Toutes les valeurs WHERE/LIMIT/OFFSET utilisent des espaces réservés paramétrés, pas de concaténation de chaînes.
- La recherche (
q) utiliseILIKE '%' || $1 || '%'avec un paramètre, pas d’interpolation de chaîne. - TanStack Table est utilisé uniquement dans le composant
"use client"— pas d’import@tanstack/react-tabledans les composants serveurs. - Les paramètres de recherche d’URL pilotent l’état de tri/page/recherche — le bouton Retour du navigateur fonctionne correctement.
- Le compte
totalprovient d’unSELECT COUNT(*)dans la même fonction de requête — pas d’analyse complète de la table séparée de la requête paginée.
5. Commandes de test
bun dev
# Hit the page as an unauthenticated usercurl -I http://localhost:3000/admin/users# Expect: 404 or redirect, not 200
# Log in as an admin user in the browser, visit /admin/users# Confirm: 25 rows, sortable columns, search, pagination
# SQL audit — confirm no dynamic column names slip throughgrep -n "sort\|order\|column" src/lib/queries/users.ts
bun tsc --noEmitbun test6. Échecs courants
- Injection SQL via la colonne de tri — l’agent interpole
sortdirectement :`ORDER BY ${sort}`. Doit utiliser une recherche dans une liste blanche vers une chaîne de nom de colonne sécurisée. searchParamsest asynchrone dans Next.js 15+ — doit être attendu avecawait:const { page } = await searchParams.- TanStack Table s’affiche dans un composant serveur — erreur de build car il utilise
useState. Déplacez-le dans un composant"use client". - Le compte
totaldécalé d’une page —Math.ceil(total / 25)donne un nombre de pages incorrect sitotalest 0. Protection :Math.max(1, Math.ceil(total / 25)). - Vérification de rôle manquante — l’agent vérifie seulement une session, pas le rôle admin. Tout utilisateur connecté peut accéder au tableau.
7. Correctif prompt
The sort column in getUsers is interpolated directly into SQL: `ORDER BY ${sort} ${order}`This is a SQL injection risk.
Fix: create an allowlist object: const ALLOWED_SORT: Record<string, string> = { email: "users.email", name: "users.name", createdAt: "users.created_at", role: "users.role", }; const safeSort = ALLOWED_SORT[sort] ?? "users.created_at";
Then use `safeSort` in the query. The order direction must also bevalidated: only accept "asc" or "desc", default to "desc".8. Description de la PR
## Feature: Admin users table at /admin/users
- Server-side pagination (25 rows/page), sorting, and email search- Role guard: redirects non-admins server-side (no client-only check)- Parameterized SQL with sort-column whitelist — no injection surface- TanStack Table v8 client component; sort/page/search state lives in URL- `getUsers` returns `{ rows, total }` from a single query function