import fp from 'fastify-plugin'; import { asClass, Lifetime } from 'awilix'; import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { z } from 'zod/v4'; import { CreateStoreSchema, UpdateStoreSchema, StoreQuerySchema, StoreResponseSchema, StoreListResponseSchema, } from '@meshitrack/shared'; import { StoresRepository } from './stores.repository.js'; import { StoresService } from './stores.service.js'; type AnyStoreDoc = { _id: string | { toString: () => string }; householdId: string; name: string; address?: string; location?: { lat: number; lng: number }; url?: string; notes?: string; tags: string[]; isActive: boolean; createdBy: string; createdAt: string | Date | { toISOString: () => string }; updatedAt: string | Date | { toISOString: () => string }; }; function toIso(v: string | Date | { toISOString: () => string }): string { if (typeof v === 'string') return v; return v.toISOString(); } function toStoreResponse(rawDoc: unknown) { const doc = rawDoc as AnyStoreDoc; return { _id: typeof doc._id === 'string' ? doc._id : doc._id.toString(), householdId: doc.householdId, name: doc.name, ...(doc.address != null ? { address: doc.address } : {}), ...(doc.location != null ? { location: doc.location } : {}), ...(doc.url != null ? { url: doc.url } : {}), ...(doc.notes != null ? { notes: doc.notes } : {}), tags: doc.tags, isActive: doc.isActive, createdBy: doc.createdBy, createdAt: toIso(doc.createdAt), updatedAt: toIso(doc.updatedAt), }; } declare module '@fastify/awilix' { interface Cradle { storesRepository: StoresRepository; storesService: StoresService; } } export default fp( async (fastify) => { fastify.diContainer.register({ storesRepository: asClass(StoresRepository, { lifetime: Lifetime.SINGLETON }), storesService: asClass(StoresService, { lifetime: Lifetime.SINGLETON }), }); const app = fastify.withTypeProvider(); const householdParams = z.object({ householdId: z.string() }); app.route({ method: 'GET', url: '/api/v1/households/:householdId/stores', schema: { params: householdParams, querystring: StoreQuerySchema, response: { 200: StoreListResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('storesService'); const result = await service.list(request.params.householdId, request.query); return reply.send({ data: result.data.map(toStoreResponse), pagination: result.pagination, }); }, }); app.route({ method: 'GET', url: '/api/v1/households/:householdId/stores/:id', schema: { params: householdParams.extend({ id: z.string() }), response: { 200: StoreResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('storesService'); const store = await service.getById(request.params.id, request.params.householdId); return reply.send(toStoreResponse(store)); }, }); app.route({ method: 'POST', url: '/api/v1/households/:householdId/stores', schema: { params: householdParams, body: CreateStoreSchema, response: { 201: StoreResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('storesService'); const store = await service.create( request.body, request.params.householdId, request.user.keycloakId, ); return reply.status(201).send(toStoreResponse(store)); }, }); app.route({ method: 'PATCH', url: '/api/v1/households/:householdId/stores/:id', schema: { params: householdParams.extend({ id: z.string() }), body: UpdateStoreSchema, response: { 200: StoreResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('storesService'); const store = await service.update( request.params.id, request.params.householdId, request.body, ); return reply.send(toStoreResponse(store)); }, }); app.route({ method: 'DELETE', url: '/api/v1/households/:householdId/stores/:id', schema: { params: householdParams.extend({ id: z.string() }), response: { 200: StoreResponseSchema }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('storesService'); const store = await service.deactivate(request.params.id, request.params.householdId); return reply.send(toStoreResponse(store)); }, }); }, { name: 'stores-routes', dependencies: ['auth-plugin'], }, );