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:
1
server/src/integrations/s3-storage/interfaces/index.ts
Normal file
1
server/src/integrations/s3-storage/interfaces/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './s3-storage-module.interface';
|
||||
@ -0,0 +1,5 @@
|
||||
import { S3ClientConfig } from '@aws-sdk/client-s3';
|
||||
|
||||
export interface S3StorageModuleOptions extends S3ClientConfig {
|
||||
bucketName: string;
|
||||
}
|
||||
@ -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();
|
||||
10
server/src/integrations/s3-storage/s3-storage.module.ts
Normal file
10
server/src/integrations/s3-storage/s3-storage.module.ts
Normal 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 {}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
86
server/src/integrations/s3-storage/s3-storage.service.ts
Normal file
86
server/src/integrations/s3-storage/s3-storage.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user