82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
|
|
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<ZodTypeProvider>();
|
||
|
|
|
||
|
|
// 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'] },
|
||
|
|
);
|