49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import { Injectable, OnModuleInit } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { createReadStream, promises as fs } from 'fs';
|
|
import * as path from 'path';
|
|
import type { Readable } from 'stream';
|
|
|
|
@Injectable()
|
|
export class LocalFileStorage implements OnModuleInit {
|
|
private readonly root: string;
|
|
|
|
constructor(private readonly config: ConfigService) {
|
|
this.root =
|
|
this.config.get<string>('UPLOAD_DIR')?.trim() ||
|
|
path.join(process.cwd(), 'uploads');
|
|
}
|
|
|
|
async onModuleInit() {
|
|
await fs.mkdir(this.root, { recursive: true });
|
|
}
|
|
|
|
absolutePath(storageKey: string): string {
|
|
const resolved = path.resolve(this.root, storageKey);
|
|
if (!resolved.startsWith(path.resolve(this.root))) {
|
|
throw new Error('Invalid storage key');
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
async save(storageKey: string, buffer: Buffer): Promise<void> {
|
|
const abs = this.absolutePath(storageKey);
|
|
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
await fs.writeFile(abs, buffer);
|
|
}
|
|
|
|
async remove(storageKey: string): Promise<void> {
|
|
const abs = this.absolutePath(storageKey);
|
|
try {
|
|
await fs.unlink(abs);
|
|
} catch (err) {
|
|
const code = (err as NodeJS.ErrnoException).code;
|
|
if (code !== 'ENOENT') throw err;
|
|
}
|
|
}
|
|
|
|
openReadStream(storageKey: string): Readable {
|
|
return createReadStream(this.absolutePath(storageKey));
|
|
}
|
|
}
|