From 31ebafb53cde377aa7e4ea7186b01427b150a1e5 Mon Sep 17 00:00:00 2001 From: Kavehhn174 Date: Wed, 19 Aug 2026 04:58:14 +0330 Subject: [PATCH] feat(bucket): auto-create buckets and move files from temp to main buckets on property save --- components/file/fileService.js | 52 +++++++++++++++++++++++--- components/property/propertyService.js | 15 ++++++++ setup/databaseConnection.js | 6 +++ utils/upload/bucket.js | 33 +++++++++++++--- 4 files changed, 96 insertions(+), 10 deletions(-) diff --git a/components/file/fileService.js b/components/file/fileService.js index fb4d39c..3fd4bb8 100644 --- a/components/file/fileService.js +++ b/components/file/fileService.js @@ -135,12 +135,54 @@ const getMultipleSignedUrls = async (fileNameArray, bucketName) => { const moveFromTempToMain = async (files, targetBucket) => { const tempBucket = 'temp' - - for (const file of files) { - await copyObject(targetBucket, file, `${tempBucket}/${file}`) - - await removeObject(tempBucket, file) + if (!files || !Array.isArray(files) || files.length === 0) { + return [] } + + const { ensureBucketsExist, minioClient } = require('../../utils/upload/bucket') + await ensureBucketsExist([tempBucket, targetBucket]).catch(() => {}) + + const updatedUrls = [] + + for (const item of files) { + const rawStr = typeof item === 'object' && item !== null ? (item.url || item.fileName || item.name || '') : String(item || '') + const fileName = getLastUrlSegment(rawStr) + + if (!fileName) { + continue + } + + try { + // Check if object exists in temp bucket + const existsInTemp = await minioClient.statObject(tempBucket, fileName).then(() => true).catch(() => false) + if (existsInTemp) { + // Copy object from temp to targetBucket + await copyObject(targetBucket, fileName, `/${tempBucket}/${fileName}`) + // Remove from temp bucket + await removeObject(tempBucket, fileName) + // Update File record in database if exists + await File.findOneAndUpdate({ fileName }, { bucketName: targetBucket }, null).catch(() => {}) + } + + // Construct new URL for targetBucket + const protocol = process.env.AWS_USE_SSL === 'true' ? 'https://' : 'http://' + const endpoint = process.env.AWS_ENDPOINT || 'localhost' + const hostPort = (endpoint.includes(':') || !process.env.AWS_PORT) ? endpoint : `${endpoint}:${process.env.AWS_PORT}` + const fullHost = hostPort.startsWith('http://') || hostPort.startsWith('https://') ? hostPort : `${protocol}${hostPort}` + const newUrl = `${fullHost.replace(/\/$/, '')}/${targetBucket}/${fileName}` + + updatedUrls.push(newUrl) + } catch (err) { + console.error(`Failed to move file "${fileName}" from ${tempBucket} to ${targetBucket}:`, err) + if (rawStr.includes(`/${tempBucket}/`)) { + updatedUrls.push(rawStr.replace(`/${tempBucket}/`, `/${targetBucket}/`)) + } else { + updatedUrls.push(rawStr) + } + } + } + + return updatedUrls } const getImage = async (bucketName, fileName) => { diff --git a/components/property/propertyService.js b/components/property/propertyService.js index ca0bcf0..a713faa 100644 --- a/components/property/propertyService.js +++ b/components/property/propertyService.js @@ -4,12 +4,20 @@ const Building = require('./buildingModel') const AppError = require('../../globalErrorHandler/AppError') const { client } = require('../../setup/typesense') const TsApiFeatures = require('../../utils/TsApiFeatures') +const { moveFromTempToMain } = require('../file/fileService') const getAllProperties = async () => { return Property.find({}, null, null).populate('category').populate('status') } const createProperty = async (data) => { + if (data.mainImageUrls && Array.isArray(data.mainImageUrls) && data.mainImageUrls.length > 0) { + data.mainImageUrls = await moveFromTempToMain(data.mainImageUrls, 'properties') + } + if (data.documentUrls && Array.isArray(data.documentUrls) && data.documentUrls.length > 0) { + data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents') + } + if (data.propertyType === 'Land' || data.propertyType === 'زمین') { return Land.create(data) } else if (data.propertyType === 'Building' || data.propertyType === 'ساختمان') { @@ -37,6 +45,13 @@ const getProperty = async (propertyId) => { } const updateProperty = async (propertyId, data) => { + if (data.mainImageUrls && Array.isArray(data.mainImageUrls) && data.mainImageUrls.length > 0) { + data.mainImageUrls = await moveFromTempToMain(data.mainImageUrls, 'properties') + } + if (data.documentUrls && Array.isArray(data.documentUrls) && data.documentUrls.length > 0) { + data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents') + } + const property = await Property.findOneAndUpdate({ _id: propertyId }, data, { new: true }) if (!property) { diff --git a/setup/databaseConnection.js b/setup/databaseConnection.js index d094fb7..92f92b2 100644 --- a/setup/databaseConnection.js +++ b/setup/databaseConnection.js @@ -23,6 +23,12 @@ module.exports = (app) => { const { syncPermissions } = require('../components/permission/permissionService') await syncPermissions() + // Ensure default MinIO buckets exist (temp, properties, documents) + const { ensureBucketsExist } = require('../utils/upload/bucket') + await ensureBucketsExist(['temp', 'properties', 'documents']).catch((err) => { + console.error('Failed to ensure default MinIO buckets:', err) + }) + app.listen(port, function () { console.log('Server Started On Port ' + port) }) diff --git a/utils/upload/bucket.js b/utils/upload/bucket.js index 996af24..2b7fdfb 100644 --- a/utils/upload/bucket.js +++ b/utils/upload/bucket.js @@ -1,6 +1,20 @@ const minioClient = require('./minio') const fs = require('fs') +// Ensure buckets exist in MinIO +const ensureBucketsExist = async (bucketNames = ['temp', 'properties', 'documents']) => { + for (const bucketName of bucketNames) { + try { + const exists = await minioClient.bucketExists(bucketName).catch(() => false) + if (!exists) { + await minioClient.makeBucket(bucketName, process.env.AWS_REGION || 'us-east-1') + console.log(`MinIO bucket "${bucketName}" created successfully.`) + } + } catch (err) { + console.error(`Error ensuring MinIO bucket "${bucketName}" exists:`, err.message) + } + } +} // Upload File to s3 bucket exports.uploadObject = async (file, bucketName) => { @@ -14,11 +28,12 @@ exports.uploadObject = async (file, bucketName) => { const uploadedFile = await minioClient.putObject(bucketName, `${file.filename}-${file.originalName}`, fileStream) const protocol = process.env.AWS_USE_SSL === 'true' ? 'https://' : 'http://' - const hostPort = process.env.AWS_PORT ? `${process.env.AWS_ENDPOINT}:${process.env.AWS_PORT}` : process.env.AWS_ENDPOINT - uploadedFile.fileURL = protocol + hostPort + '/' + bucketName + '/' + file.filename + '-' + file.originalName + const endpoint = process.env.AWS_ENDPOINT || 'localhost' + const hostPort = (endpoint.includes(':') || !process.env.AWS_PORT) ? endpoint : `${endpoint}:${process.env.AWS_PORT}` + const fullHost = hostPort.startsWith('http://') || hostPort.startsWith('https://') ? hostPort : `${protocol}${hostPort}` + uploadedFile.fileURL = fullHost.replace(/\/$/, '') + '/' + bucketName + '/' + file.filename + '-' + file.originalName uploadedFile.fileName = file.filename + '-' + file.originalName - uploadedFile.fileEndpoint = process.env.AWS_ENDPOINT return uploadedFile @@ -52,10 +67,18 @@ exports.deleteObject = (fileName, bucketName) => { return minioClient.removeObject(bucketName, fileName) } -exports.copyObject = (targetBucket, fileName, sourceBucketAndFile) => { - return minioClient.copyObject(targetBucket, fileName, sourceBucketAndFile) +exports.copyObject = async (targetBucket, fileName, sourceBucketAndFile) => { + const bucketExists = await minioClient.bucketExists(targetBucket).catch(() => false) + if (!bucketExists) { + await minioClient.makeBucket(targetBucket, process.env.AWS_REGION || 'us-east-1') + } + const conds = minioClient.CopyConditions ? new minioClient.CopyConditions() : null + return minioClient.copyObject(targetBucket, fileName, sourceBucketAndFile, conds) } exports.removeObject = (bucketName, fileName) => { return minioClient.removeObject(bucketName, fileName) } + +exports.ensureBucketsExist = ensureBucketsExist +exports.minioClient = minioClient