import { z } from 'zod' import { router, protectedProcedure } from '../trpc' import { generateAvatarKey, type StorageProviderType } from '@/lib/storage' import { getImageUploadUrl, confirmImageUpload, getImageUrl, deleteImage, type ImageUploadConfig, } from '../utils/image-upload' type AvatarSelect = { profileImageKey: string | null profileImageProvider: string | null } const avatarConfig: ImageUploadConfig = { label: 'avatar', generateKey: generateAvatarKey, findCurrent: (prisma, entityId) => prisma.user.findUnique({ where: { id: entityId }, select: { profileImageKey: true, profileImageProvider: true }, }), getImageKey: (record) => record.profileImageKey, getProviderType: (record) => (record.profileImageProvider as StorageProviderType) || 's3', setImage: (prisma, entityId, key, providerType) => prisma.user.update({ where: { id: entityId }, data: { profileImageKey: key, profileImageProvider: providerType }, }), clearImage: (prisma, entityId) => prisma.user.update({ where: { id: entityId }, data: { profileImageKey: null, profileImageProvider: null }, }), auditEntityType: 'User', auditFieldName: 'profileImageKey', } export const avatarRouter = router({ /** * Get a pre-signed URL for uploading an avatar */ getUploadUrl: protectedProcedure .input( z.object({ fileName: z.string(), contentType: z.string(), }) ) .mutation(async ({ ctx, input }) => { return getImageUploadUrl( ctx.user.id, input.fileName, input.contentType, generateAvatarKey ) }), /** * Confirm avatar upload and update user profile */ confirmUpload: protectedProcedure .input( z.object({ key: z.string(), providerType: z.enum(['s3', 'local']), }) ) .mutation(async ({ ctx, input }) => { const userId = ctx.user.id await confirmImageUpload(ctx.prisma, avatarConfig, userId, input.key, input.providerType, { userId: ctx.user.id, ip: ctx.ip, userAgent: ctx.userAgent, }) // Return the updated user fields to match original API contract const user = await ctx.prisma.user.findUnique({ where: { id: userId }, select: { id: true, profileImageKey: true, profileImageProvider: true, }, }) return user }), /** * Get the current user's avatar URL */ getUrl: protectedProcedure.query(async ({ ctx }) => { return getImageUrl(ctx.prisma, avatarConfig, ctx.user.id) }), /** * Delete the current user's avatar */ delete: protectedProcedure.mutation(async ({ ctx }) => { return deleteImage(ctx.prisma, avatarConfig, ctx.user.id, { userId: ctx.user.id, ip: ctx.ip, userAgent: ctx.userAgent, }) }), })