All files / web/src/lib/curriculum progression-deferrals.ts

31.39% Statements 27/86
100% Branches 0/0
0% Functions 0/3
31.39% Lines 27/86

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 872x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                                         2x 2x 2x 2x 2x                                                           2x 2x 2x 2x                      
/**
 * Progression Deferrals
 *
 * Server-side functions for managing skill progression deferrals.
 * When a teacher clicks "Not yet, ask again later", the system defers
 * the progression recommendation for 7 days.
 */
 
import { and, eq, gt } from 'drizzle-orm'
import { db, schema } from '@/db'
import type { ProgressionDeferral } from '@/db/schema/progression-deferrals'
 
/** Default deferral duration: 7 days in milliseconds */
const DEFERRAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000
 
/**
 * Check if there is an active (non-expired) deferral for a skill.
 */
export async function getActiveDeferral(
  playerId: string,
  skillId: string
): Promise<ProgressionDeferral | null> {
  const now = Date.now()

  const result = await db
    .select()
    .from(schema.progressionDeferrals)
    .where(
      and(
        eq(schema.progressionDeferrals.playerId, playerId),
        eq(schema.progressionDeferrals.skillId, skillId),
        gt(schema.progressionDeferrals.expiresAt, now)
      )
    )
    .limit(1)

  return result[0] ?? null
}
 
/**
 * Create or update a progression deferral for a skill.
 * Uses INSERT OR REPLACE since there's a unique constraint on (playerId, skillId).
 */
export async function deferProgression(
  playerId: string,
  skillId: string,
  durationMs: number = DEFERRAL_DURATION_MS
): Promise<ProgressionDeferral> {
  const now = Date.now()

  // Delete existing deferral first (upsert pattern for SQLite)
  await db
    .delete(schema.progressionDeferrals)
    .where(
      and(
        eq(schema.progressionDeferrals.playerId, playerId),
        eq(schema.progressionDeferrals.skillId, skillId)
      )
    )

  const [result] = await db
    .insert(schema.progressionDeferrals)
    .values({
      playerId,
      skillId,
      deferredAt: now,
      expiresAt: now + durationMs,
    })
    .returning()

  return result
}
 
/**
 * Remove a progression deferral (e.g., teacher changes their mind).
 */
export async function clearDeferral(playerId: string, skillId: string): Promise<void> {
  await db
    .delete(schema.progressionDeferrals)
    .where(
      and(
        eq(schema.progressionDeferrals.playerId, playerId),
        eq(schema.progressionDeferrals.skillId, skillId)
      )
    )
}