@@ -219,3 +219,71 @@ function getPublicUrl(key: string, provider: StorageProvider): string {
|
||||
// Fallback
|
||||
return `/${key}`;
|
||||
}
|
||||
|
||||
// ── 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!,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getHetznerBucket(): string {
|
||||
return process.env.HETZNER_BUCKET_NAME ?? "vfx-review";
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a buffer directly to the Hetzner bucket (always uses Hetzner credentials,
|
||||
* regardless of the global STORAGE_PROVIDER setting).
|
||||
*/
|
||||
export async function uploadToHetzner(
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
contentType: string,
|
||||
folder: string = "highres"
|
||||
): Promise<{ key: string }> {
|
||||
const key = `${folder}/${randomUUID()}-${fileName}`;
|
||||
const client = buildHetznerClient();
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: getHetznerBucket(),
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: contentType,
|
||||
})
|
||||
);
|
||||
return { key };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned download URL from Hetzner that forces a file download
|
||||
* with the original filename preserved via Content-Disposition.
|
||||
*/
|
||||
export async function generateHetznerDownloadUrl(
|
||||
key: string,
|
||||
originalFilename: string,
|
||||
expiresIn: number = 3600
|
||||
): Promise<string> {
|
||||
const client = buildHetznerClient();
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: getHetznerBucket(),
|
||||
Key: key,
|
||||
ResponseContentDisposition: `attachment; filename="${originalFilename}"`,
|
||||
});
|
||||
return getSignedUrl(client, command, { expiresIn });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from the Hetzner bucket by key.
|
||||
*/
|
||||
export async function deleteFromHetzner(key: string): Promise<void> {
|
||||
const client = buildHetznerClient();
|
||||
await client.send(
|
||||
new DeleteObjectCommand({ Bucket: getHetznerBucket(), Key: key })
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user