feat: upload module (#486)

* feat: wip upload module

* feat: local storage and serve local images

* feat: protect against injections

* feat: server local and s3 files

* fix: use storage location when serving local files

* feat: cross field env validation
This commit is contained in:
Jérémy M
2023-07-04 16:02:44 +02:00
committed by GitHub
parent 820ef184d3
commit 5e1fc1ad11
52 changed files with 2632 additions and 64 deletions

View File

@ -0,0 +1 @@
export * from './s3-storage-module.interface';

View File

@ -0,0 +1,5 @@
import { S3ClientConfig } from '@aws-sdk/client-s3';
export interface S3StorageModuleOptions extends S3ClientConfig {
bucketName: string;
}

View File

@ -0,0 +1,9 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { S3StorageModuleOptions } from './interfaces';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<S3StorageModuleOptions>({
moduleName: 'S3Storage',
})
.setClassMethodName('forRoot')
.build();

View File

@ -0,0 +1,10 @@
import { Global, Module } from '@nestjs/common';
import { S3StorageService } from './s3-storage.service';
import { ConfigurableModuleClass } from './s3-storage.module-definition';
@Global()
@Module({
providers: [S3StorageService],
exports: [S3StorageService],
})
export class S3StorageModule extends ConfigurableModuleClass {}

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { S3StorageService } from './s3-storage.service';
import { MODULE_OPTIONS_TOKEN } from './s3-storage.module-definition';
describe('S3StorageService', () => {
let service: S3StorageService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
S3StorageService,
{
provide: MODULE_OPTIONS_TOKEN,
useValue: {},
},
],
}).compile();
service = module.get<S3StorageService>(S3StorageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,86 @@
import { Injectable, Inject } from '@nestjs/common';
import { MODULE_OPTIONS_TOKEN } from './s3-storage.module-definition';
import { S3StorageModuleOptions } from './interfaces';
import {
CreateBucketCommandInput,
GetObjectCommand,
GetObjectCommandInput,
GetObjectCommandOutput,
HeadBucketCommandInput,
NotFound,
PutObjectCommand,
PutObjectCommandInput,
PutObjectCommandOutput,
S3,
} from '@aws-sdk/client-s3';
@Injectable()
export class S3StorageService {
private s3Client: S3;
private bucketName: string;
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
private readonly options: S3StorageModuleOptions,
) {
const { bucketName, ...s3Options } = options;
this.s3Client = new S3(s3Options);
this.bucketName = bucketName;
}
public get client(): S3 {
return this.s3Client;
}
async uploadFile(
params: Omit<PutObjectCommandInput, 'Bucket'>,
): Promise<PutObjectCommandOutput> {
const command = new PutObjectCommand({
...params,
Bucket: this.bucketName,
});
await this.createBucket({ Bucket: this.bucketName });
return this.s3Client.send(command);
}
async getFile(
params: Omit<GetObjectCommandInput, 'Bucket'>,
): Promise<GetObjectCommandOutput> {
const command = new GetObjectCommand({
...params,
Bucket: this.bucketName,
});
return this.s3Client.send(command);
}
async checkBucketExists(args: HeadBucketCommandInput) {
try {
await this.s3Client.headBucket(args);
return true;
} catch (error) {
console.log(error);
if (error instanceof NotFound) {
return false;
}
throw error;
}
}
async createBucket(args: CreateBucketCommandInput) {
const exist = await this.checkBucketExists({
Bucket: args.Bucket,
});
if (exist) {
return;
}
return this.s3Client.createBucket(args);
}
}