chahinebrini 3bc5360832 feat(admin): Phase 2 Frontend — Domains/Stats/Users/Moderation pages + responsive layout
4 page-implementations + server-route-proxies (admin-secret stays server-only):

DOMAINS (apps/admin/pages/domains.vue):
- UTable mit pending-submissions queue
- Approve / Reject buttons per row
- Reject-confirm-modal mit optional note
- useToast + refresh nach action
- 3 server-routes: GET list + POST approve/reject

STATS (apps/admin/pages/stats.vue):
- Stat-cards: Total Users + delta-week, Total Posts + delta-week,
  Domains pending (link to /domains), Domains approved, Feedback pending,
  Lyra-Posts (30d)
- UProgress für Domain-Approval-Quote
- Auto-refresh 60s + manual refresh-button
- USkeleton während loading
- 1 server-route: GET /api/stats

USERS (apps/admin/pages/users.vue):
- UTable mit avatar+nickname/username, plan-badge, streak, status, createdAt
- Search-input + plan-filter dropdown
- Action-dropdown per row: Plan-Change / Ban-Toggle / Soft-Delete
- 3 separate UModals mit confirm-pattern
- Cursor-pagination (Mehr laden button)
- 3 server-routes: GET list, PATCH /:id, DELETE /:id

MODERATION (apps/admin/pages/moderation.vue):
- Stack-layout mit card-pro-item (statt table — content-preview braucht space)
- Type-badge (Post/Comment), Author + Plan-badge, content-preview (200 chars),
  reportedAt
- Action-buttons: Dismiss (gray), Delete Content (red soft + reason-modal),
  Ban User (red solid + warning-modal)
- Empty-state, cursor-pagination
- 4 server-routes: GET /queue, POST /:id/dismiss/delete/ban-user

Server-route pattern (apps/admin/server/api/...):
- Use useRuntimeConfig().adminSecret server-only
- Client never sees x-admin-secret
- Body/query passthrough to backend

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:48:35 +02:00

40 lines
1.1 KiB
TypeScript

// apps/admin/server/api/moderation/[id]/delete.post.ts
//
// Proxy: leitet Soft-Delete-Request ans Backend (content="", isDeleted=true).
// Original-Content + reporter-info bleiben in moderation_actions (audit-log).
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const apiBase = config.public.apiBase;
const adminSecret = config.adminSecret;
if (!adminSecret) {
throw createError({
statusCode: 500,
statusMessage: "ADMIN_SECRET nicht konfiguriert",
});
}
const id = getRouterParam(event, "id");
if (!id) throw createError({ statusCode: 400, statusMessage: "ID fehlt" });
const body = await readBody(event).catch(() => ({}));
try {
return await $fetch(
`${apiBase}/api/admin/moderation/${encodeURIComponent(id)}/delete`,
{
method: "POST",
headers: { "x-admin-secret": adminSecret },
body: body ?? {},
},
);
} catch (err: any) {
throw createError({
statusCode: err?.statusCode ?? 502,
statusMessage:
err?.statusMessage ?? err?.message ?? "Backend-Request fehlgeschlagen",
});
}
});