All files / web/src/app/api/worksheets/mastery route.ts

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

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 144 145 146 147 148 149 150 151 152                                                                                                                                                                                                                                                                                                               
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import type { SkillId } from '@/app/create/worksheets/skills'
import { db, schema } from '@/db'
import { getUserId } from '@/lib/viewer'
import { withAuth } from '@/lib/auth/withAuth'

/**
 * GET /api/worksheets/mastery?operator=addition
 * Load user's mastery states for all skills
 *
 * Query params:
 *   - operator: 'addition' | 'subtraction'
 *
 * Returns:
 *   - masteryStates: Array of mastery records
 *   - skillCount: Total number of skills tracked
 */
export const GET = withAuth(async (request) => {
  try {
    const userId = await getUserId()
    const { searchParams } = new URL(request.url)
    const operator = searchParams.get('operator')

    if (!operator) {
      return NextResponse.json({ error: 'Missing operator parameter' }, { status: 400 })
    }

    if (operator !== 'addition' && operator !== 'subtraction') {
      return NextResponse.json({ error: `Invalid operator: ${operator}` }, { status: 400 })
    }

    // Fetch all mastery records for this user
    // Note: We don't filter by operator here because skill IDs are already namespaced
    // (e.g., "sd-no-regroup" for addition, "sd-sub-no-borrow" for subtraction)
    const masteryRecords = await db
      .select()
      .from(schema.worksheetMastery)
      .where(eq(schema.worksheetMastery.userId, userId))

    return NextResponse.json({
      masteryStates: masteryRecords,
      skillCount: masteryRecords.length,
    })
  } catch (error: any) {
    console.error('Failed to load mastery states:', error)
    return NextResponse.json({ error: 'Failed to load mastery states' }, { status: 500 })
  }
})

/**
 * POST /api/worksheets/mastery
 * Update mastery state for a skill
 *
 * Body:
 *   - skillId: string (e.g., "td-ones-regroup")
 *   - isMastered: boolean
 *   - totalAttempts?: number (optional)
 *   - correctAttempts?: number (optional)
 *   - lastAccuracy?: number (optional, 0.0-1.0)
 *
 * Returns:
 *   - success: boolean
 *   - masteryState: Updated mastery record
 */
export const POST = withAuth(async (request) => {
  try {
    const userId = await getUserId()
    const body = await request.json()

    const { skillId, isMastered, totalAttempts, correctAttempts, lastAccuracy } = body

    if (!skillId) {
      return NextResponse.json({ error: 'Missing skillId field' }, { status: 400 })
    }

    if (typeof isMastered !== 'boolean') {
      return NextResponse.json(
        { error: 'Missing or invalid isMastered field (must be boolean)' },
        { status: 400 }
      )
    }

    // Check if user already has mastery record for this skill
    const [existing] = await db
      .select()
      .from(schema.worksheetMastery)
      .where(
        and(
          eq(schema.worksheetMastery.userId, userId),
          eq(schema.worksheetMastery.skillId, skillId)
        )
      )
      .limit(1)

    const now = new Date()

    if (existing) {
      // Update existing record
      const updated = {
        isMastered,
        totalAttempts: totalAttempts !== undefined ? totalAttempts : existing.totalAttempts,
        correctAttempts: correctAttempts !== undefined ? correctAttempts : existing.correctAttempts,
        lastAccuracy: lastAccuracy !== undefined ? lastAccuracy : existing.lastAccuracy,
        masteredAt: isMastered ? existing.masteredAt || now : null, // Set mastered timestamp on first mastery
        lastPracticedAt: now,
        updatedAt: now,
      }

      await db
        .update(schema.worksheetMastery)
        .set(updated)
        .where(eq(schema.worksheetMastery.id, existing.id))

      return NextResponse.json({
        success: true,
        masteryState: {
          ...existing,
          ...updated,
        },
      })
    } else {
      // Insert new record
      const id = crypto.randomUUID()
      const newRecord = {
        id,
        userId: userId,
        skillId: skillId as SkillId,
        isMastered,
        totalAttempts: totalAttempts || 0,
        correctAttempts: correctAttempts || 0,
        lastAccuracy: lastAccuracy || null,
        firstAttemptAt: now,
        masteredAt: isMastered ? now : null,
        lastPracticedAt: now,
        createdAt: now,
        updatedAt: now,
      }

      await db.insert(schema.worksheetMastery).values(newRecord)

      return NextResponse.json({
        success: true,
        masteryState: newRecord,
      })
    }
  } catch (error: any) {
    console.error('Failed to update mastery state:', error)
    return NextResponse.json({ error: 'Failed to update mastery state' }, { status: 500 })
  }
})