142 lines
3.6 KiB
TypeScript
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);
|
|
}
|
|
}
|