Add file upload

This commit is contained in:
2023-12-11 23:05:15 -07:00
parent 6b2fd89ab2
commit fbbfae4ab2
8 changed files with 149 additions and 1 deletions

View File

@@ -0,0 +1,36 @@
import { Controller, Get, Param, Redirect, Render, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileService } from './file.service';
import { Post } from '@nestjs/common';
import { UploadedObjectInfo } from 'minio';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiConsumes } from '@nestjs/swagger';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) { }
@Get()
@Render('file/upload')
generateUploadForm() {
return {}
}
@Get(':key')
@Redirect('', 302)
async getFile(@Param('key') key: string) {
const url = await this.fileService.generatePresignedUrl(key)
return { url }
}
@Post()
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@Render('file/upload-result')
async handleFileUpload(@UploadedFile() file: Express.Multer.File): Promise<any> {
const upload = await this.fileService.handleFileUpload(file);
return {
...upload,
expireTime: new Date(upload.expireAt * 1000).toLocaleString(),
}
}
}

10
src/file/file.module.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { FileService } from './file.service';
import { FileController } from './file.controller';
import { MinioService } from 'src/minio/minio.service';
@Module({
providers: [FileService, MinioService],
controllers: [FileController]
})
export class FileModule { }

73
src/file/file.service.ts Normal file
View File

@@ -0,0 +1,73 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Cron } from '@nestjs/schedule';
import { UploadedObjectInfo } from 'minio';
import { MinioService } from 'src/minio/minio.service';
@Injectable()
export class FileService {
private readonly logger = new Logger(FileService.name);
private readonly bucketName;
private readonly filePrefix = 'file';
private expirationTime: number;
constructor(
private readonly minioService: MinioService,
private readonly configService: ConfigService,
) {
this.expirationTime = this.configService.get('file.defaultTtl', 30 * 24 * 60 * 60);
this.bucketName = this.configService.get('file.bucketName', 'api.us.dev-files');
try {
this.minioService.listBucketObjects(this.bucketName);
} catch (e) {
this.logger.log(`Error with bucket ${this.bucketName}: ${e.message}`);
}
this.deleteExpiredFiles()
}
private generateRandomKey(): string {
return Math.random().toString(36).substring(2);
}
async handleFileUpload(file: Express.Multer.File): Promise<{ uploadResult: UploadedObjectInfo, expireAt: number, key: string, originalFilename: string }> {
const expireAt = (Date.now() / 1000) + this.expirationTime;
const key = this.generateRandomKey();
const uploadResult = await this.minioService.uploadBuffer(this.bucketName, [this.filePrefix, key].join('/'), file.buffer, {
expireAt,
originalFilename: file.originalname,
'content-type': file.mimetype,
});
return {
uploadResult,
key,
expireAt,
originalFilename: file.originalname,
}
}
@Cron('0 0 * * *')
private async deleteExpiredFiles(): Promise<void> {
this.logger.debug('Running cron job to delete expired files');
const now = Date.now() / 1000;
const objectNames = await this.minioService.listBucketObjects(this.bucketName, this.filePrefix, true);
for (const objectName of objectNames) {
this.logger.debug(`Checking object ${objectName}`)
const objectInfo = await this.minioService.getObjectMetadata(this.bucketName, objectName);
if (objectInfo.metaData.expireat < now) {
this.logger.debug(`Deleting object ${objectName}`);
await this.minioService.deleteObject(this.bucketName, objectName);
}
}
}
async generatePresignedUrl(key: string): Promise<string> {
const objectPath = [this.filePrefix, key].join('/');
const metadata = await this.minioService.getObjectMetadata(this.bucketName, objectPath);
if (metadata.expireAt < Date.now() / 1000) {
throw new Error('Object has expired');
}
return await this.minioService.generatePresignedUrl(this.bucketName, objectPath, 10);
}
}