import fp from 'fastify-plugin'; import { asClass, Lifetime } from 'awilix'; import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { z } from 'zod/v4'; import { UsersRepository } from './users.repository.js'; import { UsersService } from './users.service.js'; import { NotFoundError } from '../../common/errors.js'; type AnyUserDoc = { _id: string | { toString: () => string }; keycloakId: string; displayName: string; email: string; householdIds: string[]; defaultHouseholdId?: string | null; createdAt: string | { toISOString: () => string }; updatedAt: string | { toISOString: () => string }; }; function toUserResponse(doc: AnyUserDoc) { const id = typeof doc._id === 'string' ? doc._id : doc._id.toString(); const createdAt = typeof doc.createdAt === 'string' ? doc.createdAt : doc.createdAt.toISOString(); const updatedAt = typeof doc.updatedAt === 'string' ? doc.updatedAt : doc.updatedAt.toISOString(); return { _id: id, keycloakId: doc.keycloakId, displayName: doc.displayName, email: doc.email, householdIds: doc.householdIds, defaultHouseholdId: doc.defaultHouseholdId ?? null, createdAt, updatedAt, }; } declare module '@fastify/awilix' { interface Cradle { usersRepository: UsersRepository; usersService: UsersService; } } export default fp( async (fastify) => { // Register DI fastify.diContainer.register({ usersRepository: asClass(UsersRepository, { lifetime: Lifetime.SINGLETON }), usersService: asClass(UsersService, { lifetime: Lifetime.SINGLETON }), }); const app = fastify.withTypeProvider(); // GET /api/v1/users/me — get current user profile (syncs from token on first call) app.route({ method: 'GET', url: '/api/v1/users/me', config: { skipHousehold: true }, schema: { response: { 200: z.object({ _id: z.string(), keycloakId: z.string(), displayName: z.string(), email: z.string(), householdIds: z.array(z.string()), defaultHouseholdId: z.string().nullable(), createdAt: z.string(), updatedAt: z.string(), }), }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('usersService'); const user = await service.syncFromToken(request.user); if (!user) throw new NotFoundError('User sync failed'); return reply.send(toUserResponse(user)); }, }); }, { name: 'users-routes', dependencies: ['auth-plugin'] }, );