Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import { and, eq, inArray } from 'drizzle-orm' import { NextResponse } from 'next/server' import { db, schema } from '@/db' import { withAuth } from '@/lib/auth/withAuth' import { seedProfilePlayers } from '@/db/schema/seed-profile-players' import { getUserId } from '@/lib/viewer' /** * Find all expungeable players owned by the current user. * * Players are expungeable when `is_expungeable = true`, which is set at * creation time by debug endpoints, seed tools, and e2e tests. * * As a fallback for players created before the flag existed, also matches: * - Debug-{timestamp} name + 🐛 emoji (debug hub) * - * Test Child name suffix (e2e tests) * - Entries in seed_profile_players (seed tool) * * Only returns players owned by the requesting user (via parent_child). */ async function findCleanupCandidates(userId: string) { // Get all player IDs owned by this user const ownedRelations = await db .select({ playerId: schema.parentChild.childPlayerId }) .from(schema.parentChild) .where(eq(schema.parentChild.parentUserId, userId)) const ownedIds = ownedRelations.map((r) => r.playerId) if (ownedIds.length === 0) return [] // Find seed players (tracked in seed_profile_players) for source annotation const seedEntries = await db .select({ playerId: seedProfilePlayers.playerId, profileId: seedProfilePlayers.profileId }) .from(seedProfilePlayers) .where(inArray(seedProfilePlayers.playerId, ownedIds)) const seedPlayerIds = new Set(seedEntries.map((e) => e.playerId)) // Fetch all owned players const allOwned = await db .select({ id: schema.players.id, name: schema.players.name, emoji: schema.players.emoji, color: schema.players.color, createdAt: schema.players.createdAt, isExpungeable: schema.players.isExpungeable, }) .from(schema.players) .where(inArray(schema.players.id, ownedIds)) const candidates = allOwned.filter((p) => { // Primary: flag set at creation time if (p.isExpungeable) return true // Fallback: legacy patterns for players created before the flag existed if (seedPlayerIds.has(p.id)) return true if (p.name.startsWith('Debug-') && p.emoji === '🐛') return true if (p.name.endsWith(' Test Child')) return true return false }) // Annotate with source return candidates.map((p) => { const seedEntry = seedEntries.find((e) => e.playerId === p.id) let source: string = 'debug' if (seedEntry) source = `seed:${seedEntry.profileId}` else if (p.name.endsWith(' Test Child')) source = 'e2e' return { id: p.id, name: p.name, emoji: p.emoji, color: p.color, createdAt: p.createdAt, source, } }) } /** * GET /api/debug/cleanup * * Preview: returns the list of expungeable players that would be deleted. */ export const GET = withAuth( async () => { try { const userId = await getUserId() const candidates = await findCleanupCandidates(userId) return NextResponse.json({ players: candidates, count: candidates.length, }) } catch (error) { console.error('[debug/cleanup] Preview failed:', error) return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to preview cleanup' }, { status: 500 } ) } }, { role: 'admin' } ) /** * DELETE /api/debug/cleanup * * Deletes all expungeable players owned by the current user. * All related data (sessions, skills, enrollments, etc.) cascades automatically. */ export const DELETE = withAuth( async () => { try { const userId = await getUserId() const candidates = await findCleanupCandidates(userId) if (candidates.length === 0) { return NextResponse.json({ deleted: 0, players: [] }) } const ids = candidates.map((c) => c.id) // Delete seed_profile_players entries first (FK references players) await db.delete(seedProfilePlayers).where(inArray(seedProfilePlayers.playerId, ids)) // Delete the players (cascades to all related tables) await db.delete(schema.players).where(inArray(schema.players.id, ids)) return NextResponse.json({ deleted: candidates.length, players: candidates.map((c) => ({ id: c.id, name: c.name, source: c.source })), }) } catch (error) { console.error('[debug/cleanup] Delete failed:', error) return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to cleanup' }, { status: 500 } ) } }, { role: 'admin' } ) |