mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import {NextResponse} from "next/server";
|
|
import {auth} from "@/lib/auth/auth";
|
|
import {headers} from "next/headers";
|
|
import {checkFileExistsInBucket, getObjectFromClient} from "@/utils/s3-file-management";
|
|
import {env} from "@/env.mjs";
|
|
import * as stream from "node:stream";
|
|
import path from "path";
|
|
import {db} from "@/db";
|
|
import * as drizzleDb from "@/db";
|
|
import {eq} from "drizzle-orm";
|
|
import fs from "fs/promises";
|
|
|
|
function nodeStreamToWebStream(nodeStream: stream.Readable) {
|
|
return new ReadableStream({
|
|
start(controller) {
|
|
nodeStream.on("data", chunk => controller.enqueue(chunk));
|
|
nodeStream.on("end", () => controller.close());
|
|
nodeStream.on("error", err => controller.error(err));
|
|
},
|
|
cancel() {
|
|
nodeStream.destroy();
|
|
},
|
|
});
|
|
}
|
|
|
|
const privateS3ImageDir = "images/";
|
|
|
|
|
|
export async function GET(
|
|
req: Request,
|
|
{params}: { params: Promise<{ fileName: string }> }
|
|
) {
|
|
const fileName = (await params).fileName;
|
|
if (!fileName) return NextResponse.json({error: "Missing file parameter"}, {status: 400});
|
|
|
|
const session = await auth.api.getSession({headers: await headers()});
|
|
if (!session) return NextResponse.json({error: "Unauthorized"}, {status: 403});
|
|
|
|
const [settings] = await db
|
|
.select()
|
|
.from(drizzleDb.schemas.setting)
|
|
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
|
.limit(1);
|
|
|
|
if (!settings) throw new Error("System settings not found.");
|
|
|
|
const storageType = settings.storage; // "local" or "s3"
|
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
const contentType =
|
|
ext === "png"
|
|
? "image/png"
|
|
: ext === "jpg" || ext === "jpeg"
|
|
? "image/jpeg"
|
|
: ext === "gif"
|
|
? "image/gif"
|
|
: ext === "webp"
|
|
? "image/webp"
|
|
: "application/octet-stream";
|
|
|
|
try {
|
|
if (storageType === "local") {
|
|
const filePath = path.join(process.cwd(), "private/uploads/images", fileName);
|
|
|
|
try {
|
|
await fs.access(filePath);
|
|
const file = await fs.readFile(filePath);
|
|
|
|
return new NextResponse(file, {
|
|
headers: {
|
|
"Content-Type": contentType,
|
|
"Cache-Control": "no-store",
|
|
"Content-Disposition": `inline; filename="${fileName}"`,
|
|
},
|
|
});
|
|
} catch {
|
|
// if not found locally, fallback to S3
|
|
}
|
|
}
|
|
|
|
const exists = await checkFileExistsInBucket({
|
|
bucketName: env.S3_BUCKET_NAME!,
|
|
fileName: `${privateS3ImageDir}${fileName}`,
|
|
});
|
|
if (!exists) return NextResponse.json({error: "File not found"}, {status: 404});
|
|
|
|
const nodeStream = await getObjectFromClient({
|
|
bucketName: env.S3_BUCKET_NAME!,
|
|
fileName: `${privateS3ImageDir}${fileName}`,
|
|
});
|
|
const webStream = nodeStreamToWebStream(nodeStream);
|
|
|
|
return new NextResponse(webStream, {
|
|
headers: {
|
|
"Content-Type": contentType,
|
|
"Cache-Control": "no-store",
|
|
"Content-Disposition": `inline; filename="${fileName}"`,
|
|
},
|
|
});
|
|
} catch (err) {
|
|
console.error("Error streaming image:", err);
|
|
return NextResponse.json({error: "Error fetching file"}, {status: 500});
|
|
}
|
|
} |