import { TRPCError } from '@trpc/server'; import { createPresignedUpload, publicUrl, uploadRequestSchema, StorageError } from '@linkder/storage'; import { protectedProcedure, router } from '../trpc'; /** * Hands out short-lived presigned PUTs so the browser uploads straight to R2. * * The server picks the key, so a caller can only ever write under their own * user id — they cannot overwrite someone else's document by guessing a path. */ export const uploadRouter = router({ presign: protectedProcedure.input(uploadRequestSchema).mutation(async ({ ctx, input }) => { try { const upload = await createPresignedUpload({ ...input, ownerId: ctx.session.userId }); return { ...upload, // Credentials are private; the caller stores the key and admins read it // through a signed GET rather than a public URL. publicUrl: input.kind === 'credential' ? null : publicUrl(upload.key), }; } catch (error) { if (error instanceof StorageError) { throw new TRPCError({ code: 'BAD_REQUEST', message: error.message }); } throw error; } }), });