From 06af5507417468b4e7ca36e9e8fa39cffdcabaa8 Mon Sep 17 00:00:00 2001 From: Pham Hoang Vu Date: Fri, 26 Jun 2026 14:36:06 +0700 Subject: [PATCH 1/3] implement upload file/image --- .../core/api/company/company.controller.js | 40 +++++++++++++++++++ .../src/core/api/company/company.resolver.js | 23 +++++++++++ .../src/core/api/media/media.controller.js | 3 +- backend/src/core/api/media/media.resolver.js | 15 +++++-- backend/src/core/common/swagger/index.js | 1 + .../src/core/common/swagger/upload-file.js | 6 +-- .../src/core/common/swagger/upload-image.js | 9 +++++ .../document/interceptor/file.interceptor.js | 14 +++++++ .../modules/document/interceptor/index.js | 1 + 9 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 backend/src/core/api/company/company.controller.js create mode 100644 backend/src/core/api/company/company.resolver.js create mode 100644 backend/src/core/common/swagger/upload-image.js create mode 100644 backend/src/core/modules/document/interceptor/file.interceptor.js diff --git a/backend/src/core/api/company/company.controller.js b/backend/src/core/api/company/company.controller.js new file mode 100644 index 0000000..81b8f2e --- /dev/null +++ b/backend/src/core/api/company/company.controller.js @@ -0,0 +1,40 @@ +import { MediaService } from 'core/modules/document'; +import { ValidHttpResponse } from 'packages/handler/response/validHttp.response'; + +class Controller { + constructor() { + this.mediaService = MediaService; + } + + createCompany = async req => { + const companyData = { + ...req.body, + }; + + const newCompany = await this.companyService.create(companyData); + return ValidHttpResponse.toCreatedResponse(newCompany); + }; + + updateCompany = async req => { + const companyId = req.params.id; + const { logo_url, logo_public_id, ...otherData } = req.body; + + if (logo_public_id) { + const oldCompany = await this.companyService.findById(companyId); + + if (oldCompany && oldCompany.logo_public_id && oldCompany.logo_public_id !== logo_public_id) { + await this.mediaService.deleteOne(oldCompany.logo_public_id); + } + } + + const updateData = { + ...otherData, + ...(logo_url && { logo_url, logo_public_id }) + }; + + const updatedCompany = await this.companyService.update(companyId, updateData); + return ValidHttpResponse.toOkResponse(updatedCompany); + }; +} + +export const CompanyController = new Controller(); \ No newline at end of file diff --git a/backend/src/core/api/company/company.resolver.js b/backend/src/core/api/company/company.resolver.js new file mode 100644 index 0000000..8bd2366 --- /dev/null +++ b/backend/src/core/api/company/company.resolver.js @@ -0,0 +1,23 @@ +import { Module } from 'packages/handler/Module'; +import { CompanyController } from './company.controller'; + +export const CompanyResolver = Module.builder() + .addPrefix({ + prefixPath: '/api/v1/companies', + tag: 'companies', + module: 'CompanyModule', + }) + .register([ + { + route: '/', + method: 'post', + controller: CompanyController.createCompany, + preAuthorization: true, + }, + { + route: '/:id', + method: 'patch', + controller: CompanyController.updateCompany, + preAuthorization: true, + }, + ]); \ No newline at end of file diff --git a/backend/src/core/api/media/media.controller.js b/backend/src/core/api/media/media.controller.js index 9e0e7b9..f1b1ab8 100644 --- a/backend/src/core/api/media/media.controller.js +++ b/backend/src/core/api/media/media.controller.js @@ -7,7 +7,8 @@ class Controller { } uploadMany = async req => { - const data = await this.service.uploadMany(req.files); + const files = req.files || (req.file ? [req.file] : []); + const data = await this.service.uploadMany(files); return ValidHttpResponse.toOkResponse(data); }; diff --git a/backend/src/core/api/media/media.resolver.js b/backend/src/core/api/media/media.resolver.js index fce9a83..96ce95a 100644 --- a/backend/src/core/api/media/media.resolver.js +++ b/backend/src/core/api/media/media.resolver.js @@ -1,5 +1,5 @@ -import { uploadMediaSwagger } from 'core/common/swagger'; -import { deleteMediasInterceptor, MediaInterceptor } from 'core/modules/document'; +import { uploadMediaSwagger, uploadFileSwagger } from 'core/common/swagger'; +import { deleteMediasInterceptor, MediaInterceptor, FileInterceptor } from 'core/modules/document'; import { Module } from 'packages/handler/Module'; import { MediaController } from './media.controller'; @@ -15,7 +15,16 @@ export const MediaResolver = Module.builder() method: 'post', params: [uploadMediaSwagger], consumes: ['multipart/form-data'], - interceptors: [new MediaInterceptor(10)], + interceptors: [new MediaInterceptor(1)], + controller: MediaController.uploadMany, + preAuthorization: true + }, + { + route: '/files', + method: 'post', + params: [uploadFileSwagger], + consumes: ['multipart/form-data'], + interceptors: [new FileInterceptor(1)], controller: MediaController.uploadMany, preAuthorization: true }, diff --git a/backend/src/core/common/swagger/index.js b/backend/src/core/common/swagger/index.js index 4e10146..a52122b 100644 --- a/backend/src/core/common/swagger/index.js +++ b/backend/src/core/common/swagger/index.js @@ -1,5 +1,6 @@ export * from './filter'; export * from './record-id'; +export * from './upload-image'; export * from './upload-file'; export * from './page'; export * from './page-size'; diff --git a/backend/src/core/common/swagger/upload-file.js b/backend/src/core/common/swagger/upload-file.js index 82354b4..b7bf298 100644 --- a/backend/src/core/common/swagger/upload-file.js +++ b/backend/src/core/common/swagger/upload-file.js @@ -1,9 +1,9 @@ import { SwaggerDocument } from '../../../packages/swagger'; -export const uploadMediaSwagger = SwaggerDocument.ApiParams({ - name: 'image', +export const uploadFileSwagger = SwaggerDocument.ApiParams({ + name: 'file', paramsIn: 'formData', require: true, type: 'file', - description: 'Image file to upload', + description: 'Document file to upload (.pdf, .doc, .docx, .xls, .xlsx)', }); diff --git a/backend/src/core/common/swagger/upload-image.js b/backend/src/core/common/swagger/upload-image.js new file mode 100644 index 0000000..82354b4 --- /dev/null +++ b/backend/src/core/common/swagger/upload-image.js @@ -0,0 +1,9 @@ +import { SwaggerDocument } from '../../../packages/swagger'; + +export const uploadMediaSwagger = SwaggerDocument.ApiParams({ + name: 'image', + paramsIn: 'formData', + require: true, + type: 'file', + description: 'Image file to upload', +}); diff --git a/backend/src/core/modules/document/interceptor/file.interceptor.js b/backend/src/core/modules/document/interceptor/file.interceptor.js new file mode 100644 index 0000000..fad61e7 --- /dev/null +++ b/backend/src/core/modules/document/interceptor/file.interceptor.js @@ -0,0 +1,14 @@ +import { ROOT_DIR } from 'core/env'; +import { BaseMulterInterceptor } from './multer.interceptor'; +import { MulterUploader } from '../multer.handler'; + +export class FileInterceptor extends BaseMulterInterceptor { + constructor(fileQuantity = 1) { + super(new MulterUploader( + ['.pdf', '.doc', '.docx', '.xls', '.xlsx'], + 'file', + fileQuantity, + `${ROOT_DIR}/core/uploads/documents` + )); + } +} \ No newline at end of file diff --git a/backend/src/core/modules/document/interceptor/index.js b/backend/src/core/modules/document/interceptor/index.js index 12dd613..da60fb2 100644 --- a/backend/src/core/modules/document/interceptor/index.js +++ b/backend/src/core/modules/document/interceptor/index.js @@ -1,2 +1,3 @@ export * from './deleteFiles.interceptor'; export * from './media.interceptor'; +export * from './file.interceptor'; \ No newline at end of file From f19be06e090322b165eefc5531c60e29fadfd285 Mon Sep 17 00:00:00 2001 From: Pham Hoang Vu Date: Tue, 30 Jun 2026 13:17:11 +0700 Subject: [PATCH 2/3] fix: change uploadMany to uploadOne --- .../core/api/company/company.controller.js | 40 ------------------- .../src/core/api/company/company.resolver.js | 23 ----------- backend/src/core/api/index.js | 2 +- .../src/core/api/media/media.controller.js | 5 +-- backend/src/core/api/media/media.resolver.js | 4 +- .../modules/document/service/media.service.js | 6 --- 6 files changed, 5 insertions(+), 75 deletions(-) delete mode 100644 backend/src/core/api/company/company.controller.js delete mode 100644 backend/src/core/api/company/company.resolver.js diff --git a/backend/src/core/api/company/company.controller.js b/backend/src/core/api/company/company.controller.js deleted file mode 100644 index 81b8f2e..0000000 --- a/backend/src/core/api/company/company.controller.js +++ /dev/null @@ -1,40 +0,0 @@ -import { MediaService } from 'core/modules/document'; -import { ValidHttpResponse } from 'packages/handler/response/validHttp.response'; - -class Controller { - constructor() { - this.mediaService = MediaService; - } - - createCompany = async req => { - const companyData = { - ...req.body, - }; - - const newCompany = await this.companyService.create(companyData); - return ValidHttpResponse.toCreatedResponse(newCompany); - }; - - updateCompany = async req => { - const companyId = req.params.id; - const { logo_url, logo_public_id, ...otherData } = req.body; - - if (logo_public_id) { - const oldCompany = await this.companyService.findById(companyId); - - if (oldCompany && oldCompany.logo_public_id && oldCompany.logo_public_id !== logo_public_id) { - await this.mediaService.deleteOne(oldCompany.logo_public_id); - } - } - - const updateData = { - ...otherData, - ...(logo_url && { logo_url, logo_public_id }) - }; - - const updatedCompany = await this.companyService.update(companyId, updateData); - return ValidHttpResponse.toOkResponse(updatedCompany); - }; -} - -export const CompanyController = new Controller(); \ No newline at end of file diff --git a/backend/src/core/api/company/company.resolver.js b/backend/src/core/api/company/company.resolver.js deleted file mode 100644 index 8bd2366..0000000 --- a/backend/src/core/api/company/company.resolver.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Module } from 'packages/handler/Module'; -import { CompanyController } from './company.controller'; - -export const CompanyResolver = Module.builder() - .addPrefix({ - prefixPath: '/api/v1/companies', - tag: 'companies', - module: 'CompanyModule', - }) - .register([ - { - route: '/', - method: 'post', - controller: CompanyController.createCompany, - preAuthorization: true, - }, - { - route: '/:id', - method: 'patch', - controller: CompanyController.updateCompany, - preAuthorization: true, - }, - ]); \ No newline at end of file diff --git a/backend/src/core/api/index.js b/backend/src/core/api/index.js index 64d9838..56c7aeb 100644 --- a/backend/src/core/api/index.js +++ b/backend/src/core/api/index.js @@ -21,5 +21,5 @@ export const ModuleResolver = HandlerResolver.builder() ApplicationsResolver, JobResolver, AdminJobResolver, - RecruiterJobResolver, + RecruiterJobResolver ]); diff --git a/backend/src/core/api/media/media.controller.js b/backend/src/core/api/media/media.controller.js index f1b1ab8..8b70102 100644 --- a/backend/src/core/api/media/media.controller.js +++ b/backend/src/core/api/media/media.controller.js @@ -6,9 +6,8 @@ class Controller { this.service = MediaService; } - uploadMany = async req => { - const files = req.files || (req.file ? [req.file] : []); - const data = await this.service.uploadMany(files); + upload = async req => { + const data = await this.service.uploadOne(req.file); return ValidHttpResponse.toOkResponse(data); }; diff --git a/backend/src/core/api/media/media.resolver.js b/backend/src/core/api/media/media.resolver.js index 96ce95a..2b06680 100644 --- a/backend/src/core/api/media/media.resolver.js +++ b/backend/src/core/api/media/media.resolver.js @@ -16,7 +16,7 @@ export const MediaResolver = Module.builder() params: [uploadMediaSwagger], consumes: ['multipart/form-data'], interceptors: [new MediaInterceptor(1)], - controller: MediaController.uploadMany, + controller: MediaController.upload, preAuthorization: true }, { @@ -25,7 +25,7 @@ export const MediaResolver = Module.builder() params: [uploadFileSwagger], consumes: ['multipart/form-data'], interceptors: [new FileInterceptor(1)], - controller: MediaController.uploadMany, + controller: MediaController.upload, preAuthorization: true }, { diff --git a/backend/src/core/modules/document/service/media.service.js b/backend/src/core/modules/document/service/media.service.js index ec4bf0e..e87c0f7 100644 --- a/backend/src/core/modules/document/service/media.service.js +++ b/backend/src/core/modules/document/service/media.service.js @@ -28,12 +28,6 @@ class Service { } } - async uploadMany(files, folderName = '') { - const uploadTasks = files.map(file => this.uploadOne(file, folderName)); - - return Promise.all(uploadTasks); - } - async deleteMany(ids) { const deleteTasks = ids.map(id => this.deleteOne(id)); From 4b94ee9bd17d7eb1dfaa075121c65873a6439fea Mon Sep 17 00:00:00 2001 From: Pham Hoang Vu Date: Tue, 30 Jun 2026 13:28:29 +0700 Subject: [PATCH 3/3] edit name endpoint to avoid misunderstanding --- backend/src/core/api/media/media.resolver.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/core/api/media/media.resolver.js b/backend/src/core/api/media/media.resolver.js index 2b06680..4d7c93f 100644 --- a/backend/src/core/api/media/media.resolver.js +++ b/backend/src/core/api/media/media.resolver.js @@ -11,7 +11,7 @@ export const MediaResolver = Module.builder() }) .register([ { - route: '/images', + route: '/image', method: 'post', params: [uploadMediaSwagger], consumes: ['multipart/form-data'], @@ -20,7 +20,7 @@ export const MediaResolver = Module.builder() preAuthorization: true }, { - route: '/files', + route: '/file', method: 'post', params: [uploadFileSwagger], consumes: ['multipart/form-data'], @@ -29,7 +29,7 @@ export const MediaResolver = Module.builder() preAuthorization: true }, { - route: '/images', + route: '/', method: 'delete', interceptors: [deleteMediasInterceptor], body: 'DeleteFileDto',