profileShare

rasmusjy / voicetask

Read-only snapshot

No repository description.

main default branch 105 files Expires Sep 13, 2026, 9:06 AM
sessions.ts 2,835 bytes
1 import { randomUUID } from 'node:crypto'
2 import { mkdir } from 'node:fs/promises'
3 import path from 'node:path'
4 import type { FastifyInstance } from 'fastify'
5 import { AnswerRequestSchema, CreateSessionRequestSchema } from '../../shared/types'
6 import { SessionNotFoundError, submitAnswer } from '../engine/turn'
7 import { ProviderNotConfiguredError, type InterviewLlm } from '../providers/types'
8 import type { SessionStore } from '../store/sessionStore'
9
10 export interface SessionRouteDeps {
11 store: SessionStore
12 llm: InterviewLlm
13 /**
14 * When set, the spec pack is written to a fresh folder inside this directory
15 * and the `targetDir` the browser sent is ignored. Locally that field is the
16 * point - you pick where your project lives - but on a public host it is a
17 * request to write files anywhere the server can reach.
18 */
19 sandboxRoot?: string
20 }
21
22 export function registerSessionRoutes(app: FastifyInstance, deps: SessionRouteDeps): void {
23 const { store, llm, sandboxRoot } = deps
24
25 app.post('/api/sessions', async (request, reply) => {
26 const parsed = CreateSessionRequestSchema.safeParse(request.body)
27 if (!parsed.success) {
28 return reply.code(400).send({ error: parsed.error.message })
29 }
30 let targetDir = parsed.data.targetDir
31 if (sandboxRoot !== undefined) {
32 targetDir = path.join(sandboxRoot, randomUUID())
33 await mkdir(targetDir, { recursive: true })
34 }
35 const session = await store.createSession(parsed.data.name, targetDir)
36 return reply.code(201).send(session)
37 })
38
39 app.get('/api/sessions', async () => {
40 return store.listSessions()
41 })
42
43 app.get<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
44 const session = await store.getSession(request.params.id)
45 if (!session) return reply.code(404).send({ error: 'session not found' })
46 return session
47 })
48
49 app.delete<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
50 const deleted = await store.deleteSession(request.params.id)
51 if (!deleted) return reply.code(404).send({ error: 'session not found' })
52 return reply.code(204).send()
53 })
54
55 app.post<{ Params: { id: string } }>('/api/sessions/:id/answer', async (request, reply) => {
56 const parsed = AnswerRequestSchema.safeParse(request.body)
57 if (!parsed.success) {
58 return reply.code(400).send({ error: parsed.error.message })
59 }
60
61 try {
62 const response = await submitAnswer(store, llm, request.params.id, parsed.data.text)
63 return reply.send(response)
64 } catch (err) {
65 if (err instanceof SessionNotFoundError) {
66 return reply.code(404).send({ error: 'session not found' })
67 }
68 if (err instanceof ProviderNotConfiguredError) {
69 return reply.code(503).send({ error: err.message })
70 }
71 throw err
72 }
73 })
74 }
75