chahinebrini 68fe8afab2 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:47:05 +02:00

63 lines
1.5 KiB
TypeScript

// apps/admin/server/api/moderation/queue.get.ts
//
// Server-side proxy: holt die Moderation-Queue (gemeldete Posts + Comments)
// vom Backend. Admin-Secret bleibt server-only.
//
// Query-Forwarding: cursor + limit werden an Backend durchgereicht.
export interface ModerationItem {
id: string;
type: "post" | "comment";
content: string;
postId: string | null;
userId: string;
reportedAt: string | null;
createdAt: string;
isDeleted: boolean;
author: {
id: string;
nickname: string | null;
avatar: string | null;
plan: string;
} | null;
}
export interface ModerationQueueResponse {
items: ModerationItem[];
nextCursor: string | null;
}
export default defineEventHandler(
async (event): Promise<ModerationQueueResponse> => {
const config = useRuntimeConfig();
const apiBase = config.public.apiBase;
const adminSecret = config.adminSecret;
if (!adminSecret) {
throw createError({
statusCode: 500,
statusMessage: "ADMIN_SECRET nicht konfiguriert (Infisical-Var fehlt)",
});
}
const query = getQuery(event);
try {
return await $fetch<ModerationQueueResponse>(
`${apiBase}/api/admin/moderation/queue`,
{
method: "GET",
headers: { "x-admin-secret": adminSecret },
query,
},
);
} catch (err: any) {
throw createError({
statusCode: err?.statusCode ?? 502,
statusMessage:
err?.statusMessage ?? err?.message ?? "Backend-Request fehlgeschlagen",
});
}
},
);