2026-02-14 15:26:42 +01:00
|
|
|
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<LogoSelect> = {
|
|
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
})
|