- care reminders now fire with plant-specific body ("Time to water Pothos")
via title/body columns on the reminders table (migration 0016)
- bi-directional task sync: checking off a garden-linked task list item
creates a care log; logging care from garden marks the linked task done
(list_items.metadata stores gardenPlantId + gardenCareType linkage)
- upload error response no longer leaks debug detail; error message
corrected from "5 MB" to "100 MB"
63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { NextResponse } from "next/server";
|
|
import { getCurrentSession } from "@/lib/session";
|
|
import { ensureBucket, minioClient, MINIO_BUCKET } from "@/lib/minio";
|
|
|
|
export const runtime = "nodejs";
|
|
export const maxDuration = 60;
|
|
|
|
export const config = {
|
|
api: { bodyParser: { sizeLimit: "100mb" } },
|
|
};
|
|
|
|
const MAX_FILE_SIZE = 100 * 1024 * 1024;
|
|
|
|
export async function POST(request: Request) {
|
|
let session: Awaited<ReturnType<typeof getCurrentSession>>;
|
|
try {
|
|
session = await getCurrentSession();
|
|
} catch {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
let formData: FormData;
|
|
try {
|
|
formData = await request.formData();
|
|
} catch {
|
|
return NextResponse.json({ error: "Invalid multipart body" }, { status: 400 });
|
|
}
|
|
|
|
const file = formData.get("file");
|
|
if (!(file instanceof File)) {
|
|
return NextResponse.json({ error: "No file field in request" }, { status: 400 });
|
|
}
|
|
|
|
if (!file.type.startsWith("image/")) {
|
|
return NextResponse.json({ error: "Only image files are allowed" }, { status: 415 });
|
|
}
|
|
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
return NextResponse.json({ error: "File exceeds 100 MB limit" }, { status: 413 });
|
|
}
|
|
|
|
const rawExt = file.name.split(".").pop() ?? "bin";
|
|
const safeExt = rawExt
|
|
.replace(/[^a-z0-9]/gi, "")
|
|
.toLowerCase()
|
|
.slice(0, 8);
|
|
const key = `garden/${session.household.id}/${randomUUID()}.${safeExt}`;
|
|
|
|
try {
|
|
await ensureBucket();
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
await minioClient.putObject(MINIO_BUCKET, key, buffer, buffer.length, {
|
|
"Content-Type": file.type,
|
|
});
|
|
} catch (err) {
|
|
console.error("Upload failed", err);
|
|
return NextResponse.json({ error: "Upload service unavailable" }, { status: 503 });
|
|
}
|
|
|
|
return NextResponse.json({ url: `/api/uploads/${key}` });
|
|
}
|