Prepare production and Coolify deployment with original exports and datetime pickers

This commit is contained in:
2026-09-09 17:59:20 -04:00
parent 574f29a68e
commit 64d7acc9c0
38 changed files with 1296 additions and 8 deletions
@@ -16,11 +16,13 @@ import {
groupMemberships,
guests,
photos,
photoExports,
submissions,
user,
} from "@album/database";
import {
createEventInputSchema,
exportPhotosInputSchema,
checkEventSlugInputSchema,
generateEventSlugInputSchema,
locationSearchInputSchema,
@@ -79,6 +81,30 @@ async function signedPhotoUrls(photo: {
}
export const managerRouter = createTRPCRouter({
requestExport: protectedProcedure.input(exportPhotosInputSchema).mutation(async ({ctx,input}) => {
const {access} = await loadEventAccess(ctx.session.user.id,input.eventId,await getPlatformRole(ctx.session.user.id));
requireEventPermission(access.permissions,EVENT_PERMISSIONS.SETTINGS_MANAGE);
requireEventPermission(access.permissions,EVENT_PERMISSIONS.PHOTOS_PRIVATE_READ);
const [job] = await getDb().insert(photoExports).values({eventId:input.eventId,requestedBy:ctx.session.user.id,filter:input.filter,expiresAt:new Date(Date.now()+86400000)}).onConflictDoNothing().returning({id:photoExports.id});
if (!job) throw new TRPCError({code:"CONFLICT",message:"An export is already queued or processing."});
await writeAudit({eventId:input.eventId,actorUserId:ctx.session.user.id,action:"photo.export.requested",subjectType:"export",subjectId:job.id,metadata:{filter:input.filter}});
return job;
}),
exports: protectedProcedure.input(z.object({eventId:z.string().uuid()})).query(async ({ctx,input}) => {
const {access} = await loadEventAccess(ctx.session.user.id,input.eventId,await getPlatformRole(ctx.session.user.id));
requireEventPermission(access.permissions,EVENT_PERMISSIONS.SETTINGS_MANAGE);
requireEventPermission(access.permissions,EVENT_PERMISSIONS.PHOTOS_PRIVATE_READ);
return getDb().select({id:photoExports.id,status:photoExports.status,filter:photoExports.filter,total:photoExports.total,processed:photoExports.processed,expiresAt:photoExports.expiresAt}).from(photoExports)
.where(and(eq(photoExports.eventId,input.eventId),eq(photoExports.requestedBy,ctx.session.user.id))).orderBy(desc(photoExports.createdAt)).limit(10);
}),
downloadExport: protectedProcedure.input(z.object({eventId:z.string().uuid(),exportId:z.string().uuid()})).mutation(async ({ctx,input}) => {
const {access} = await loadEventAccess(ctx.session.user.id,input.eventId,await getPlatformRole(ctx.session.user.id));
requireEventPermission(access.permissions,EVENT_PERMISSIONS.SETTINGS_MANAGE);
requireEventPermission(access.permissions,EVENT_PERMISSIONS.PHOTOS_PRIVATE_READ);
const [job] = await getDb().select().from(photoExports).where(and(eq(photoExports.eventId,input.eventId),eq(photoExports.id,input.exportId),eq(photoExports.requestedBy,ctx.session.user.id)));
if (!job || job.status !== "ready" || job.expiresAt <= new Date()) throw new TRPCError({code:"NOT_FOUND",message:"Export is unavailable or expired."});
return {url:await createPresignedGetUrl(`exports/${input.eventId}/${job.id}.zip`,Math.max(1,Math.min(300,Math.floor((job.expiresAt.getTime()-Date.now())/1000))))};
}),
searchLocations: protectedProcedure.input(locationSearchInputSchema).query(async ({ ctx, input }) => {
const platformRole = await getPlatformRole(ctx.session.user.id);
const { access } = await loadEventAccess(ctx.session.user.id, input.eventId, platformRole);