From 4fc323cd160105f95375a85d4bb6e5aedafbe8b9 Mon Sep 17 00:00:00 2001 From: Kaveh Hajinajaf Date: Sun, 27 Apr 2025 16:17:17 +0330 Subject: [PATCH] ADD: API_URL to env ADD: DocumentUrls and mainImageUrls field in belonging model FIX: Indexing bugs for belonging ADD: getImage endpoint to get private images ADD: PrivateUrl field as virtual FIX: Minor bug in bucketDeleteFile service ADD: Word type to multer file checker --- components/belonging/belongingModel.js | 14 ++----- components/belonging/belongingService.js | 22 +++++----- components/file/fileController.js | 24 ++++++++++- components/file/fileModel.js | 4 ++ components/file/fileRoutes.js | 2 + components/file/fileService.js | 51 ++++++++++++++++++++++-- config/.env | 10 +++-- middlewares/multerMiddleware.js | 2 +- 8 files changed, 100 insertions(+), 29 deletions(-) diff --git a/components/belonging/belongingModel.js b/components/belonging/belongingModel.js index 4512ba7..36a11f1 100644 --- a/components/belonging/belongingModel.js +++ b/components/belonging/belongingModel.js @@ -35,10 +35,8 @@ const belongingSchema = new mongoose.Schema( required: false, }, }, - documentNames: [String], - imageNames: [String], - mainImageNames: [String], - mainImageUrl: String, + documentUrls: [String], + mainImageUrls: [String], tags: [String], isActive: { type: Boolean, @@ -66,15 +64,11 @@ const belongingSchema = new mongoose.Schema( belongingSchema.plugin(AutoIncrement, { inc_field: 'belongingCode' }) belongingSchema.methods.getDocumentsSignedUrls = function () { - return getMultipleSignedUrls(this.documentNames, 'belonging-documents') -} - -belongingSchema.methods.getImagesSignedUrls = function () { - return getMultipleSignedUrls(this.imageNames, 'belonging-images') + return getMultipleSignedUrls(this.documentUrls, 'belonging-documents') } belongingSchema.methods.getMainImagedSignedUrl = function () { - return getMultipleSignedUrls(this.mainImageNames, 'belonging-images') + return getMultipleSignedUrls(this.mainImageUrls, 'belonging-images') } belongingSchema.index({ location: '2dsphere' }) // Enable geospatial queries diff --git a/components/belonging/belongingService.js b/components/belonging/belongingService.js index 8bb3656..bad1181 100644 --- a/components/belonging/belongingService.js +++ b/components/belonging/belongingService.js @@ -20,13 +20,11 @@ const getBelonging = async (belongingId) => { } const documentUrls = await belonging.getDocumentsSignedUrls() - const imageUrls = await belonging.getImagesSignedUrls() const mainImageUrls = await belonging.getMainImagedSignedUrl() belonging = JSON.parse(JSON.stringify(belonging)) belonging.signedDocumentUrls = documentUrls - belonging.signedImageUrls = imageUrls belonging.signedMainImageUrls = mainImageUrls return belonging @@ -91,18 +89,21 @@ const indexAllBelongings = async () => { let documents = [] for (const belonging of belongings) { - const categoryArray = Array.isArray(belonging.category) ? belonging.category : belonging.category ? [belonging.category] : [] - const tags = belonging.tags.join(', ') + + const combinedTags = belonging.tags.join(' '); const data = { _id: belonging._id, - name: belonging.name, + isActive: belonging.isActive, belongingCode: belonging.belongingCode, - category: categoryArray.map((x) => x.englishName), - nativeCategory: categoryArray.map((x) => x.nativeName), + name: belonging.name, + category: belonging.category.englishName, + nativeCategory: belonging.category.nativeName, + imageUrl: belonging.mainImageUrls[0] || '', + documents: belonging.documentUrls, description: belonging.description, unit: belonging.unit, - tags: tags, + tags: combinedTags, coordinates: belonging.location.coordinates, createdAt: Math.floor(new Date(belonging.createdAt).getTime() / 1000), // Store as timestamp in seconds } @@ -124,13 +125,14 @@ const getTypeSenseSchema = () => { { name: '_id', type: 'string', facet: false, infix: false }, { name: 'name', type: 'string', facet: false, infix: true }, { name: 'belongingCode', type: 'int64', facet: false }, - { name: 'category', type: 'string[]', facet: true, infix: true }, - { name: 'nativeCategory', type: 'string[]', facet: false, infix: true }, + { name: 'category', type: 'string', facet: true, infix: true }, + { name: 'nativeCategory', type: 'string', facet: false, infix: true }, { name: 'description', type: 'string', facet: false }, { name: 'unit', type: 'string', facet: false, infix: true }, { name: 'tags', type: 'string', facet: false, infix: true }, { name: 'coordinates', type: 'geopoint' }, { name: 'createdAt', type: 'int64', facet: false }, + { name: 'imageUrl', type: 'string', facet: false }, ], default_sorting_field: 'createdAt', } diff --git a/components/file/fileController.js b/components/file/fileController.js index 44d1faa..76eb592 100644 --- a/components/file/fileController.js +++ b/components/file/fileController.js @@ -7,6 +7,7 @@ exports.bucketSingleUpload = catchAsync(async (req, res, next) => { res.status(200).json({ status: 'success', data: fileData, + bucketName: req.params.bucketName, }) }) @@ -27,6 +28,7 @@ exports.bucketMultiUpload = catchAsync(async (req, res, next) => { status: 'success', results: files.length, data: files, + bucketName: req.params.bucketName, }) }) @@ -49,11 +51,31 @@ exports.getSignedUrl = catchAsync(async (req, res, next) => { }) }) +exports.getImage = catchAsync(async (req, res, next) => { + const { bucketName, fileName } = req.params; + + const imageData = await fileService.getImage(bucketName, fileName); + + + if (!imageData) { + return res.status(404).json({ + status: 'fail', + message: 'Image not found' + }); + } + + // Set appropriate headers for the image response + res.set('Content-Type', imageData.contentType); + + res.send(imageData.buffer); +}); + exports.bucketDeleteFile = catchAsync(async (req, res, next) => { - await fileService.bucketDeleteFile(req.body, req.params.bucketName) + const { fileName } = await fileService.bucketDeleteFile(req.body, req.params.bucketName) res.status(200).json({ status: 'success', + data: fileName, }) }) diff --git a/components/file/fileModel.js b/components/file/fileModel.js index 562659a..122aa26 100644 --- a/components/file/fileModel.js +++ b/components/file/fileModel.js @@ -28,5 +28,9 @@ fileSchema.virtual('publicUrl').get(function () { return this.fileEndpoint + '/' + this.bucketName + '/' + this.fileName }) +fileSchema.virtual('privateUrl').get(function () { + return process.env.API_URL + '/api/files/get-image/' + this.bucketName + '/' + this.fileName +}) + const File = mongoose.model('file', fileSchema) module.exports = File diff --git a/components/file/fileRoutes.js b/components/file/fileRoutes.js index cc90e47..4a50d4f 100644 --- a/components/file/fileRoutes.js +++ b/components/file/fileRoutes.js @@ -35,6 +35,8 @@ router.get('/bucket/get-files', perm.requires('getbucketfiles'), fileController. router.get('/bucket/get-signed', perm.requires('getbucketfiles'), fileController.getSignedUrl) +router.get('/get-image/:bucketName/:fileName', perm.requires('getimages'), fileController.getImage); + router.delete('/bucket/delete-file/:bucketName', perm.requires('deletebucketfiles'), fileController.bucketDeleteFile) router.get('/admin/get-all', perm.requires('getallfiles'), fileController.getAllFiles) diff --git a/components/file/fileService.js b/components/file/fileService.js index 04079cd..be2b74c 100644 --- a/components/file/fileService.js +++ b/components/file/fileService.js @@ -52,10 +52,20 @@ const bucketMultiUpload = async (files, bucketName) => { } const bucketDeleteFile = async (fileName, bucketName) => { - await deleteObject(fileName, bucketName) - await deleteFile(fileName) + let exportedFileName = getLastUrlSegment(fileName) + await deleteObject(exportedFileName, bucketName) + await deleteFile(exportedFileName) - return null + return { fileName } +} + +const getLastUrlSegment = (url) => { + if (!url.includes('/')) { + return url + } + const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url + const segments = cleanUrl.split('/') + return segments[segments.length - 1] } const getAllFiles = async () => { @@ -100,6 +110,7 @@ const getSignedUrl = async (fileName, bucketName) => { if (!fileName) { return } + console.log(bucketName,fileName) return signedUrl(bucketName, fileName) } @@ -120,6 +131,39 @@ const moveFromTempToMain = async (files, targetBucket) => { await removeObject(tempBucket, file) } } + +const getImage = async (bucketName, fileName) => { + if (!fileName) { + throw new AppError('NoFileFound', 404, {}) + } + + + // Get signed URL from the file service + const signedUrl = await getSignedUrl(fileName, bucketName); + + if (!signedUrl) { + return null; + } + + // Fetch the image from the signed URL + const response = await fetch(signedUrl); + + if (!response.ok) { + throw new AppError('NoFileFound', 404, {}) + } + + // Get the content type from the response headers + const contentType = response.headers.get('content-type'); + + // Convert the response to a buffer + const buffer = await response.arrayBuffer(); + + // Return both the buffer and content type + return { + buffer: Buffer.from(buffer), + contentType + }; +}; module.exports = { moveFromTempToMain, getMultipleSignedUrls, @@ -133,4 +177,5 @@ module.exports = { bucketMultiUpload, bucketDeleteFile, bucketGetFiles, + getImage, } diff --git a/config/.env b/config/.env index ef01c2e..370c738 100644 --- a/config/.env +++ b/config/.env @@ -7,7 +7,9 @@ LOGIN_ATTEMPT_LIMIT=900 SUPER_ADMIN_ROLE_ID=66a2da5b9b989a8909948848 TYPE_SENSE_URL=bm-typesense.chbk.app TYPE_SENSE_API_KEY=rHV4vZePdfBYFC9q -AWS_ENDPOINT=bm-minio.chbk.app -AWS_ACCESS_KEY=EFYrmV0EiH4qwGERtbhJygNglaCZYtti -AWS_SECRET_KEY=ArkI5GDTId6GsQ0WvKFSp54lrx78gSW1 -AWS_REGION=default \ No newline at end of file +AWS_ENDPOINT=bm-minio-2.chbk.app +AWS_URL=https://bm-minio-2.chbk.app +AWS_ACCESS_KEY=lwcAHMbCea4jeKMtj6SOjMtmETY93rBb +AWS_SECRET_KEY=xXKrceoCV3Wx35d0JmULgNvHJ0UlTQnO +AWS_REGION=default +API_URL=http://localhost:3000 \ No newline at end of file diff --git a/middlewares/multerMiddleware.js b/middlewares/multerMiddleware.js index 78a5931..1967fae 100644 --- a/middlewares/multerMiddleware.js +++ b/middlewares/multerMiddleware.js @@ -1,7 +1,7 @@ const multer = require('multer') const AppError = require('../globalErrorHandler/AppError') -const types = ['png', 'jpg', 'jpeg', 'webp', 'pdf', 'doc', 'docx', 'xlsx', 'dwg'] +const types = ['png', 'jpg', 'jpeg', 'webp', 'pdf', 'doc', 'docx', 'xlsx', 'dwg', 'vnd.openxmlformats-officedocument.wordprocessingml.document'] const formatsText = types.join(' - ') const fileFilter = (req, file, cb) => {