Prompt-to-PR: Admin-Datentabelle hinzufügen
SOP zum Hinzufügen einer serverseitig paginierten, sortierbaren, durchsuchbaren Admin-Datentabelle in Next.js mit TanStack Table und einer PostgreSQL-Abfrage — kein ORM-Zauber.
CursorClaude CodeCodexWindsurf Next.jsTypeScriptPostgreSQLTailwind
Admin-Tabellen sind eine häufige KI-Codierungsaufgabe, die oft unsichere, rein clientseitige Implementierungen hervorbringt. Dieses Playbook hält Paginierung und Sortierung serverseitig und schützt die Route explizit.
1. Anforderung
Füge eine /admin/users-Seite hinzu, die nur für Benutzer mit role = "admin" zugänglich ist. Die Tabelle ist serverseitig paginiert (25 Zeilen), nach Spalten sortierbar und nach E-Mail durchsuchbar. Die Daten stammen aus einer PostgreSQL-users-Tabelle über eine parametrisierte Abfrage — kein unparametrisiertes SQL.
2. Erster 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. Erwartete Dateiänderungen
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. Review-Checkliste
- Die Seite prüft serverseitig
session.user.role === "admin"— kein reiner Client-Guard. getUsersverwendet eine Whitelist zulässiger Sortierspalten, bevor sie in SQL eingefügt werden — keine rohen Benutzereingaben in Spaltennamen.- Alle WHERE/LIMIT/OFFSET-Werte verwenden parametrisierte Platzhalter, keine String-Verkettung.
- Die Suche (
q) verwendetILIKE '%' || $1 || '%'mit einem Parameter, keine String-Interpolation. - TanStack Table wird nur in der
"use client"-Komponente verwendet — kein@tanstack/react-table-Import in Server Components. - URL-Suchparameter steuern den Sortier-/Seiten-/Suchstatus — der Zurück-Button des Browsers funktioniert korrekt.
- Der
total-Wert stammt aus einemSELECT COUNT(*)in derselben Abfragefunktion — kein vollständiger Tabellen-Scan getrennt von der paginierten Abfrage.
5. Test-Befehle
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. Häufige Fehler
- SQL-Injection über Sortierspalte — der Agent interpoliert
sortdirekt:`ORDER BY ${sort}`. Es muss eine Whitelist-Lookup auf einen sicheren Spaltennamen-String verwendet werden. searchParamsist asynchron in Next.js 15+ — muss mitawaitaufgerufen werden:const { page } = await searchParams.- TanStack Table rendert in einer Server-Komponente — Build-Fehler, weil es
useStateverwendet. Verschiebe es in eine"use client"-Komponente. totalist um eine Seite verschoben —Math.ceil(total / 25)ergibt eine falsche Seitenzahl, wenntotal0 ist. Absicherung:Math.max(1, Math.ceil(total / 25)).- Rollenprüfung fehlt — der Agent prüft nur auf eine Session, nicht auf die Admin-Rolle. Jeder eingeloggte Benutzer kann auf die Tabelle zugreifen.
7. Fix-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. PR-Beschreibung
## 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