+33
-16
@@ -20,6 +20,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { randomUUID } from "crypto";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
export type StorageProvider = "local" | "uploadthing" | "s3" | "r2" | "b2" | "minio";
|
||||
|
||||
@@ -222,19 +223,35 @@ function getPublicUrl(key: string, provider: StorageProvider): string {
|
||||
|
||||
// ── Hetzner Object Storage (dedicated high-res bucket) ───────────────────────
|
||||
|
||||
function buildHetznerClient(): S3Client {
|
||||
return new S3Client({
|
||||
region: "auto",
|
||||
endpoint: process.env.HETZNER_ENDPOINT!,
|
||||
credentials: {
|
||||
accessKeyId: process.env.HETZNER_ACCESS_KEY!,
|
||||
secretAccessKey: process.env.HETZNER_SECRET_KEY!,
|
||||
},
|
||||
async function getHetznerConfig(): Promise<{
|
||||
endpoint: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
bucket: string;
|
||||
}> {
|
||||
const rows = await db.systemConfig.findMany({
|
||||
where: { key: { in: ["hetzner_endpoint", "hetzner_access_key", "hetzner_secret_key", "hetzner_bucket_name"] } },
|
||||
});
|
||||
const map = Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
||||
return {
|
||||
endpoint: map["hetzner_endpoint"] ?? process.env.HETZNER_ENDPOINT ?? "",
|
||||
accessKey: map["hetzner_access_key"] ?? process.env.HETZNER_ACCESS_KEY ?? "",
|
||||
secretKey: map["hetzner_secret_key"] ?? process.env.HETZNER_SECRET_KEY ?? "",
|
||||
bucket: map["hetzner_bucket_name"] ?? process.env.HETZNER_BUCKET_NAME ?? "vfx-review",
|
||||
};
|
||||
}
|
||||
|
||||
function getHetznerBucket(): string {
|
||||
return process.env.HETZNER_BUCKET_NAME ?? "vfx-review";
|
||||
async function buildHetznerClient(): Promise<{ client: S3Client; bucket: string }> {
|
||||
const cfg = await getHetznerConfig();
|
||||
const client = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: cfg.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: cfg.accessKey,
|
||||
secretAccessKey: cfg.secretKey,
|
||||
},
|
||||
});
|
||||
return { client, bucket: cfg.bucket };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,10 +265,10 @@ export async function uploadToHetzner(
|
||||
folder: string = "highres"
|
||||
): Promise<{ key: string }> {
|
||||
const key = `${folder}/${randomUUID()}-${fileName}`;
|
||||
const client = buildHetznerClient();
|
||||
const { client, bucket } = await buildHetznerClient();
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: getHetznerBucket(),
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: contentType,
|
||||
@@ -269,9 +286,9 @@ export async function generateHetznerDownloadUrl(
|
||||
originalFilename: string,
|
||||
expiresIn: number = 3600
|
||||
): Promise<string> {
|
||||
const client = buildHetznerClient();
|
||||
const { client, bucket } = await buildHetznerClient();
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: getHetznerBucket(),
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
ResponseContentDisposition: `attachment; filename="${originalFilename}"`,
|
||||
});
|
||||
@@ -282,8 +299,8 @@ export async function generateHetznerDownloadUrl(
|
||||
* Delete a file from the Hetzner bucket by key.
|
||||
*/
|
||||
export async function deleteFromHetzner(key: string): Promise<void> {
|
||||
const client = buildHetznerClient();
|
||||
const { client, bucket } = await buildHetznerClient();
|
||||
await client.send(
|
||||
new DeleteObjectCommand({ Bucket: getHetznerBucket(), Key: key })
|
||||
new DeleteObjectCommand({ Bucket: bucket, Key: key })
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user