Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
63
src/lib/minio/index.ts
Normal file
63
src/lib/minio/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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.
|
||||
* Should be called once at application startup.
|
||||
*/
|
||||
export async function ensureBucket(): Promise<void> {
|
||||
try {
|
||||
const exists = await minioClient.bucketExists(BUCKET);
|
||||
if (!exists) {
|
||||
await minioClient.makeBucket(BUCKET);
|
||||
logger.info({ bucket: BUCKET }, 'MinIO bucket created');
|
||||
} 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<string> {
|
||||
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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user