Two parallel agent-batches consolidated: USERS-MGMT (rebreak-backend agent): - Schema: Profile gets banned, bannedAt, bannedReason, deletedAt + indexes - Migration: 20260509_profile_admin_management (additive, idempotent) - DB-layer backend/server/db/adminUsers.ts: listAdminUsers (cursor-pagination, search, plan-filter) updateAdminUser (plan-validation, ban-stamping) softDeleteAdminUser (DSGVO PII-scrub: nickname=null, email=deleted-{shortid}@deleted.local) - 3 endpoints under /api/admin/users: GET (list with ?cursor&limit&q&plan&includeDeleted) PATCH /:id (plan/banned/bannedReason/lyraVoiceId) DELETE /:id (soft-delete idempotent) - 12 tests passing MODERATION (rebreak-backend agent): - Schema: CommunityPost+CommunityReply get isModerated, isDeleted, deletedAt, reportedAt + index (is_moderated, reported_at) - New ModerationAction model → audit-log table - Migration: 20260509_moderation_queue (additive, idempotent) - DB-layer backend/server/db/moderation.ts: listModerationQueue (merge posts+comments, sort by reportedAt, cursor) dismissModerationItem deleteModerationItem (content scrub + audit snapshot) banUserFromModerationItem (reuses banned/bannedAt/bannedReason fields) - 4 endpoints under /api/admin/moderation: GET /queue, POST /:id/dismiss, POST /:id/delete, POST /:id/ban-user - 11 tests passing Backend total: 78 tests passing | 4 skipped (pre-existing requireAdmin tests) Auth: x-admin-secret header (consistent with existing /admin/* endpoints). DSGVO: - Soft-delete scrubt PII statt hard-delete - Email NICHT in admin user-list (lebt nur in auth.users) - Audit-log für moderation-actions (90-day cleanup-cron pending hans-mueller-DSB-review) ⚠️ MIGRATIONS — auto-deploy via pipeline (commit b38bf17 detection): - 20260509_profile_admin_management - 20260509_moderation_queue Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { listAdminUsers } from "../../../db/adminUsers";
|
|
|
|
/**
|
|
* GET /api/admin/users — Admin-User-Liste (cursor-paginated, search, plan-filter)
|
|
*
|
|
* Query-Params:
|
|
* ?cursor=<id> — pagination cursor (id from previous nextCursor)
|
|
* ?limit=50 — max 100
|
|
* ?q=<search> — fuzzy-match auf nickname + username (case-insensitive)
|
|
* ?plan=free|pro|legend — filter
|
|
* ?includeDeleted=1 — auch soft-deleted users zurückgeben
|
|
*
|
|
* Auth: x-admin-secret Header (gleicher Pattern wie /api/admin/stats).
|
|
*
|
|
* DSGVO-Note: Email lebt in supabase.auth.users — bewusst NICHT joinen,
|
|
* Admin-UI zeigt nur Nickname/Username (siehe memory/feedback_anonymity_nickname).
|
|
* Wenn Email für DSGVO-Auskunft nötig ist → separater Endpoint mit
|
|
* zusätzlicher Bestätigung (Phase F, hans-mueller).
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const config = useRuntimeConfig();
|
|
const secret = getHeader(event, "x-admin-secret");
|
|
if (!config.adminSecret || secret !== config.adminSecret) {
|
|
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
}
|
|
|
|
const query = getQuery(event);
|
|
const limitNum = query.limit ? Number(query.limit) : undefined;
|
|
const planRaw = typeof query.plan === "string" ? query.plan : undefined;
|
|
const plan =
|
|
planRaw === "free" || planRaw === "pro" || planRaw === "legend"
|
|
? planRaw
|
|
: undefined;
|
|
|
|
return listAdminUsers({
|
|
cursor: typeof query.cursor === "string" ? query.cursor : undefined,
|
|
limit: Number.isFinite(limitNum) ? limitNum : undefined,
|
|
q: typeof query.q === "string" ? query.q : undefined,
|
|
plan,
|
|
includeDeleted:
|
|
query.includeDeleted === "1" || query.includeDeleted === "true",
|
|
});
|
|
});
|