From 21277a1aeb3af3e219b802f84119b13c4adcce44 Mon Sep 17 00:00:00 2001 From: Sanjib Sen Date: Wed, 15 Jan 2025 15:00:54 +0600 Subject: [PATCH] added more --- .gitignore | 5 +- Dockerfile | 6 +- bun.lockb | Bin 172458 -> 191850 bytes docker-compose.yaml | 36 ++++++++--- package.json | 2 + src/index.ts | 3 +- src/lib/storage/s3.ts | 143 ++++++++++++++++++++++++++++++++++++++++++ src/lib/utils/env.ts | 92 +++++++++++++++++++++++++++ 8 files changed, 274 insertions(+), 13 deletions(-) create mode 100644 src/lib/storage/s3.ts create mode 100644 src/lib/utils/env.ts diff --git a/.gitignore b/.gitignore index 996fc0e..ec53828 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ yarn-error.log* **/*.tgz **/*.log package-lock.json -**/*.bun \ No newline at end of file +**/*.bun + +./server +./.storage_data diff --git a/Dockerfile b/Dockerfile index 36cc29e..a1c63b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN bun install --frozen-lockfile # Copy source code COPY . . -# Build the application -RUN bun build ./src/index.ts --compile --outfile server +# RUN DB Migrations and build +RUN bun run db:migrateb && bun run build # Production stage FROM debian:bookworm-slim @@ -28,4 +28,4 @@ COPY --from=builder /app/server . EXPOSE 3000 # Run the binary -CMD ["./server"] \ No newline at end of file +CMD ["./server"] diff --git a/bun.lockb b/bun.lockb index b1220bfc067062977d43b54adfebd8000ebdd030..1872bc585141c17455afd9ce4081442285c997f0 100755 GIT binary patch delta 43243 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 delta 31898 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 userInfo(user, session)); +validateEnv(); app.listen(3000); - console.log("Server is running on: http://localhost:3000") diff --git a/src/lib/storage/s3.ts b/src/lib/storage/s3.ts new file mode 100644 index 0000000..c9e3648 --- /dev/null +++ b/src/lib/storage/s3.ts @@ -0,0 +1,143 @@ +import { Client } from 'minio'; +import { Buffer } from 'buffer'; +import { getMinioConfig } from '../utils/env'; + +// MinIO client configuration + +const minioConfig = getMinioConfig() +const minioClient = new Client({ + endPoint: minioConfig.MINIO_ENDPOINT_URL, + useSSL: false, + accessKey: minioConfig.MINIO_ACCESS_KEY, + secretKey: minioConfig.MINIO_SECRET_KEY +}); + +const BUCKET_NAME = minioConfig.MINIO_BUCKET_NAME; +const SIGNED_URL_EXPIRY = 24 * 60 * 60; // 24 hours in seconds + +// Ensure bucket exists +const ensureBucket = async (): Promise => { + const bucketExists = await minioClient.bucketExists(BUCKET_NAME); + if (!bucketExists) { + await minioClient.makeBucket(BUCKET_NAME); + } +}; + +/** + * Convert File/Blob to Buffer + * @param file - File or Blob object + * @returns Promise + */ +const fileToBuffer = async (file: File | Blob): Promise => { + const arrayBuffer = await file.arrayBuffer(); + return Buffer.from(arrayBuffer); +}; + +/** + * Upload a file to MinIO and return its signed URL + * Supports both Buffer and File/Blob (from multipart form-data) + * @param filename - The name to store the file as + * @param file - The file to upload (Buffer, File, or Blob) + * @param contentType - Optional MIME type of the file + * @returns Promise with the signed URL of the uploaded file + */ +export const uploadFileAndGetUrl = async ( + filename: string, + file: Buffer | File | Blob, + contentType?: string +): Promise => { + try { + await ensureBucket(); + + // Convert File/Blob to Buffer if needed + const fileBuffer = Buffer.isBuffer(file) ? file : await fileToBuffer(file); + + // If file is from form-data and no contentType is provided, use its type + const metadata: Record = {}; + if (!contentType && 'type' in file) { + contentType = file.type; + } + if (contentType) { + metadata['Content-Type'] = contentType; + } + + // Upload the file + await minioClient.putObject( + BUCKET_NAME, + filename, + fileBuffer, + fileBuffer.length, + metadata + ); + + // Generate and return signed URL + const url = await minioClient.presignedGetObject( + BUCKET_NAME, + filename, + SIGNED_URL_EXPIRY + ); + + return url; + } catch (error) { + console.error('Error uploading file:', error); + if (error instanceof Error) { + throw new Error(`Failed to upload file: ${error.message}`); + } + throw new Error(`Failed to upload file: ${error}`); + } +}; + +/** + * Get a signed URL for an existing file + * @param filename - The name of the file to get URL for + * @returns Promise with the signed URL + */ +export const getSignedUrl = async (filename: string): Promise => { + try { + // Check if file exists first + await minioClient.statObject(BUCKET_NAME, filename); + + const url = await minioClient.presignedGetObject( + BUCKET_NAME, + filename, + SIGNED_URL_EXPIRY + ); + + return url; + } catch (error) { + console.error('Error generating signed URL:', error); + if (error instanceof Error) throw new Error(`Failed to generate signed URL: ${error.message}`); + throw new Error(`Failed to generate signed URL: ${error}`); + } +}; + +/** + * Delete a file from MinIO + * @param filename - The name of the file to delete + * @returns Promise + */ +export const deleteFile = async (filename: string): Promise => { + try { + await minioClient.removeObject(BUCKET_NAME, filename); + } catch (error) { + console.error('Error deleting file:', error); + if (error instanceof Error) throw new Error(`Failed to delete file: ${error.message}`); + throw new Error(`Failed to delete file: ${error}`); + } +}; + +// Usage examples: + +// Upload a file and get its URL +// const fileBuffer = Buffer.from('Hello World'); +// const url = await uploadFileAndGetUrl('hello.txt', fileBuffer); +// console.log('Uploaded file URL:', url); + +// Get signed URL for existing file +// const url = await getSignedUrl('hello.txt'); +// console.log('Signed URL:', url); + +// Delete a file +// await deleteFile('hello.txt'); +// console.log('File deleted successfully'); + diff --git a/src/lib/utils/env.ts b/src/lib/utils/env.ts new file mode 100644 index 0000000..5bffa5c --- /dev/null +++ b/src/lib/utils/env.ts @@ -0,0 +1,92 @@ +import { z } from 'zod'; + +// Define the environment schema +const envSchema = z.object({ + // Database + DATABASE_URL: z.string().url(), + + // MinIO + MINIO_ACCESS_KEY: z.string(), + MINIO_SECRET_KEY: z.string().min(8), + MINIO_ENDPOINT_URL: z.string().url(), + MINIO_BUCKET_NAME: z.string().url(), +}); + +// Create a type from the schema +type EnvConfig = z.infer; + +/** + * Validates environment variables and returns a typed config object + * Prints warnings for missing or invalid variables + */ +export const validateEnv = (): EnvConfig => { + const warnings: string[] = []; + + // Parse environment with warning collection + const config = envSchema.safeParse(process.env); + + if (!config.success) { + console.warn('\n🚨 Environment Variable Warnings:'); + + // Collect and categorize warnings + config.error.errors.forEach((error) => { + const path = error.path.join('.'); + const message = error.message; + + let warningMessage = `❌ ${path}: ${message}`; + + // Add specific functionality warnings + if (path.startsWith('DB_') || path === 'DATABASE_URL') { + warningMessage += '\n ⚠️ Database functionality may not work properly'; + } + if (path.startsWith('MINIO_')) { + warningMessage += '\n ⚠️ File storage functionality may not work properly'; + } + + warnings.push(warningMessage); + }); + + // Print all warnings + warnings.forEach((warning) => console.warn(warning)); + console.warn('\n'); + + throw new Error('Environment validation failed. Check warnings above.'); + } + + return config.data; +}; + +/** + * Get validated environment config + * Throws error if validation fails + */ +export const getConfig = (): EnvConfig => { + return validateEnv(); +}; + +// Optional: Export individual config getters with type safety +export const getDbConfig = (): Pick => { + const config = getConfig(); + return { + DATABASE_URL: config.DATABASE_URL, + }; +}; + +export const getMinioConfig = (): Pick => { + const config = getConfig(); + return { + MINIO_ACCESS_KEY: config.MINIO_ACCESS_KEY, + MINIO_SECRET_KEY: config.MINIO_SECRET_KEY, + MINIO_ENDPOINT_URL: config.MINIO_ENDPOINT_URL, + MINIO_BUCKET_NAME: config.MINIO_BUCKET_NAME, + }; +}; + +// Usage example: +try { + const config = getConfig(); + // Your application code here +} catch (error) { + // Handle validation errors + process.exit(1); +}