import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3" import { getSignedUrl } from "@aws-sdk/s3-request-presigner" import { nanoid } from "nanoid" import { getS3Client, getUploadBucket } from "@/lib/storage/s3" function safeFilename(filename: string) { const normalized = filename .normalize("NFKD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-zA-Z0-9._-]/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, "") return normalized || "upload" } export function createInvitationUploadKey(invitationId: string, filename: string) { return `invitations/${invitationId}/${nanoid(12)}-${safeFilename(filename)}` } export function isObjectKeyForInvitation(objectKey: string, invitationId: string) { return objectKey.startsWith(`invitations/${invitationId}/`) } export async function createPresignedUploadUrl({ objectKey, contentType, sizeBytes, }: { objectKey: string contentType: string sizeBytes: number }) { const client = getS3Client() const bucket = getUploadBucket() return getSignedUrl( client, new PutObjectCommand({ Bucket: bucket, Key: objectKey, ContentType: contentType, ContentLength: sizeBytes, }), { expiresIn: 60 * 5 }, ) } export async function createPresignedDownloadUrl(objectKey: string) { const client = getS3Client() const bucket = getUploadBucket() return getSignedUrl( client, new GetObjectCommand({ Bucket: bucket, Key: objectKey, }), { expiresIn: 60 * 10 }, ) } export async function downloadUploadObject(objectKey: string, maxSizeBytes: number) { const response = await getS3Client().send(new GetObjectCommand({ Bucket: getUploadBucket(), Key: objectKey, })) const body = response.Body if (!body) throw new Error("Uploaded object has no body") if ("transformToByteArray" in body && typeof body.transformToByteArray === "function") { const bytes = await body.transformToByteArray() if (bytes.byteLength > maxSizeBytes) throw new Error("Uploaded object is too large") return Buffer.from(bytes) } const chunks: Buffer[] = [] let size = 0 for await (const chunk of body as AsyncIterable) { const buffer = Buffer.from(chunk) size += buffer.length if (size > maxSizeBytes) throw new Error("Uploaded object is too large") chunks.push(buffer) } return Buffer.concat(chunks, size) } export async function putUploadObject({ objectKey, contentType, body, }: { objectKey: string contentType: string body: Buffer }) { await getS3Client().send(new PutObjectCommand({ Bucket: getUploadBucket(), Key: objectKey, Body: body, ContentType: contentType, ContentLength: body.length, })) } export async function getUploadedObjectMetadata(objectKey: string) { const response = await getS3Client().send(new HeadObjectCommand({ Bucket: getUploadBucket(), Key: objectKey, })) return { contentType: response.ContentType, sizeBytes: response.ContentLength } } export async function deleteUploadObject(objectKey: string) { const client = getS3Client() const bucket = getUploadBucket() await client.send( new DeleteObjectCommand({ Bucket: bucket, Key: objectKey, }), ) }