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>
63 lines
1.5 KiB
TypeScript
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",
|
|
});
|
|
}
|
|
},
|
|
);
|