MeshiTrack/packages/api/src/modules/regimens/regimens.routes.ts

236 lines
7.7 KiB
TypeScript
Raw Normal View History

2026-03-28 18:25:49 +09:00
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,
2026-05-19 16:15:15 +09:00
type CreateRegimenInput,
type UpdateRegimenInput,
type RegimenQueryInput,
2026-03-28 18:25:49 +09:00
} from '@meshitrack/shared';
import { RegimensRepository } from './regimens.repository.js';
import { RegimensService } from './regimens.service.js';
2026-05-19 16:15:15 +09:00
import type { RegimenDocument } from '../../schemas/regimen.schema.js';
2026-03-28 18:25:49 +09:00
2026-05-19 16:15:15 +09:00
interface SerializedRegimenMedication {
2026-03-28 18:25:49 +09:00
medicineId: string;
medicineName: string;
medicineStrength: number;
medicineStrengthUnit: string;
medicineForm: string;
dosage: number;
dosageUnit: string;
frequency: string;
2026-05-19 16:15:15 +09:00
customFrequencyPerDay?: number;
timeOfDay?: string;
instructions?: string;
}
2026-03-28 18:25:49 +09:00
2026-05-19 16:15:15 +09:00
interface SerializedRegimenResponse {
_id: string;
2026-03-28 18:25:49 +09:00
householdId: string;
userId: string;
name: string;
isActive: boolean;
2026-05-19 16:15:15 +09:00
medications: SerializedRegimenMedication[];
2026-03-28 18:25:49 +09:00
createdBy: string;
2026-05-19 16:15:15 +09:00
createdAt: string;
updatedAt: string;
2026-03-28 18:25:49 +09:00
}
2026-05-19 16:15:15 +09:00
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);
};
2026-03-28 18:25:49 +09:00
return {
2026-05-19 16:15:15 +09:00
_id: doc._id.toString(),
2026-03-28 18:25:49 +09:00
householdId: doc.householdId,
userId: doc.userId,
name: doc.name,
isActive: doc.isActive,
2026-05-19 16:15:15 +09:00
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;
}),
2026-03-28 18:25:49 +09:00
createdBy: doc.createdBy,
2026-05-19 16:15:15 +09:00
createdAt: getIsoStr(docAny.createdAt),
updatedAt: getIsoStr(docAny.updatedAt),
2026-03-28 18:25:49 +09:00
};
}
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<ZodTypeProvider>();
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string };
const query = request.query as RegimenQueryInput;
const result = await service.list(params.householdId, request.user.keycloakId, query);
2026-03-28 18:25:49 +09:00
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string };
const data = await service.calculateBurnRates(params.householdId, request.user.keycloakId);
2026-03-28 18:25:49 +09:00
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string; id: string };
2026-04-26 18:44:59 +09:00
const regimen = await service.getById(
2026-05-19 16:15:15 +09:00
params.id,
params.householdId,
2026-04-26 18:44:59 +09:00
request.user.keycloakId,
);
2026-03-28 18:25:49 +09:00
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string };
const body = request.body as CreateRegimenInput;
const regimen = await service.create(body, params.householdId, request.user.keycloakId);
2026-03-28 18:25:49 +09:00
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string; id: string };
const body = request.body as UpdateRegimenInput;
2026-03-28 18:25:49 +09:00
const regimen = await service.update(
2026-05-19 16:15:15 +09:00
params.id,
params.householdId,
2026-03-28 18:25:49 +09:00
request.user.keycloakId,
2026-05-19 16:15:15 +09:00
body,
2026-03-28 18:25:49 +09:00
);
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');
2026-05-19 16:15:15 +09:00
const params = request.params as { householdId: string; id: string };
await service.delete(params.id, params.householdId, request.user.keycloakId);
2026-03-28 18:25:49 +09:00
return reply.status(204).send();
},
});
},
{
name: 'regimens-routes',
dependencies: ['auth-plugin'],
},
);