import { Client } from 'minio'; import { env } from '@/lib/env'; import { logger } from '@/lib/logger'; export const minioClient = new Client({ endPoint: env.MINIO_ENDPOINT, port: env.MINIO_PORT, useSSL: env.MINIO_USE_SSL, accessKey: env.MINIO_ACCESS_KEY, secretKey: env.MINIO_SECRET_KEY, }); const BUCKET = env.MINIO_BUCKET; /** * Ensures the configured bucket exists, creating it if not. * * Gated by MINIO_AUTO_CREATE_BUCKET=true so a misconfigured prod * deploy can't accidentally mint a fresh empty bucket and start * writing into it (silently losing access to the intended one). * The pluggable S3 backend in `src/lib/storage/s3.ts` already * applies the same gate; this legacy export keeps the contract * consistent for any caller still importing from `@/lib/minio`. */ export async function ensureBucket(): Promise { try { const exists = await minioClient.bucketExists(BUCKET); if (!exists) { if (process.env.MINIO_AUTO_CREATE_BUCKET !== 'true') { throw new Error( `MinIO bucket '${BUCKET}' does not exist. Create it manually or set ` + `MINIO_AUTO_CREATE_BUCKET=true.`, ); } await minioClient.makeBucket(BUCKET); logger.info({ bucket: BUCKET }, 'MinIO bucket auto-created (MINIO_AUTO_CREATE_BUCKET=true)'); } else { logger.debug({ bucket: BUCKET }, 'MinIO bucket exists'); } } catch (err) { logger.error({ err, bucket: BUCKET }, 'Failed to ensure MinIO bucket'); throw err; } } /** * Generates a pre-signed GET URL for an object. * * Default expiry is 15 minutes (900 seconds) per SECURITY-GUIDELINES.md §7.1. */ export async function getPresignedUrl(objectKey: string, expirySeconds = 900): Promise { return minioClient.presignedGetObject(BUCKET, objectKey, expirySeconds); } /** * Constructs a storage path from typed components. * * Format: `{portSlug}/{entity}/{entityId}/{fileId}.{extension}` * * No user-supplied input should ever be used as path components - only UUIDs * and controlled slugs (SECURITY-GUIDELINES.md §3.4, §7.1). */ export function buildStoragePath( portSlug: string, entity: string, entityId: string, fileId: string, extension: string, ): string { return `${portSlug}/${entity}/${entityId}/${fileId}.${extension}`; }