- New tables: lesson_plan_template, lesson_plan_template_section, lesson_plan_template_item - skill_level enum: beginner, intermediate, advanced, all_levels - Templates are reusable curriculum definitions independent of any member/enrollment - POST /lesson-plan-templates/:id/create-plan deep-copies the template into a member plan - Instantiation uses template name as default plan title, accepts custom title override - Instantiation deactivates any existing active plan on the enrollment (one-active rule) - Plan items are independent copies — renaming the template does not affect existing plans - 11 new integration tests
541 lines
28 KiB
TypeScript
541 lines
28 KiB
TypeScript
import type { FastifyPluginAsync } from 'fastify'
|
|
import {
|
|
PaginationSchema,
|
|
InstructorCreateSchema,
|
|
InstructorUpdateSchema,
|
|
LessonTypeCreateSchema,
|
|
LessonTypeUpdateSchema,
|
|
ScheduleSlotCreateSchema,
|
|
ScheduleSlotUpdateSchema,
|
|
EnrollmentCreateSchema,
|
|
EnrollmentUpdateSchema,
|
|
EnrollmentStatusUpdateSchema,
|
|
LessonSessionStatusUpdateSchema,
|
|
LessonSessionNotesSchema,
|
|
LessonSessionUpdateSchema,
|
|
GradingScaleCreateSchema,
|
|
GradingScaleUpdateSchema,
|
|
LessonPlanCreateSchema,
|
|
LessonPlanUpdateSchema,
|
|
LessonPlanItemUpdateSchema,
|
|
GradeCreateSchema,
|
|
SessionPlanItemsSchema,
|
|
LessonPlanTemplateCreateSchema,
|
|
LessonPlanTemplateUpdateSchema,
|
|
TemplateInstantiateSchema,
|
|
InstructorBlockedDateCreateSchema,
|
|
StoreClosureCreateSchema,
|
|
} from '@lunarfront/shared/schemas'
|
|
import { InstructorService, LessonTypeService, ScheduleSlotService, EnrollmentService, LessonSessionService, GradingScaleService, LessonPlanService, LessonPlanItemService, LessonPlanTemplateService, GradeHistoryService, SessionPlanItemService, InstructorBlockedDateService, StoreClosureService } from '../../services/lesson.service.js'
|
|
|
|
export const lessonRoutes: FastifyPluginAsync = async (app) => {
|
|
// --- Instructors ---
|
|
|
|
app.post('/instructors', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = InstructorCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const instructor = await InstructorService.create(app.db, parsed.data)
|
|
return reply.status(201).send(instructor)
|
|
})
|
|
|
|
app.get('/instructors', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const params = PaginationSchema.parse(request.query)
|
|
const result = await InstructorService.list(app.db, params)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/instructors/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const instructor = await InstructorService.getById(app.db, id)
|
|
if (!instructor) return reply.status(404).send({ error: { message: 'Instructor not found', statusCode: 404 } })
|
|
return reply.send(instructor)
|
|
})
|
|
|
|
app.patch('/instructors/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = InstructorUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const instructor = await InstructorService.update(app.db, id, parsed.data)
|
|
if (!instructor) return reply.status(404).send({ error: { message: 'Instructor not found', statusCode: 404 } })
|
|
return reply.send(instructor)
|
|
})
|
|
|
|
app.delete('/instructors/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const instructor = await InstructorService.delete(app.db, id)
|
|
if (!instructor) return reply.status(404).send({ error: { message: 'Instructor not found', statusCode: 404 } })
|
|
return reply.send(instructor)
|
|
})
|
|
|
|
// --- Lesson Types ---
|
|
|
|
app.post('/lesson-types', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = LessonTypeCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const lessonType = await LessonTypeService.create(app.db, parsed.data)
|
|
return reply.status(201).send(lessonType)
|
|
})
|
|
|
|
app.get('/lesson-types', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const params = PaginationSchema.parse(request.query)
|
|
const result = await LessonTypeService.list(app.db, params)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/lesson-types/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const lessonType = await LessonTypeService.getById(app.db, id)
|
|
if (!lessonType) return reply.status(404).send({ error: { message: 'Lesson type not found', statusCode: 404 } })
|
|
return reply.send(lessonType)
|
|
})
|
|
|
|
app.patch('/lesson-types/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonTypeUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const lessonType = await LessonTypeService.update(app.db, id, parsed.data)
|
|
if (!lessonType) return reply.status(404).send({ error: { message: 'Lesson type not found', statusCode: 404 } })
|
|
return reply.send(lessonType)
|
|
})
|
|
|
|
app.delete('/lesson-types/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const lessonType = await LessonTypeService.delete(app.db, id)
|
|
if (!lessonType) return reply.status(404).send({ error: { message: 'Lesson type not found', statusCode: 404 } })
|
|
return reply.send(lessonType)
|
|
})
|
|
|
|
// --- Schedule Slots ---
|
|
|
|
app.post('/schedule-slots', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = ScheduleSlotCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await ScheduleSlotService.create(app.db, parsed.data)
|
|
if ('error' in result) {
|
|
return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
}
|
|
return reply.status(201).send(result)
|
|
})
|
|
|
|
app.get('/schedule-slots', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
const filters = {
|
|
instructorId: query.instructorId,
|
|
dayOfWeek: query.dayOfWeek !== undefined ? Number(query.dayOfWeek) : undefined,
|
|
}
|
|
const result = await ScheduleSlotService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/schedule-slots/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const slot = await ScheduleSlotService.getById(app.db, id)
|
|
if (!slot) return reply.status(404).send({ error: { message: 'Schedule slot not found', statusCode: 404 } })
|
|
return reply.send(slot)
|
|
})
|
|
|
|
app.patch('/schedule-slots/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = ScheduleSlotUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await ScheduleSlotService.update(app.db, id, parsed.data)
|
|
if (!result) return reply.status(404).send({ error: { message: 'Schedule slot not found', statusCode: 404 } })
|
|
if ('error' in result) {
|
|
return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
}
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.delete('/schedule-slots/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const slot = await ScheduleSlotService.delete(app.db, id)
|
|
if (!slot) return reply.status(404).send({ error: { message: 'Schedule slot not found', statusCode: 404 } })
|
|
return reply.send(slot)
|
|
})
|
|
|
|
// --- Enrollments ---
|
|
|
|
app.post('/enrollments', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const parsed = EnrollmentCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await EnrollmentService.create(app.db, parsed.data)
|
|
if ('error' in result) {
|
|
return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
}
|
|
return reply.status(201).send(result)
|
|
})
|
|
|
|
app.get('/enrollments', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
const filters = {
|
|
memberId: query.memberId,
|
|
accountId: query.accountId,
|
|
instructorId: query.instructorId,
|
|
status: query.status?.split(',').filter(Boolean),
|
|
}
|
|
const result = await EnrollmentService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/enrollments/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const enrollment = await EnrollmentService.getById(app.db, id)
|
|
if (!enrollment) return reply.status(404).send({ error: { message: 'Enrollment not found', statusCode: 404 } })
|
|
return reply.send(enrollment)
|
|
})
|
|
|
|
app.patch('/enrollments/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = EnrollmentUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const enrollment = await EnrollmentService.update(app.db, id, parsed.data)
|
|
if (!enrollment) return reply.status(404).send({ error: { message: 'Enrollment not found', statusCode: 404 } })
|
|
return reply.send(enrollment)
|
|
})
|
|
|
|
app.post('/enrollments/:id/status', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = EnrollmentStatusUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const enrollment = await EnrollmentService.updateStatus(app.db, id, parsed.data.status)
|
|
if (!enrollment) return reply.status(404).send({ error: { message: 'Enrollment not found', statusCode: 404 } })
|
|
return reply.send(enrollment)
|
|
})
|
|
|
|
app.post('/enrollments/:id/generate-sessions', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const query = request.query as Record<string, string | undefined>
|
|
const weeks = query.weeks ? Number(query.weeks) : 4
|
|
const sessions = await LessonSessionService.generateSessions(app.db, id, weeks)
|
|
return reply.send({ generated: sessions.length, sessions })
|
|
})
|
|
|
|
// --- Lesson Sessions ---
|
|
|
|
app.get('/lesson-sessions', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
const filters = {
|
|
enrollmentId: query.enrollmentId,
|
|
instructorId: query.instructorId,
|
|
status: query.status?.split(',').filter(Boolean),
|
|
dateFrom: query.dateFrom,
|
|
dateTo: query.dateTo,
|
|
}
|
|
const result = await LessonSessionService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/lesson-sessions/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const session = await LessonSessionService.getById(app.db, id)
|
|
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
|
return reply.send(session)
|
|
})
|
|
|
|
app.patch('/lesson-sessions/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonSessionUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await LessonSessionService.update(app.db, id, parsed.data)
|
|
if (!result) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
|
if ('error' in result) return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.post('/lesson-sessions/:id/status', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonSessionStatusUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const session = await LessonSessionService.updateStatus(app.db, id, parsed.data.status)
|
|
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
|
return reply.send(session)
|
|
})
|
|
|
|
app.post('/lesson-sessions/:id/notes', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonSessionNotesSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const session = await LessonSessionService.updateNotes(app.db, id, parsed.data)
|
|
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
|
return reply.send(session)
|
|
})
|
|
|
|
// --- Grading Scales ---
|
|
|
|
app.post('/grading-scales', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = GradingScaleCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const scale = await GradingScaleService.create(app.db, parsed.data, request.user.id)
|
|
return reply.status(201).send(scale)
|
|
})
|
|
|
|
app.get('/grading-scales', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const params = PaginationSchema.parse(request.query)
|
|
const result = await GradingScaleService.list(app.db, params)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/grading-scales/all', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (_request, reply) => {
|
|
const scales = await GradingScaleService.listAll(app.db)
|
|
return reply.send(scales)
|
|
})
|
|
|
|
app.get('/grading-scales/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const scale = await GradingScaleService.getById(app.db, id)
|
|
if (!scale) return reply.status(404).send({ error: { message: 'Grading scale not found', statusCode: 404 } })
|
|
return reply.send(scale)
|
|
})
|
|
|
|
app.patch('/grading-scales/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = GradingScaleUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const scale = await GradingScaleService.update(app.db, id, parsed.data)
|
|
if (!scale) return reply.status(404).send({ error: { message: 'Grading scale not found', statusCode: 404 } })
|
|
return reply.send(scale)
|
|
})
|
|
|
|
app.delete('/grading-scales/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const scale = await GradingScaleService.delete(app.db, id)
|
|
if (!scale) return reply.status(404).send({ error: { message: 'Grading scale not found', statusCode: 404 } })
|
|
return reply.send(scale)
|
|
})
|
|
|
|
// --- Lesson Plans ---
|
|
|
|
app.post('/lesson-plans', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const parsed = LessonPlanCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const plan = await LessonPlanService.create(app.db, parsed.data, request.user.id)
|
|
return reply.status(201).send(plan)
|
|
})
|
|
|
|
app.get('/lesson-plans', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
const filters = {
|
|
enrollmentId: query.enrollmentId,
|
|
memberId: query.memberId,
|
|
isActive: query.isActive === 'true' ? true : query.isActive === 'false' ? false : undefined,
|
|
}
|
|
const result = await LessonPlanService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/lesson-plans/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const plan = await LessonPlanService.getById(app.db, id)
|
|
if (!plan) return reply.status(404).send({ error: { message: 'Lesson plan not found', statusCode: 404 } })
|
|
return reply.send(plan)
|
|
})
|
|
|
|
app.patch('/lesson-plans/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonPlanUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const plan = await LessonPlanService.update(app.db, id, parsed.data)
|
|
if (!plan) return reply.status(404).send({ error: { message: 'Lesson plan not found', statusCode: 404 } })
|
|
return reply.send(plan)
|
|
})
|
|
|
|
// --- Lesson Plan Items ---
|
|
|
|
app.patch('/lesson-plan-items/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonPlanItemUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const item = await LessonPlanItemService.update(app.db, id, parsed.data)
|
|
if (!item) return reply.status(404).send({ error: { message: 'Lesson plan item not found', statusCode: 404 } })
|
|
return reply.send(item)
|
|
})
|
|
|
|
// --- Lesson Plan Templates ---
|
|
|
|
app.post('/lesson-plan-templates', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = LessonPlanTemplateCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const template = await LessonPlanTemplateService.create(app.db, parsed.data, request.user.id)
|
|
return reply.status(201).send(template)
|
|
})
|
|
|
|
app.get('/lesson-plan-templates', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const query = request.query as Record<string, string | undefined>
|
|
const params = PaginationSchema.parse(query)
|
|
const filters = {
|
|
instrument: query.instrument,
|
|
skillLevel: query.skillLevel,
|
|
}
|
|
const result = await LessonPlanTemplateService.list(app.db, params, filters)
|
|
return reply.send(result)
|
|
})
|
|
|
|
app.get('/lesson-plan-templates/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const template = await LessonPlanTemplateService.getById(app.db, id)
|
|
if (!template) return reply.status(404).send({ error: { message: 'Template not found', statusCode: 404 } })
|
|
return reply.send(template)
|
|
})
|
|
|
|
app.patch('/lesson-plan-templates/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = LessonPlanTemplateUpdateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const template = await LessonPlanTemplateService.update(app.db, id, parsed.data)
|
|
if (!template) return reply.status(404).send({ error: { message: 'Template not found', statusCode: 404 } })
|
|
return reply.send(template)
|
|
})
|
|
|
|
app.delete('/lesson-plan-templates/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const template = await LessonPlanTemplateService.delete(app.db, id)
|
|
if (!template) return reply.status(404).send({ error: { message: 'Template not found', statusCode: 404 } })
|
|
return reply.send(template)
|
|
})
|
|
|
|
app.post('/lesson-plan-templates/:id/create-plan', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = TemplateInstantiateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const plan = await LessonPlanTemplateService.instantiate(app.db, id, parsed.data, request.user.id)
|
|
if (!plan) return reply.status(404).send({ error: { message: 'Template not found', statusCode: 404 } })
|
|
return reply.status(201).send(plan)
|
|
})
|
|
|
|
// --- Grade History ---
|
|
|
|
app.post('/lesson-plan-items/:id/grades', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = GradeCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await GradeHistoryService.create(app.db, id, parsed.data, request.user.id)
|
|
if (!result) return reply.status(404).send({ error: { message: 'Lesson plan item not found', statusCode: 404 } })
|
|
return reply.status(201).send(result)
|
|
})
|
|
|
|
app.get('/lesson-plan-items/:id/grade-history', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const history = await GradeHistoryService.list(app.db, id)
|
|
return reply.send(history)
|
|
})
|
|
|
|
// --- Session Plan Items ---
|
|
|
|
app.post('/lesson-sessions/:id/plan-items', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = SessionPlanItemsSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await SessionPlanItemService.linkItems(app.db, id, parsed.data)
|
|
if (result === null) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
|
return reply.status(201).send({ linked: result.length, items: result })
|
|
})
|
|
|
|
app.get('/lesson-sessions/:id/plan-items', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const items = await SessionPlanItemService.listForSession(app.db, id)
|
|
return reply.send(items)
|
|
})
|
|
|
|
// --- Instructor Blocked Dates ---
|
|
|
|
app.post('/instructors/:id/blocked-dates', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const parsed = InstructorBlockedDateCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const instructor = await InstructorService.getById(app.db, id)
|
|
if (!instructor) return reply.status(404).send({ error: { message: 'Instructor not found', statusCode: 404 } })
|
|
const result = await InstructorBlockedDateService.create(app.db, id, parsed.data)
|
|
if ('error' in result) return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
return reply.status(201).send(result)
|
|
})
|
|
|
|
app.get('/instructors/:id/blocked-dates', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const instructor = await InstructorService.getById(app.db, id)
|
|
if (!instructor) return reply.status(404).send({ error: { message: 'Instructor not found', statusCode: 404 } })
|
|
const dates = await InstructorBlockedDateService.list(app.db, id)
|
|
return reply.send(dates)
|
|
})
|
|
|
|
app.delete('/instructors/:instructorId/blocked-dates/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { instructorId, id } = request.params as { instructorId: string; id: string }
|
|
const row = await InstructorBlockedDateService.delete(app.db, id, instructorId)
|
|
if (!row) return reply.status(404).send({ error: { message: 'Blocked date not found', statusCode: 404 } })
|
|
return reply.send(row)
|
|
})
|
|
|
|
// --- Store Closures ---
|
|
|
|
app.post('/store-closures', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const parsed = StoreClosureCreateSchema.safeParse(request.body)
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
|
}
|
|
const result = await StoreClosureService.create(app.db, parsed.data)
|
|
if ('error' in result) return reply.status(409).send({ error: { message: result.error, statusCode: 409 } })
|
|
return reply.status(201).send(result)
|
|
})
|
|
|
|
app.get('/store-closures', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (_request, reply) => {
|
|
const closures = await StoreClosureService.list(app.db)
|
|
return reply.send(closures)
|
|
})
|
|
|
|
app.delete('/store-closures/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.admin')] }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
const row = await StoreClosureService.delete(app.db, id)
|
|
if (!row) return reply.status(404).send({ error: { message: 'Store closure not found', statusCode: 404 } })
|
|
return reply.send(row)
|
|
})
|
|
}
|