Close the web-feasible 100% gaps: REST write/token API, tickets, draw-badge, /me, sanitisation, dusk, radio SSE
Final parity push (web-tier only): - REST API write + token auth: POST /api/tokens (issue a personal_access_token for the session user), Bearer auth via src/lib/api-auth.ts, POST /api/articles/[slug]/comment, GET/DELETE /api/me/tokens, full tickets API (/api/tickets +[id] +[id]/reply), radio current-dj/points/points-leaderboard/ embed-config + POST shouts, and a real-time /api/radio/stream (SSE). 31 public API routes total. - Pages: /draw-badge (buy a custom profile badge → credits + RCON), /me dashboard (stats + online friends + referral claim). Wired into the nav. - HTML sanitisation (sanitize-html) — the HTMLPurifier equivalent — applied to writeable boxes + article bodies before dangerouslySetInnerHTML. - "Dusk" dark theme preset + a default-dark site option honoured by the no-flash boot script. Verified live (prod, amx_test): token issue → Bearer endpoint 200, no-token 401; /api/me/tokens lists it; current-dj/leaderboard JSON; /me + /draw-badge 200; reverted the test user + tokens. tsc 0, vitest 49/49, next build 0.
This commit is contained in:
1 parent
8cedf5614e
commit
f7b3845131
30 files changed
+1734
-10
No files matched your search
@@ -0,0 +1,35 @@
|
||||
// Public REST API — issue a personal access token for the SIGNED-IN user.
|
||||
//
|
||||
// POST /api/tokens — mints a new Sanctum-style personal_access_token bound to
|
||||
// the NextAuth-authenticated user and returns the plaintext ONCE. The plaintext
|
||||
// is never stored (only its sha256 hash lives in the DB) so it cannot be shown
|
||||
// again. Requires a logged-in web session, not a Bearer token.
|
||||
|
||||
import { apiError, apiJson } from "@/lib/api";
|
||||
import { issueToken } from "@/lib/api-auth";
|
||||
import { auth } from "@/lib/auth";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
const id = session?.user?.id ? Number(session.user.id) : null;
|
||||
if (!id || Number.isNaN(id)) {
|
||||
return apiError("Unauthorized", 401);
|
||||
}
|
||||
|
||||
const body = (await req.json().catch(() => ({}))) as { name?: unknown };
|
||||
const rawName = typeof body.name === "string" ? body.name.trim() : "";
|
||||
const name = rawName ? rawName.slice(0, 100) : "api";
|
||||
|
||||
try {
|
||||
const token = await issueToken(id, name);
|
||||
if (!token) {
|
||||
return apiError("Could not issue token", 500);
|
||||
}
|
||||
// Plaintext token — shown only once, never recoverable afterwards.
|
||||
return apiJson({ token });
|
||||
} catch {
|
||||
return apiError("Could not issue token", 500);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user