import type { FastifyInstance } from 'fastify'; import { updateProfileSchema } from '@family-wishlist/shared'; import { ConflictError, NotFoundError, ValidationError } from '../../utils/errors.js'; import { Prisma } from '@prisma/client'; import { deleteLocalImageIfAny, saveUploadedAvatar } from '../images/storage.service.js'; const MAX_AVATAR_BYTES = 2 * 1024 * 1024; export default async function profileRoutes(app: FastifyInstance) { app.addHook('preHandler', app.authenticate); app.get('/', async (request) => { const profile = await app.prisma.user.findUnique({ where: { id: request.user.id } }); if (!profile) throw new NotFoundError('Profile'); return profile; }); app.patch('/', async (request) => { const body = updateProfileSchema.parse(request.body); try { const updated = await app.prisma.user.update({ where: { id: request.user.id }, data: body, }); return updated; } catch (err) { if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') { throw new ConflictError('Slug is already taken'); } throw err; } }); app.post('/avatar', async (request) => { const current = await app.prisma.user.findUnique({ where: { id: request.user.id } }); if (!current) throw new NotFoundError('Profile'); const data = await request.file(); if (!data) throw new ValidationError('No file uploaded'); const buffer = await data.toBuffer(); if (buffer.byteLength > MAX_AVATAR_BYTES) { throw new ValidationError('Avatar must be 2 MB or less'); } const { imageUrl } = await saveUploadedAvatar(request.user.id, data.mimetype, buffer); await deleteLocalImageIfAny(current.avatarUrl); return app.prisma.user.update({ where: { id: request.user.id }, data: { avatarUrl: imageUrl }, }); }); }