import { z } from 'zod' import { TRPCError } from '@trpc/server' import { router, adminProcedure } from '../trpc' import { generateLogoKey, type StorageProviderType } from '@/lib/storage' import { getImageUploadUrl, confirmImageUpload, getImageUrl, deleteImage, type ImageUploadConfig, } from '../utils/image-upload' type LogoSelect = { logoKey: string | null logoProvider: string | null } const logoConfig: ImageUploadConfig = { label: 'logo', generateKey: generateLogoKey, findCurrent: (prisma, entityId) => prisma.project.findUnique({ where: { id: entityId }, select: { logoKey: true, logoProvider: true }, }), getImageKey: (record) => record.logoKey, getProviderType: (record) => (record.logoProvider as StorageProviderType) || 's3', setImage: (prisma, entityId, key, providerType) => prisma.project.update({ where: { id: entityId }, data: { logoKey: key, logoProvider: providerType }, }), clearImage: (prisma, entityId) => prisma.project.update({ where: { id: entityId }, data: { logoKey: null, logoProvider: null }, }), auditEntityType: 'Project', auditFieldName: 'logoKey', } export const logoRouter = router({ /** * Get a pre-signed URL for uploading a project logo */ getUploadUrl: adminProcedure .input( z.object({ projectId: z.string(), fileName: z.string(), contentType: z.string(), }) ) .mutation(async ({ ctx, input }) => { // Verify project exists const project = await ctx.prisma.project.findUnique({ where: { id: input.projectId }, select: { id: true }, }) if (!project) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Project not found' }) } return getImageUploadUrl( input.projectId, input.fileName, input.contentType, generateLogoKey ) }), /** * Confirm logo upload and update project */ confirmUpload: adminProcedure .input( z.object({ projectId: z.string(), key: z.string(), providerType: z.enum(['s3', 'local']), }) ) .mutation(async ({ ctx, input }) => { await confirmImageUpload( ctx.prisma, logoConfig, input.projectId, input.key, input.providerType, { userId: ctx.user.id, ip: ctx.ip, userAgent: ctx.userAgent, } ) // Return the updated project fields to match original API contract const project = await ctx.prisma.project.findUnique({ where: { id: input.projectId }, select: { id: true, logoKey: true, logoProvider: true, }, }) return project }), /** * Get a project's logo URL */ getUrl: adminProcedure .input(z.object({ projectId: z.string() })) .query(async ({ ctx, input }) => { return getImageUrl(ctx.prisma, logoConfig, input.projectId) }), /** * Delete a project's logo */ delete: adminProcedure .input(z.object({ projectId: z.string() })) .mutation(async ({ ctx, input }) => { return deleteImage(ctx.prisma, logoConfig, input.projectId, { userId: ctx.user.id, ip: ctx.ip, userAgent: ctx.userAgent, }) }), })