Files
teknav-bak/app/backend/src/lib/storage.ts
T
arsam ad99a85a16 Import source: app/backend/src/lib/storage
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:19:42 +03:30

73 lines
2.4 KiB
TypeScript

// Storage abstraction layer.
// Switch between local disk and S3-compatible storage via STORAGE_BACKEND env var.
// Required env vars for S3: S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY
// Default: LocalStorageAdapter (STORAGE_BACKEND=local or unset)
import fs from 'node:fs/promises';
import path from 'node:path';
import { config } from '../config.js';
export interface StorageAdapter {
/** Write file buffer at the given key, return the public URL */
upload(file: Buffer, key: string, mimeType?: string): Promise<string>;
/** Delete the object at the given key */
delete(key: string): Promise<void>;
/** Return the public URL for a key (without writing) */
url(key: string): string;
}
class LocalStorageAdapter implements StorageAdapter {
async upload(file: Buffer, key: string): Promise<string> {
const dest = path.join(config.UPLOAD_DIR, key);
await fs.mkdir(path.dirname(dest), { recursive: true });
await fs.writeFile(dest, file);
return this.url(key);
}
async delete(key: string): Promise<void> {
await fs.unlink(path.join(config.UPLOAD_DIR, key)).catch(() => undefined);
}
url(key: string): string {
return `/uploads/${key}`;
}
}
class S3StorageAdapter implements StorageAdapter {
private endpoint: string;
private bucket: string;
private accessKey: string;
private secretKey: string;
constructor() {
this.endpoint = process.env.S3_ENDPOINT ?? '';
this.bucket = process.env.S3_BUCKET ?? '';
this.accessKey = process.env.S3_ACCESS_KEY ?? '';
this.secretKey = process.env.S3_SECRET_KEY ?? '';
if (!this.endpoint || !this.bucket || !this.accessKey || !this.secretKey) {
throw new Error('S3 storage requires S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY');
}
}
async upload(_file: Buffer, _key: string, _mimeType?: string): Promise<string> {
// Wire a real S3 client (e.g. @aws-sdk/client-s3 or minio SDK) in Phase 12
throw new Error('S3StorageAdapter.upload: not yet implemented');
}
async delete(_key: string): Promise<void> {
throw new Error('S3StorageAdapter.delete: not yet implemented');
}
url(key: string): string {
return `${this.endpoint}/${this.bucket}/${key}`;
}
}
function createStorageAdapter(): StorageAdapter {
const backend = process.env.STORAGE_BACKEND ?? 'local';
if (backend === 's3') return new S3StorageAdapter();
return new LocalStorageAdapter();
}
export const storage: StorageAdapter = createStorageAdapter();