All files / web/src/app/api/players route.ts

0% Statements 0/128
0% Branches 0/1
0% Functions 0/1
0% Lines 0/128

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                                                                                                                                                                                                                                                                 
import { and, eq, inArray } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { db, schema } from '@/db'
import { generateFamilyCode, parentChild } from '@/db/schema'
import { withAuth } from '@/lib/auth/withAuth'
import { getParentedPlayerIds } from '@/lib/classroom/access-control'
import { getLimitsForUser } from '@/lib/subscription'
import { getUserId } from '@/lib/viewer'
import { normalizeBirthdayInput } from '@/lib/playerAge'

/**
 * GET /api/players
 * List all players for the current viewer (guest or user)
 * Includes both created players and linked children via parent_child
 *
 * Guest sharing expiry is handled centrally by getParentedPlayerIds().
 */
export const GET = withAuth(async () => {
  try {
    const userId = await getUserId()

    // Get all player IDs the user has parent access to (owned + linked, with guest expiry)
    const parentedIds = await getParentedPlayerIds(userId)

    // Get all players the user can access
    const players =
      parentedIds.length > 0
        ? await db.query.players.findMany({
            where: inArray(schema.players.id, parentedIds),
            orderBy: (players, { desc }) => [desc(players.createdAt)],
          })
        : []

    return NextResponse.json({ players })
  } catch (error) {
    console.error('Failed to fetch players:', error)
    return NextResponse.json({ error: 'Failed to fetch players' }, { status: 500 })
  }
})

/**
 * POST /api/players
 * Create a new player for the current viewer
 */
export const POST = withAuth(async (request) => {
  try {
    const userId = await getUserId()
    const body = await request.json()

    // Validate required fields
    if (!body.name || !body.emoji || !body.color) {
      return NextResponse.json(
        { error: 'Missing required fields: name, emoji, color' },
        { status: 400 }
      )
    }

    const isPracticeStudent = body.isPracticeStudent ?? true

    // Enforce practice student limit (arcade players are unlimited)
    if (isPracticeStudent) {
      const limits = await getLimitsForUser(userId)
      if (limits.maxPracticeStudents !== Infinity) {
        const practiceStudentCount = await db
          .select({ id: schema.players.id })
          .from(schema.players)
          .where(
            and(
              eq(schema.players.userId, userId),
              eq(schema.players.isArchived, false),
              eq(schema.players.isPracticeStudent, true)
            )
          )
          .all()
        if (practiceStudentCount.length >= limits.maxPracticeStudents) {
          return NextResponse.json(
            {
              error: 'Practice student limit reached',
              code: 'PRACTICE_STUDENT_LIMIT_REACHED',
              limit: limits.maxPracticeStudents,
            },
            { status: 403 }
          )
        }
      }
    }

    let normalizedBirthday: string | null | undefined
    if (body.birthday === null) {
      normalizedBirthday = null
    } else if (typeof body.birthday === 'string') {
      normalizedBirthday = normalizeBirthdayInput(body.birthday)
      if (normalizedBirthday === null) {
        return NextResponse.json({ error: 'Invalid birthday' }, { status: 400 })
      }
    }

    // Generate a unique family code for the new player
    const familyCode = generateFamilyCode()

    // Create player with family code
    const [player] = await db
      .insert(schema.players)
      .values({
        userId,
        name: body.name,
        emoji: body.emoji,
        color: body.color,
        isActive: body.isActive ?? false,
        isPracticeStudent,
        isExpungeable: body.isExpungeable ?? false,
        familyCode,
        ...(normalizedBirthday !== undefined && { birthday: normalizedBirthday }),
      })
      .returning()

    // Create parent-child relationship
    await db.insert(parentChild).values({
      parentUserId: userId,
      childPlayerId: player.id,
    })

    return NextResponse.json({ player }, { status: 201 })
  } catch (error) {
    console.error('Failed to create player:', error)
    return NextResponse.json({ error: 'Failed to create player' }, { status: 500 })
  }
})