import fp from 'fastify-plugin'; import { asClass, Lifetime } from 'awilix'; import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { z } from 'zod/v4'; import { CreateRegimenSchema, UpdateRegimenSchema, RegimenQuerySchema, RegimenResponseSchema, RegimenListResponseSchema, BurnRateResponseSchema, type CreateRegimenInput, type UpdateRegimenInput, type RegimenQueryInput, } from '@meshitrack/shared'; import { RegimensRepository } from './regimens.repository.js'; import { RegimensService } from './regimens.service.js'; import type { RegimenDocument } from '../../schemas/regimen.schema.js'; interface SerializedRegimenMedication { medicineId: string; medicineName: string; medicineStrength: number; medicineStrengthUnit: string; medicineForm: string; dosage: number; dosageUnit: string; frequency: string; customFrequencyPerDay?: number; timeOfDay?: string; instructions?: string; } interface SerializedRegimenResponse { _id: string; householdId: string; userId: string; name: string; isActive: boolean; medications: SerializedRegimenMedication[]; createdBy: string; createdAt: string; updatedAt: string; } function toRegimenResponse(doc: RegimenDocument): SerializedRegimenResponse { const docAny = doc as unknown as { createdAt?: { toISOString?: () => string } | string; updatedAt?: { toISOString?: () => string } | string; }; const getIsoStr = (d: { toISOString?: () => string } | string | undefined | null): string => { if (!d) return ''; if (typeof d === 'string') return d; if (typeof d.toISOString === 'function') return d.toISOString(); return String(d); }; return { _id: doc._id.toString(), householdId: doc.householdId, userId: doc.userId, name: doc.name, isActive: doc.isActive, medications: doc.medications.map((med) => { const medAny = med as unknown as { customFrequencyPerDay?: number | null; timeOfDay?: string | null; instructions?: string | null; }; const response: SerializedRegimenMedication = { medicineId: med.medicineId, medicineName: med.medicineName, medicineStrength: med.medicineStrength, medicineStrengthUnit: med.medicineStrengthUnit, medicineForm: med.medicineForm, dosage: med.dosage, dosageUnit: med.dosageUnit, frequency: med.frequency, }; if (medAny.customFrequencyPerDay != null) { response.customFrequencyPerDay = medAny.customFrequencyPerDay; } if (medAny.timeOfDay) { response.timeOfDay = medAny.timeOfDay; } if (medAny.instructions) { response.instructions = medAny.instructions; } return response; }), createdBy: doc.createdBy, createdAt: getIsoStr(docAny.createdAt), updatedAt: getIsoStr(docAny.updatedAt), }; } declare module '@fastify/awilix' { interface Cradle { regimensRepository: RegimensRepository; regimensService: RegimensService; } } export default fp( async (fastify) => { fastify.diContainer.register({ regimensRepository: asClass(RegimensRepository, { lifetime: Lifetime.SINGLETON }), regimensService: asClass(RegimensService, { lifetime: Lifetime.SINGLETON }), }); const app = fastify.withTypeProvider(); const householdParams = z.object({ householdId: z.string() }); // GET /api/v1/households/:householdId/regimens — list user's regimens app.route({ method: 'GET', url: '/api/v1/households/:householdId/regimens', schema: { params: householdParams, querystring: RegimenQuerySchema, response: { 200: RegimenListResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string }; const query = request.query as RegimenQueryInput; const result = await service.list(params.householdId, request.user.keycloakId, query); return reply.send({ data: result.data.map(toRegimenResponse), pagination: result.pagination, }); }, }); // GET /api/v1/households/:householdId/regimens/burn-rate — burn rate + spending projection app.route({ method: 'GET', url: '/api/v1/households/:householdId/regimens/burn-rate', schema: { params: householdParams, response: { 200: BurnRateResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string }; const data = await service.calculateBurnRates(params.householdId, request.user.keycloakId); return reply.send({ data }); }, }); // GET /api/v1/households/:householdId/regimens/:id — get single regimen app.route({ method: 'GET', url: '/api/v1/households/:householdId/regimens/:id', schema: { params: householdParams.extend({ id: z.string() }), response: { 200: RegimenResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string; id: string }; const regimen = await service.getById( params.id, params.householdId, request.user.keycloakId, ); return reply.send(toRegimenResponse(regimen)); }, }); // POST /api/v1/households/:householdId/regimens — create regimen app.route({ method: 'POST', url: '/api/v1/households/:householdId/regimens', schema: { params: householdParams, body: CreateRegimenSchema, response: { 201: RegimenResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string }; const body = request.body as CreateRegimenInput; const regimen = await service.create(body, params.householdId, request.user.keycloakId); return reply.status(201).send(toRegimenResponse(regimen)); }, }); // PATCH /api/v1/households/:householdId/regimens/:id — update regimen app.route({ method: 'PATCH', url: '/api/v1/households/:householdId/regimens/:id', schema: { params: householdParams.extend({ id: z.string() }), body: UpdateRegimenSchema, response: { 200: RegimenResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string; id: string }; const body = request.body as UpdateRegimenInput; const regimen = await service.update( params.id, params.householdId, request.user.keycloakId, body, ); return reply.send(toRegimenResponse(regimen)); }, }); // DELETE /api/v1/households/:householdId/regimens/:id — delete regimen app.route({ method: 'DELETE', url: '/api/v1/households/:householdId/regimens/:id', schema: { params: householdParams.extend({ id: z.string() }), response: { 204: z.undefined() }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('regimensService'); const params = request.params as { householdId: string; id: string }; await service.delete(params.id, params.householdId, request.user.keycloakId); return reply.status(204).send(); }, }); }, { name: 'regimens-routes', dependencies: ['auth-plugin'], }, );