import type { Route } from '@playwright/test'; import type { ContentState } from './contentSeed'; type JsonFn = ( route: Route, status: number, body: unknown, headers?: Record ) => Promise; function listSlice(items: T[], url: URL): { rows: T[]; total: number } { const limit = Number(url.searchParams.get('limit') || 50); const offset = Number(url.searchParams.get('offset') || 0); const q = (url.searchParams.get('q') || '').toLowerCase(); let rows = items; if (q) { rows = items.filter((item) => JSON.stringify(item).toLowerCase().includes(q)); } const status = url.searchParams.get('status'); if (status && rows.length && typeof (rows[0] as { status?: string }).status === 'string') { rows = rows.filter((item) => (item as { status?: string }).status === status); } const plan = url.searchParams.get('plan'); if (plan) { rows = rows.filter((item) => (item as { plan?: string }).plan === plan); } const type = url.searchParams.get('type'); if (type) { rows = rows.filter((item) => (item as { type?: string }).type === type); } return { rows: rows.slice(offset, offset + limit), total: rows.length }; } /** Returns true if the request was handled. */ export async function tryHandleContentApi( route: Route, method: string, path: string, url: URL, content: ContentState, json: JsonFn, postData: () => unknown ): Promise { // ---- users ---- if (method === 'GET' && path.endsWith('/v1/admin/users/stats')) { await json(route, 200, { total_users: content.users.length, users_by_role: {}, users_by_status: {}, pending_users: 0, }); return true; } if (method === 'GET' && path.endsWith('/v1/admin/users')) { const page = listSlice(content.users, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } { const m = path.match(/\/v1\/admin\/users\/([^/]+)$/); if (m) { const id = decodeURIComponent(m[1]); const idx = content.users.findIndex((u) => u.id === id); if (idx < 0) { await json(route, 404, { error: 'not_found' }); return true; } if (method === 'GET') { await json(route, 200, content.users[idx]); return true; } if (method === 'PUT') { const patch = (postData() || {}) as Record; delete patch.id; content.users[idx] = { ...content.users[idx], ...patch, id }; await json(route, 200, content.users[idx]); return true; } if (method === 'DELETE') { content.users.splice(idx, 1); await json(route, 200, { ok: true }); return true; } } } // ---- calendars ---- if (method === 'GET' && path.endsWith('/v1/admin/calendars/stats')) { await json(route, 200, { total_calendars: content.calendars.length, calendars_by_type: {}, calendars_by_status: {}, top_calendars_by_reviews: [], top_calendars_by_positive_reviews: [], top_calendars_by_negative_reviews: [], top_calendars_by_rating: [], }); return true; } if (method === 'GET' && path.endsWith('/v1/admin/calendars')) { const page = listSlice(content.calendars, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } { const m = path.match(/\/v1\/admin\/calendars\/([^/]+)$/); if (m) { const id = decodeURIComponent(m[1]); const idx = content.calendars.findIndex((c) => c.id === id); if (idx < 0) { await json(route, 404, { error: 'not_found' }); return true; } if (method === 'GET') { await json(route, 200, content.calendars[idx]); return true; } if (method === 'PUT') { const patch = (postData() || {}) as Record; delete patch.id; content.calendars[idx] = { ...content.calendars[idx], ...patch, id }; await json(route, 200, content.calendars[idx]); return true; } if (method === 'DELETE') { content.calendars.splice(idx, 1); await json(route, 200, { ok: true }); return true; } } } // ---- events ---- if (method === 'GET' && path.endsWith('/v1/admin/events/stats')) { await json(route, 200, { total_events: content.events.length, events_by_type: {}, events_by_status: {}, top_events_by_rating: [], }); return true; } if (method === 'GET' && path.endsWith('/v1/admin/events')) { const page = listSlice(content.events, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } { const m = path.match(/\/v1\/admin\/events\/([^/]+)$/); if (m) { const id = decodeURIComponent(m[1]); const idx = content.events.findIndex((e) => e.id === id); if (idx < 0) { await json(route, 404, { error: 'not_found' }); return true; } if (method === 'GET') { await json(route, 200, content.events[idx]); return true; } if (method === 'PUT') { const patch = (postData() || {}) as Record; delete patch.id; content.events[idx] = { ...content.events[idx], ...patch, id }; await json(route, 200, content.events[idx]); return true; } if (method === 'DELETE') { content.events.splice(idx, 1); await json(route, 200, { ok: true }); return true; } } } // ---- subscriptions ---- if (method === 'GET' && path.endsWith('/v1/admin/subscriptions/stats')) { await json(route, 200, { total_subscriptions: content.subscriptions.length, subscriptions_by_plan: {}, subscriptions_by_status: {}, trial_subscriptions: 0, ending_paid_subscriptions: [], }); return true; } if (method === 'GET' && path.endsWith('/v1/admin/subscriptions')) { const page = listSlice(content.subscriptions, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } { const m = path.match(/\/v1\/admin\/subscriptions\/([^/]+)$/); if (m) { const id = decodeURIComponent(m[1]); const idx = content.subscriptions.findIndex((s) => s.id === id); if (idx < 0) { await json(route, 404, { error: 'not_found' }); return true; } if (method === 'GET') { await json(route, 200, content.subscriptions[idx]); return true; } if (method === 'PUT') { const patch = (postData() || {}) as Record; delete patch.id; content.subscriptions[idx] = { ...content.subscriptions[idx], ...patch, id }; await json(route, 200, content.subscriptions[idx]); return true; } if (method === 'DELETE') { content.subscriptions.splice(idx, 1); await json(route, 200, { ok: true }); return true; } } } // ---- banned words ---- if (method === 'GET' && path.endsWith('/v1/admin/banned-words')) { const page = listSlice( content.bannedWords.map((w) => ({ ...w, id: w.word })), url ); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } if (method === 'POST' && path.endsWith('/v1/admin/banned-words')) { const payload = postData() as { word?: string }; const word = (payload.word || '').trim(); if (word) content.bannedWords.push({ word, added_by: 'admin-e2e-1', added_at: '2026-07-14T12:00:00Z' }); await json(route, 200, { ok: true }); return true; } { const m = path.match(/\/v1\/admin\/banned-words\/([^/]+)$/); if (m && method === 'DELETE') { const word = decodeURIComponent(m[1]); content.bannedWords = content.bannedWords.filter((w) => w.word !== word); await json(route, 200, { ok: true }); return true; } } // ---- automod ---- if (!('_automod' in content)) { (content as { _automod?: { settings: Record; hits: Record[] } })._automod = { settings: { keyword_action: 'flag', match_mode: 'word_boundary', report_threshold: 3, updated_at: '2026-07-14T12:00:00Z', updated_by: 'admin-e2e-1', }, hits: [ { id: 'hit-1', entity_type: 'event', entity_id: 'evt-1', trigger: 'keyword', matched_words: ['spam'], action_taken: 'flag', status: 'open', created_at: '2026-07-14T12:00:00Z', resolved_at: null, resolved_by: '', }, ], }; } const automod = (content as { _automod: { settings: Record; hits: Record[] } })._automod; if (method === 'GET' && path.endsWith('/v1/admin/automod/settings')) { await json(route, 200, automod.settings); return true; } if (method === 'PUT' && path.endsWith('/v1/admin/automod/settings')) { const payload = postData() as Record; automod.settings = { ...automod.settings, ...payload }; await json(route, 200, automod.settings); return true; } if (method === 'GET' && path.endsWith('/v1/admin/automod/hits')) { const status = url.searchParams.get('status'); const rows = status ? automod.hits.filter((h) => h.status === status) : automod.hits; await json(route, 200, rows); return true; } { const m = path.match(/\/v1\/admin\/automod\/hits\/([^/]+)$/); if (m && method === 'PUT') { const id = decodeURIComponent(m[1]); const payload = postData() as { status?: string }; const idx = automod.hits.findIndex((h) => h.id === id); if (idx >= 0 && payload.status) { automod.hits[idx] = { ...automod.hits[idx], status: payload.status, resolved_at: '2026-07-14T13:00:00Z', resolved_by: 'admin-e2e-1', }; await json(route, 200, automod.hits[idx]); return true; } await json(route, 404, { error: 'not found' }); return true; } } // ---- admins ---- if (method === 'GET' && path.endsWith('/v1/admin/admins')) { const page = listSlice(content.admins, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } if (method === 'POST' && path.endsWith('/v1/admin/admins')) { const payload = postData() as Record; const created = { id: `admin-new-${content.admins.length + 1}`, email: String(payload.email || 'new@eventhub.local'), nickname: String(payload.nickname || 'New Admin'), role: String(payload.role || 'admin'), language: 'ru', phone: null, avatar_url: null, timezone: 'UTC', created_at: '2026-07-14T12:00:00Z', updated_at: '2026-07-14T12:00:00Z', last_login: null, }; content.admins.push(created); await json(route, 200, created); return true; } { const m = path.match(/\/v1\/admin\/admins\/([^/]+)$/); if (m) { const id = decodeURIComponent(m[1]); const idx = content.admins.findIndex((a) => a.id === id); if (idx < 0) { await json(route, 404, { error: 'not_found' }); return true; } if (method === 'GET') { await json(route, 200, content.admins[idx]); return true; } if (method === 'PUT') { content.admins[idx] = { ...content.admins[idx], ...(postData() as object) }; await json(route, 200, content.admins[idx]); return true; } if (method === 'DELETE') { content.admins.splice(idx, 1); await json(route, 200, { ok: true }); return true; } } } // ---- audit ---- if (method === 'GET' && path.endsWith('/v1/admin/audit')) { const page = listSlice(content.audit, url); await json(route, 200, page.rows, { 'X-Total-Count': String(page.total) }); return true; } // ---- monitoring ---- if (method === 'GET' && path.includes('/v1/admin/nodes/metrics')) { await json(route, 200, [ { active_sessions: 3, io_in: 10, io_out: 20, memory_atom: 1, memory_binary: 2, memory_ets: 3, memory_processes: 4, memory_total: 2048, mnesia_commits: 1, mnesia_failures: 0, node: 'node-a', process_count: 100, run_queue: 0, table_sizes: { users: 10 }, timestamp: '2026-07-14T12:00:00Z', ws_connections: 5, cpu_utilization: 12, memory_available: 1024, }, ]); return true; } return false; }