fischerX/services/api/src/modules/file/storage/adapters/local.adapter.ts

142 lines
3.6 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
import {
CloudStorageAdapter,
UploadOptions,
UploadResult,
} from './storage-adapter.interface';
const mkdir = promisify(fs.mkdir);
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);
const unlink = promisify(fs.unlink);
const access = promisify(fs.access);
const copyFile = promisify(fs.copyFile);
const rename = promisify(fs.rename);
export interface LocalStorageConfig {
basePath: string;
baseUrl?: string;
}
export class LocalStorageAdapter implements CloudStorageAdapter {
private basePath: string;
private baseUrl: string;
constructor(config: LocalStorageConfig) {
this.basePath = config.basePath;
this.baseUrl = config.baseUrl || 'http://localhost:3000/uploads';
this.ensureBasePath();
}
private async ensureBasePath(): Promise<void> {
try {
await access(this.basePath);
} catch {
await mkdir(this.basePath, { recursive: true });
}
}
private getFullPath(filePath: string): string {
return path.join(this.basePath, filePath);
}
private async ensureDirectory(filePath: string): Promise<void> {
const dir = path.dirname(filePath);
const fullDir = this.getFullPath(dir);
try {
await access(fullDir);
} catch {
await mkdir(fullDir, { recursive: true });
}
}
async upload(
file: Buffer | NodeJS.ReadableStream,
filename: string,
options?: UploadOptions,
): Promise<UploadResult> {
const filePath = options?.path
? path.join(options.path, filename)
: filename;
const fullPath = this.getFullPath(filePath);
await this.ensureDirectory(fullPath);
if (file instanceof Buffer) {
await writeFile(fullPath, file);
} else {
const writeStream = fs.createWriteStream(fullPath);
const readable = file as NodeJS.ReadableStream;
await new Promise((resolve, reject) => {
readable.pipe(writeStream);
readable.on('end', resolve);
readable.on('error', reject);
});
}
const stats = fs.statSync(fullPath);
return {
url: `${this.baseUrl}/${filePath}`,
path: filePath,
size: stats.size,
};
}
async download(filePath: string): Promise<Buffer> {
const fullPath = this.getFullPath(filePath);
return readFile(fullPath);
}
async delete(filePath: string): Promise<void> {
const fullPath = this.getFullPath(filePath);
await unlink(fullPath);
}
async exists(filePath: string): Promise<boolean> {
try {
const fullPath = this.getFullPath(filePath);
await access(fullPath);
return true;
} catch {
return false;
}
}
async getUrl(
filePath: string,
_bucket?: string,
expiresIn?: number,
): Promise<string> {
if (expiresIn) {
return `${this.baseUrl}/${filePath}?expires=${Date.now() + expiresIn * 1000}`;
}
return `${this.baseUrl}/${filePath}`;
}
async copy(
sourcePath: string,
targetPath: string,
_sourceBucket?: string,
_targetBucket?: string,
): Promise<void> {
const sourceFullPath = this.getFullPath(sourcePath);
const targetFullPath = this.getFullPath(targetPath);
await this.ensureDirectory(targetFullPath);
await copyFile(sourceFullPath, targetFullPath);
}
async move(
sourcePath: string,
targetPath: string,
_sourceBucket?: string,
_targetBucket?: string,
): Promise<void> {
const sourceFullPath = this.getFullPath(sourcePath);
const targetFullPath = this.getFullPath(targetPath);
await this.ensureDirectory(targetFullPath);
await rename(sourceFullPath, targetFullPath);
}
}