mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
feat: generate upload signed url for oci
This commit is contained in:
@ -1,15 +1,9 @@
|
|||||||
import { Body, Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
|
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
|
||||||
import { memoryStorage } from 'multer';
|
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
|
||||||
import { AuthenticatedUser } from '~/common/decorators';
|
|
||||||
import { AccessTokenGuard } from '~/common/guards';
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
import { ApiLangRequestHeader } from '~/core/decorators';
|
import { ApiLangRequestHeader } from '~/core/decorators';
|
||||||
import { ResponseFactory } from '~/core/utils';
|
import { ResponseFactory } from '~/core/utils';
|
||||||
import { UploadDocumentRequestDto } from '../dtos/request';
|
import { GenerateUploadSignedUrlRequestDto } from '../dtos/request';
|
||||||
import { DocumentMetaResponseDto } from '../dtos/response';
|
|
||||||
import { DocumentType } from '../enums';
|
|
||||||
import { DocumentService } from '../services';
|
import { DocumentService } from '../services';
|
||||||
@Controller('document')
|
@Controller('document')
|
||||||
@ApiTags('Document')
|
@ApiTags('Document')
|
||||||
@ -19,34 +13,9 @@ import { DocumentService } from '../services';
|
|||||||
export class DocumentController {
|
export class DocumentController {
|
||||||
constructor(private readonly documentService: DocumentService) {}
|
constructor(private readonly documentService: DocumentService) {}
|
||||||
|
|
||||||
@Post()
|
@Post('signed-url')
|
||||||
@ApiConsumes('multipart/form-data')
|
async generateSignedUrl(@Body() body: GenerateUploadSignedUrlRequestDto) {
|
||||||
@ApiBody({
|
const signedUrl = await this.documentService.generateUploadSignedUrl(body);
|
||||||
schema: {
|
return ResponseFactory.data(signedUrl);
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
document: {
|
|
||||||
type: 'string',
|
|
||||||
format: 'binary',
|
|
||||||
},
|
|
||||||
documentType: {
|
|
||||||
type: 'string',
|
|
||||||
enum: Object.values(DocumentType).filter(
|
|
||||||
(value) => ![DocumentType.DEFAULT_AVATAR, DocumentType.DEFAULT_TASKS_LOGO].includes(value),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ['document', 'documentType'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@UseInterceptors(FileInterceptor('document', { storage: memoryStorage() }))
|
|
||||||
async createDocument(
|
|
||||||
@UploadedFile() file: Express.Multer.File,
|
|
||||||
@Body() uploadedDocumentRequest: UploadDocumentRequestDto,
|
|
||||||
@AuthenticatedUser() user: IJwtPayload,
|
|
||||||
) {
|
|
||||||
const document = await this.documentService.createDocument(file, uploadedDocumentRequest, user.sub);
|
|
||||||
|
|
||||||
return ResponseFactory.data(new DocumentMetaResponseDto(document));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEnum, IsString } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { DocumentType } from '~/document/enums';
|
||||||
|
|
||||||
|
export class GenerateUploadSignedUrlRequestDto {
|
||||||
|
@ApiProperty({ enum: DocumentType })
|
||||||
|
@IsEnum(DocumentType, { message: i18n('validation.IsEnum', { path: 'general', property: 'document.documentType' }) })
|
||||||
|
documentType!: DocumentType;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'document.document.extension' }) })
|
||||||
|
extension!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'document.originalFileName' }) })
|
||||||
|
originalFileName!: string;
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
export * from './generate-upload-signed-url.request.dto';
|
||||||
export * from './upload-document.request.dto';
|
export * from './upload-document.request.dto';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { UploadDocumentRequestDto } from '../dtos/request';
|
import { GenerateUploadSignedUrlRequestDto, UploadDocumentRequestDto } from '../dtos/request';
|
||||||
import { Document } from '../entities';
|
import { Document } from '../entities';
|
||||||
import { DocumentRepository } from '../repositories';
|
import { DocumentRepository } from '../repositories';
|
||||||
import { OciService } from './oci.service';
|
import { OciService } from './oci.service';
|
||||||
@ -24,4 +24,11 @@ export class DocumentService {
|
|||||||
this.logger.log(`finding document with id ${id}`);
|
this.logger.log(`finding document with id ${id}`);
|
||||||
return this.documentRepository.findDocumentById(id);
|
return this.documentRepository.findDocumentById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateUploadSignedUrl(body: GenerateUploadSignedUrlRequestDto) {
|
||||||
|
this.logger.log(
|
||||||
|
`generating signed URL for document type ${body.documentType} with original file name ${body.originalFileName}`,
|
||||||
|
);
|
||||||
|
return this.ociService.generateUploadPreSignedUrl(body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { CreatePreauthenticatedRequestDetails } from 'oci-objectstorage/lib/mode
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { CacheService } from '~/common/modules/cache/services';
|
import { CacheService } from '~/common/modules/cache/services';
|
||||||
import { BUCKETS } from '../constants';
|
import { BUCKETS } from '../constants';
|
||||||
import { UploadDocumentRequestDto } from '../dtos/request';
|
import { GenerateUploadSignedUrlRequestDto, UploadDocumentRequestDto } from '../dtos/request';
|
||||||
import { UploadResponseDto } from '../dtos/response';
|
import { UploadResponseDto } from '../dtos/response';
|
||||||
import { Document } from '../entities';
|
import { Document } from '../entities';
|
||||||
import { generateNewFileName } from '../utils';
|
import { generateNewFileName } from '../utils';
|
||||||
@ -106,4 +106,47 @@ export class OciService {
|
|||||||
return document.name;
|
return document.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateUploadPreSignedUrl({
|
||||||
|
documentType,
|
||||||
|
originalFileName,
|
||||||
|
extension,
|
||||||
|
}: GenerateUploadSignedUrlRequestDto): Promise<UploadResponseDto> {
|
||||||
|
const bucketName = BUCKETS[documentType];
|
||||||
|
|
||||||
|
if (!bucketName) {
|
||||||
|
this.logger.error('Could not find bucket name for document type', documentType);
|
||||||
|
throw new BadRequestException('DOCUMENT.TYPE_NOT_SUPPORTED');
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectName = generateNewFileName(originalFileName);
|
||||||
|
const expiration = moment().add('1', 'hours').toDate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logger.debug(`Generating pre-signed upload URL for object ${objectName} in bucket ${bucketName}`);
|
||||||
|
|
||||||
|
const response = await this.ociClient.createPreauthenticatedRequest({
|
||||||
|
namespaceName: this.namespace,
|
||||||
|
bucketName,
|
||||||
|
createPreauthenticatedRequestDetails: {
|
||||||
|
name: `upload-${objectName}`,
|
||||||
|
accessType: CreatePreauthenticatedRequestDetails.AccessType.ObjectWrite, // 🔑 for upload
|
||||||
|
timeExpires: expiration,
|
||||||
|
objectName: `${objectName}${extension}`, // Ensure the object name includes the extension
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`Generated upload URL for ${objectName}`);
|
||||||
|
|
||||||
|
return plainToInstance(UploadResponseDto, {
|
||||||
|
name: objectName,
|
||||||
|
extension,
|
||||||
|
url: response.preauthenticatedRequest.fullPath,
|
||||||
|
documentType,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error generating pre-signed upload URL', error);
|
||||||
|
throw new BadRequestException('UPLOAD.URL_GENERATION_FAILED');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user