import type { Actions } from './$types'; import { COUCHDB_DB_NAME, S3_BUCKET_NAME } from '$env/static/private'; import { fail, redirect } from '@sveltejs/kit'; import { fileTreeSchema } from '$lib/types-zod'; import { get_blobs } from '$lib/utils'; import type { Modpack } from '$lib/types'; import { nanoid } from 'nanoid'; import { minioClient, nano } from '$lib/server/clients'; export const actions: Actions = { default: async (event) => { const session = await event.locals.getSession(); if (!session?.user) throw redirect(303, '/auth/signin'); const data = await event.request.formData(); const payload = data.get('payload'); const title = data.get('title'); if (typeof payload != 'string') throw fail(400); if (typeof title != 'string') throw fail(400); const payload_decoded = fileTreeSchema.safeParse(JSON.parse(payload)); if (!payload_decoded.success) { throw fail(400); } const blobs = get_blobs(payload_decoded.data); for (const blob of blobs) { const file = data.get(blob.sha512); if (typeof file != 'object' || !file) throw fail(400); if (file.size > 8 * 1024 * 1024) throw fail(400); const buf = await file.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-512', buf); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hash = hashArray .map((b) => b.toString(16).padStart(2, '0')) .join('') .toLowerCase(); if (hash != blob.sha512) throw fail(400); let exists = false; try { await minioClient.statObject(S3_BUCKET_NAME, blob.sha512); exists = true; } catch { // failed to stat. file doesn't exist } if (!exists) { await minioClient.putObject(S3_BUCKET_NAME, hash, Buffer.from(buf)); } } const document: Modpack = { type: 'modpack', _id: nanoid(), name: title, author: session.user.email ?? 'unknown@nodomain', revisions: [ { id: nanoid(), type: 'base', tree: payload_decoded.data, message: 'initial revision' } ] }; await nano.use(COUCHDB_DB_NAME).insert(document); } };