- Add MinIO service to compose.yaml with named volume - Add generic /api/uploads POST+GET route with auth, 5 MB, image/* guards - Add src/lib/minio.ts singleton client with ensureBucket helper - Add garden Drizzle schema: containers, plants, care_logs, care_schedules, species_cache - Register garden module in src/modules/index.ts and src/lib/db.ts - Apply migration 0015_garden_schema.sql - Add MINIO_* and PERENUAL_API_KEY to .env.example - Add task briefs 70-75 for the full garden feature arc - Add uploads.spec.ts E2E test (RED → GREEN on route auth guard)
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { minioClient, MINIO_BUCKET } from "@/lib/minio";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
export async function GET(_request: Request, { params }: { params: Promise<{ key: string[] }> }) {
|
|
const { key } = await params;
|
|
const objectKey = key.join("/");
|
|
|
|
try {
|
|
const stat = await minioClient.statObject(MINIO_BUCKET, objectKey);
|
|
const stream = await minioClient.getObject(MINIO_BUCKET, objectKey);
|
|
|
|
const chunks: Buffer[] = [];
|
|
for await (const chunk of stream) {
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array));
|
|
}
|
|
const buffer = Buffer.concat(chunks);
|
|
|
|
const metaData = stat.metaData as Record<string, string> | undefined;
|
|
const contentType =
|
|
metaData?.["content-type"] ?? metaData?.["Content-Type"] ?? "application/octet-stream";
|
|
|
|
return new Response(buffer, {
|
|
headers: {
|
|
"Content-Type": contentType,
|
|
"Cache-Control": "public, max-age=31536000, immutable",
|
|
"Content-Length": String(buffer.length),
|
|
},
|
|
});
|
|
} catch {
|
|
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
}
|
|
}
|