From da46f84ab88ac5900d5e660e1db0f9706f347703 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Tue, 7 Apr 2026 11:27:18 +0500 Subject: [PATCH] Add S3 client for Hetzner Object Storage --- .env.example | 4 ++-- src/lib/s3.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/lib/s3.ts diff --git a/.env.example b/.env.example index 82d89ef..5eb44c0 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,8 @@ BETTER_AUTH_URL="http://localhost:3000" RESEND_API_KEY="" EMAIL_FROM="noreply@school.second-brain.ru" -S3_ENDPOINT="https://fsn1.your-objectstorage.com" -S3_BUCKET="lms-uploads" +S3_ENDPOINT="https://nbg1.your-objectstorage.com" +S3_BUCKET="second-brain-lms" S3_ACCESS_KEY="" S3_SECRET_KEY="" S3_REGION="eu-central" diff --git a/src/lib/s3.ts b/src/lib/s3.ts new file mode 100644 index 0000000..b9131b7 --- /dev/null +++ b/src/lib/s3.ts @@ -0,0 +1,38 @@ +import { S3Client, PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +export const s3 = new S3Client({ + endpoint: process.env.S3_ENDPOINT!, + region: process.env.S3_REGION ?? "eu-central", + credentials: { + accessKeyId: process.env.S3_ACCESS_KEY!, + secretAccessKey: process.env.S3_SECRET_KEY!, + }, + forcePathStyle: true, +}); + +const BUCKET = process.env.S3_BUCKET!; + +export function getPublicUrl(key: string): string { + return `${process.env.S3_ENDPOINT}/${BUCKET}/${key}`; +} + +export async function uploadFile( + key: string, + body: Buffer | Uint8Array, + contentType: string +): Promise { + await s3.send( + new PutObjectCommand({ + Bucket: BUCKET, + Key: key, + Body: body, + ContentType: contentType, + }) + ); + return getPublicUrl(key); +} + +export async function deleteFile(key: string): Promise { + await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key })); +}