Added Documents Feature
This commit is contained in:
66
backend/src/core/storage/adapters/minio.adapter.ts
Normal file
66
backend/src/core/storage/adapters/minio.adapter.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Client as MinioClient } from 'minio';
|
||||
import type { Readable } from 'stream';
|
||||
import { appConfig } from '../../config/config-loader';
|
||||
import type { HeadObjectResult, SignedUrlOptions, StorageService } from '../storage.service';
|
||||
|
||||
export function createMinioAdapter(): StorageService {
|
||||
const { endpoint, port, accessKey, secretKey } = appConfig.getMinioConfig();
|
||||
|
||||
const client = new MinioClient({
|
||||
endPoint: endpoint,
|
||||
port,
|
||||
useSSL: false,
|
||||
accessKey,
|
||||
secretKey,
|
||||
});
|
||||
|
||||
const normalizeMeta = (contentType?: string, metadata?: Record<string, string>) => {
|
||||
const meta: Record<string, string> = { ...(metadata || {}) };
|
||||
if (contentType) meta['Content-Type'] = contentType;
|
||||
return meta;
|
||||
};
|
||||
|
||||
const adapter: StorageService = {
|
||||
async putObject(bucket, key, body, contentType, metadata) {
|
||||
const meta = normalizeMeta(contentType, metadata);
|
||||
// For Buffer or string, size is known. For Readable, omit size for chunked encoding.
|
||||
if (Buffer.isBuffer(body) || typeof body === 'string') {
|
||||
await client.putObject(bucket, key, body as any, (body as any).length ?? undefined, meta);
|
||||
} else {
|
||||
await client.putObject(bucket, key, body as Readable, undefined, meta);
|
||||
}
|
||||
},
|
||||
|
||||
async getObjectStream(bucket, key) {
|
||||
return client.getObject(bucket, key);
|
||||
},
|
||||
|
||||
async deleteObject(bucket, key) {
|
||||
await client.removeObject(bucket, key);
|
||||
},
|
||||
|
||||
async headObject(bucket, key): Promise<HeadObjectResult> {
|
||||
const stat = await client.statObject(bucket, key);
|
||||
// minio types: size, etag, lastModified, metaData
|
||||
return {
|
||||
size: stat.size,
|
||||
etag: stat.etag,
|
||||
lastModified: stat.lastModified ? new Date(stat.lastModified) : undefined,
|
||||
contentType: (stat.metaData && (stat.metaData['content-type'] || stat.metaData['Content-Type'])) || undefined,
|
||||
metadata: stat.metaData || undefined,
|
||||
};
|
||||
},
|
||||
|
||||
async getSignedUrl(bucket, key, options?: SignedUrlOptions) {
|
||||
const expires = Math.max(1, Math.min(7 * 24 * 3600, options?.expiresSeconds ?? 300));
|
||||
if (options?.method === 'PUT') {
|
||||
// MinIO SDK has presignedPutObject for PUT
|
||||
return client.presignedPutObject(bucket, key, expires);
|
||||
}
|
||||
// Default GET
|
||||
return client.presignedGetObject(bucket, key, expires);
|
||||
},
|
||||
};
|
||||
|
||||
return adapter;
|
||||
}
|
||||
49
backend/src/core/storage/storage.service.ts
Normal file
49
backend/src/core/storage/storage.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Provider-agnostic storage facade with S3-compatible surface.
|
||||
* Initial implementation backed by MinIO using the official SDK.
|
||||
*/
|
||||
import type { Readable } from 'stream';
|
||||
import { createMinioAdapter } from './adapters/minio.adapter';
|
||||
|
||||
export type ObjectBody = Buffer | Readable | string;
|
||||
|
||||
export interface SignedUrlOptions {
|
||||
method: 'GET' | 'PUT';
|
||||
expiresSeconds?: number; // default 300s
|
||||
}
|
||||
|
||||
export interface HeadObjectResult {
|
||||
size: number;
|
||||
etag?: string;
|
||||
lastModified?: Date;
|
||||
contentType?: string;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface StorageService {
|
||||
putObject(
|
||||
bucket: string,
|
||||
key: string,
|
||||
body: ObjectBody,
|
||||
contentType?: string,
|
||||
metadata?: Record<string, string>
|
||||
): Promise<void>;
|
||||
|
||||
getObjectStream(bucket: string, key: string): Promise<Readable>;
|
||||
|
||||
deleteObject(bucket: string, key: string): Promise<void>;
|
||||
|
||||
headObject(bucket: string, key: string): Promise<HeadObjectResult>;
|
||||
|
||||
getSignedUrl(bucket: string, key: string, options?: SignedUrlOptions): Promise<string>;
|
||||
}
|
||||
|
||||
// Simple factory — currently only MinIO; can add S3 in future without changing feature code
|
||||
let singleton: StorageService | null = null;
|
||||
export function getStorageService(): StorageService {
|
||||
if (!singleton) {
|
||||
singleton = createMinioAdapter();
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user