diff --git a/frontend/src/routes/webhook/s3/+server.ts b/frontend/src/routes/webhook/s3/+server.ts index 0b0a259..c7b7e36 100644 --- a/frontend/src/routes/webhook/s3/+server.ts +++ b/frontend/src/routes/webhook/s3/+server.ts @@ -1,10 +1,21 @@ import { db } from '$lib/db' -import { shortener } from '$lib/db/schema' +import { file, shortener, user } from '$lib/db/schema' import type { RequestHandler } from './$types' import util from 'util' import { generateId } from 'lucia' import { nanoid } from 'nanoid' import { and, eq, isNull } from 'drizzle-orm' +import { env } from '$env/dynamic/private' +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3' + +const client = new S3Client({ + region: 'auto', + endpoint: `https://fly.storage.tigris.dev`, + credentials: { + accessKeyId: env.PRIVATE_AWS_ACCESS_KEY_ID, + secretAccessKey: env.PRIVATE_AWS_SECRET_ACCESS_KEY, + }, +}) type S3WebhookStructure = { Records: { @@ -51,6 +62,18 @@ type S3WebhookStructure = { } export const POST: RequestHandler = async (event) => { + const authHeader = event.request.headers.get('Authorization') + console.log('auth header:', authHeader) + + if ( + !authHeader || + (env.PRIVATE_AWS_WEBHOOK_TOKEN && + authHeader !== `Bearer ${env.PRIVATE_AWS_WEBHOOK_TOKEN}`) + ) { + console.log('Unauthorized') + return new Response('', { status: 401 }) + } + const body = (await event.request.json()) as S3WebhookStructure const eventHandlers = body.Records.map(async (event) => { @@ -58,9 +81,16 @@ export const POST: RequestHandler = async (event) => { util.inspect(event, false, null, true /* enable colors */), ) - if (event.eventName === 'ObjectCreated:Put') { - const key = event.s3.object.key - const [userId, projectId, filename] = key.split('/') + const key = event.s3.object.key + const [userId, projectId, filename] = key.split('/') + + const objectOwner = await db.query.user.findFirst({ + where: (user, { eq }) => eq(user.id, userId), + }) + + if (!objectOwner) return + + if (event.eventName === 's3:ObjectCreated:Put') { const existingShortener = await db.query.shortener.findFirst({ where: (shortener, { eq, and, isNull }) => and( @@ -73,34 +103,70 @@ export const POST: RequestHandler = async (event) => { ), }) - if (existingShortener) { - return + if (!existingShortener) { + await db.insert(shortener).values({ + id: generateId(8), + link: '', + code: nanoid(8), + projectId: projectId !== 'personal' ? projectId : undefined, + userId: userId, + is_file_upload: true, + file_path: key, + }) } - await db.insert(shortener).values({ - id: generateId(8), - link: '', - code: nanoid(8), - projectId: projectId !== 'personal' ? projectId : undefined, - userId: userId, - is_file_upload: true, - file_path: key, + const object = await client.send( + new GetObjectCommand({ + Bucket: env.PRIVATE_AWS_BUCKET_NAME, + Key: key, + }), + ) + + const existingFile = await db.query.file.findFirst({ + where: (file, { eq }) => eq(file.key, key), }) + + if (existingFile) { + await db + .update(file) + .set({ + size: object.ContentLength, + eTag: object.ETag, + updatedAt: Date.now(), + }) + .where(eq(file.id, existingFile.id)) + + await db + .update(user) + .set({ + fileStorageUsageInByte: + objectOwner.fileStorageUsageInByte + + (object.ContentLength! - existingFile.size), + }) + .where(eq(user.id, userId)) + } else { + await db.insert(file).values({ + id: generateId(8), + userId: userId, + projectId: projectId !== 'personal' ? projectId : undefined, + key: key, + name: filename, + size: object.ContentLength!, + eTag: object.ETag!, + createdAt: Date.now(), + updatedAt: Date.now(), + }) + + await db + .update(user) + .set({ + fileStorageUsageInByte: + objectOwner.fileStorageUsageInByte + + (object.ContentLength || 0), + }) + .where(eq(user.id, userId)) + } } else if (event.eventName === 's3:ObjectRemoved:Delete') { - const key = event.s3.object.key - const [userId, projectId, filename] = key.split('/') - await db - .delete(shortener) - .where( - and( - eq(shortener.userId, userId), - projectId !== 'personal' - ? eq(shortener.projectId, projectId) - : isNull(shortener.projectId), - eq(shortener.is_file_upload, true), - eq(shortener.file_path, key), - ), - ) } })