From ea2749e8c90889f5eab463792bd5532b4c4902b1 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:47:09 +0800 Subject: [PATCH] [O] Use header key --- src/index.ts | 143 +++++++++++++++++++++------------------------ src/test-client.ts | 5 +- 2 files changed, 68 insertions(+), 80 deletions(-) diff --git a/src/index.ts b/src/index.ts index 862798b..fff2f03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,21 +17,20 @@ interface PhotoMetadata { edited_photo: { filename: string, path: string, exif: any } } +class HttpError extends Error { + constructor(public status: number, message: string) { + super(message) + } +} + /** * Loads the metadata.json file. * Returns an empty array if the file doesn't exist. */ async function getMetadata(): Promise { const file = Bun.file(FILE_METADATA) - if (!(await file.exists())) { - return [] // Return empty array if no metadata file yet - } - try { - return await file.json() - } catch (e) { - console.warn(`Warning: Could not parse 'metadata.json'. Returning empty array.`, e) - return [] - } + if (!(await file.exists())) return [] + return await file.json() } /** @@ -66,89 +65,77 @@ async function getExifData(file: File): Promise { // --- Elysia Server --- // Load secrets and ensure directories exist before starting -const INSTANT_KEY = process.env.INSTANT_KEY -if (!INSTANT_KEY) throw new Error("INSTANT_KEY is not defined") +const INSTANT_KEY = process.env.INSTANT_KEY! if (!await exists(FOLDER_PHOTOS)) await mkdir(FOLDER_PHOTOS, { recursive: true }) console.log("Server starting with valid 'secrets.json'.") -export const app = new Elysia() - .post("/upload", async ({ body, status }) => { - const { key, id, owner_key, photo, edited_photo } = body +function checkHeaderKey(headers: Record, expectList: string[] = [INSTANT_KEY]) { + const key = headers["x-instant-key"] + if (!expectList.includes(key ?? "")) throw new HttpError(401, "Invalid authentication key") +} - // 1. Authentication - if (key !== INSTANT_KEY) status(401, "Invalid authentication key") +export const app = new Elysia() + // Error handling: Return status code and message + .onError(({ error, status }) => { + console.error(error) + + if (error instanceof HttpError) return status(error.status, { error: error.message }) + else return status(500, { error: "Internal Server Error" }) + }) + + .post("/upload", async ({ body, headers, status }) => { + const { id, owner_key, photo, edited_photo } = body + checkHeaderKey(headers) // 2. Check for existing ID const metadata = await getMetadata() if (metadata.find((m) => m.id === id)) status(409, "This ID already exists") - try { - // 3. Process and save files - // We use {id}-1 and {id}-2 as requested by "{id}-{number}" - const originalExt = photo.name.split(".").pop() || "jpg" - const editedExt = edited_photo.name.split(".").pop() || "jpg" + // 3. Process and save files + // We use {id}-1 and {id}-2 as requested by "{id}-{number}" + const originalExt = photo.name.split(".").pop() || "jpg" + const editedExt = edited_photo.name.split(".").pop() || "jpg" - // Using "1" for original and "2" for edited - const originalPath = `${FOLDER_PHOTOS}/${id}-1.${originalExt}` - const editedPath = `${FOLDER_PHOTOS}/${id}-2.${editedExt}` + // Using "1" for original and "2" for edited + const originalPath = `${FOLDER_PHOTOS}/${id}-1.${originalExt}` + const editedPath = `${FOLDER_PHOTOS}/${id}-2.${editedExt}` - // Run file saving and EXIF parsing in parallel - const [originalExif, editedExif] = await Promise.all([ - getExifData(photo), - getExifData(edited_photo), - Bun.write(originalPath, photo), - Bun.write(editedPath, edited_photo), - ]) + // Run file saving and EXIF parsing in parallel + const [originalExif, editedExif] = await Promise.all([ + getExifData(photo), + getExifData(edited_photo), + Bun.write(originalPath, photo), + Bun.write(editedPath, edited_photo), + ]) - // 4. Create new metadata entry - const newEntry: PhotoMetadata = { - id: id, - owner_key: owner_key, - upload_time: new Date().toISOString(), - original_photo: { - filename: photo.name, - path: originalPath, - exif: originalExif, - }, - edited_photo: { - filename: edited_photo.name, - path: editedPath, - exif: editedExif, - }, - } - - // 5. Save updated metadata - metadata.push(newEntry) - await saveMetadata(metadata) - - return status(201, { success: true, id: newEntry.id }) - } catch (error) { - console.error("File upload processing error:", error) - return status(500, { error: "Failed to process file upload." }) + // 4. Create new metadata entry + const newEntry: PhotoMetadata = { + id: id, + owner_key: owner_key, + upload_time: new Date().toISOString(), + original_photo: { + filename: photo.name, + path: originalPath, + exif: originalExif, + }, + edited_photo: { + filename: edited_photo.name, + path: editedPath, + exif: editedExif, + }, } - }, - { + + // 5. Save updated metadata + metadata.push(newEntry) + await saveMetadata(metadata) + + return status(201, { success: true, id: newEntry.id }) + }, { // --- Validation Schema --- - // We interpret "Part 1: JSON body" as the text fields of - // the multipart form, which is the standard way to send - // metadata alongside files. body: t.Object({ - key: t.String(), - id: t.String({ - minLength: 3, - maxLength: 20, - // Simple pattern: letters, numbers, hyphens - pattern: "^[a-zA-Z0-9_-]+$", - error: - "ID must be 3-20 alphanumeric characters, hyphens, or underscores.", - }), - owner_key: t.String({ - minLength: 4, - maxLength: 4, - pattern: "^[0-9]{4}$", // e.g., "2941" - error: "Owner key must be exactly 4 digits.", - }), + id: t.String(), + owner_key: t.String(), photo: t.File({ type: ["image/jpeg", "image/png", "image/webp", "image/avif", "image/heic", "image/heif"], error: "Original photo must be a valid image (jpg, png, webp, avif, heic, heif).", @@ -174,6 +161,10 @@ export const app = new Elysia() return publicMetadata }) + // ----- 3. GET /photos/:id ----- + // Returns the static file for the *edited* photo. + // Respects the 'hide' flag. + .listen(3000) console.log(`🦊 Elysia server running at http://${app.server?.hostname}:${app.server?.port}`) diff --git a/src/test-client.ts b/src/test-client.ts index c8c74a3..b594d51 100644 --- a/src/test-client.ts +++ b/src/test-client.ts @@ -36,12 +36,8 @@ async function testUpload() { // 1. Create the multipart/form-data payload const formData = new FormData(); - formData.append("key", API_KEY); formData.append("id", TEST_ID); formData.append("owner_key", TEST_OWNER_KEY); - - // Append the files - // We'll use the same dummy image for both "original" and "edited" formData.append("photo", testFile); formData.append("edited_photo", testFile); @@ -50,6 +46,7 @@ async function testUpload() { const response = await fetch(`${SERVER_URL}/upload`, { method: "POST", body: formData, + headers: { "x-instant-key": API_KEY } }); // 3. Log the response