feat(classroom): add active sessions API endpoint

Provides API for useActiveSessionsInClassroom hook to fetch
which students in a classroom are currently practicing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-12-25 07:57:43 -06:00
parent 2015494c0e
commit 07f6bb7f9c

View File

@@ -0,0 +1,119 @@
import { and, eq, inArray, isNull } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { db, schema } from '@/db'
import { getClassroomPresence, getTeacherClassroom } from '@/lib/classroom'
import { getViewerId } from '@/lib/viewer'
/**
* Get or create user record for a viewerId (guestId)
*/
async function getOrCreateUser(viewerId: string) {
let user = await db.query.users.findFirst({
where: eq(schema.users.guestId, viewerId),
})
if (!user) {
const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
user = newUser
}
return user
}
interface RouteParams {
params: Promise<{ classroomId: string }>
}
/**
* Active session information returned by this endpoint
*/
interface ActiveSessionInfo {
/** Session plan ID (for observation) */
sessionId: string
/** Player ID */
playerId: string
/** When the session started */
startedAt: Date
/** Current part index */
currentPartIndex: number
/** Current slot index within the part */
currentSlotIndex: number
/** Total parts in session */
totalParts: number
/** Total problems in session (sum of all slots) */
totalProblems: number
/** Number of completed problems */
completedProblems: number
}
/**
* GET /api/classrooms/[classroomId]/presence/active-sessions
* Get active practice sessions for students currently present in the classroom
*
* Returns: { sessions: ActiveSessionInfo[] }
*
* This endpoint allows teachers to see which students are actively practicing
* so they can observe their sessions in real-time.
*/
export async function GET(req: NextRequest, { params }: RouteParams) {
try {
const { classroomId } = await params
const viewerId = await getViewerId()
const user = await getOrCreateUser(viewerId)
// Verify user is the teacher of this classroom
const classroom = await getTeacherClassroom(user.id)
if (!classroom || classroom.id !== classroomId) {
return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
}
// Get all students currently present in the classroom
const presences = await getClassroomPresence(classroomId)
// Filter out presences where player was deleted (undefined)
const playerIds = presences.filter((p) => p.player !== undefined).map((p) => p.player!.id)
if (playerIds.length === 0) {
return NextResponse.json({ sessions: [] })
}
// Find active sessions for these players
// Active = status is 'in_progress', startedAt is set, completedAt is null
const activeSessions = await db.query.sessionPlans.findMany({
where: and(
inArray(schema.sessionPlans.playerId, playerIds),
eq(schema.sessionPlans.status, 'in_progress'),
isNull(schema.sessionPlans.completedAt)
),
})
// Map to ActiveSessionInfo
const sessions: ActiveSessionInfo[] = activeSessions
.filter((session) => session.startedAt)
.map((session) => {
// Calculate total and completed problems
const parts = session.parts
const totalProblems = parts.reduce((sum, part) => sum + part.slots.length, 0)
let completedProblems = 0
for (let i = 0; i < session.currentPartIndex; i++) {
completedProblems += parts[i].slots.length
}
completedProblems += session.currentSlotIndex
return {
sessionId: session.id,
playerId: session.playerId,
startedAt: session.startedAt as Date,
currentPartIndex: session.currentPartIndex,
currentSlotIndex: session.currentSlotIndex,
totalParts: parts.length,
totalProblems,
completedProblems,
}
})
return NextResponse.json({ sessions })
} catch (error) {
console.error('Failed to fetch active sessions:', error)
return NextResponse.json({ error: 'Failed to fetch active sessions' }, { status: 500 })
}
}