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; } } // ---- 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; }