fix(s3): generate and use seaweedfs signed urls for properties and documents
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -118,19 +118,54 @@ const bucketGetFiles = async (bucketName) => {
|
|||||||
return listOfObjects(bucketName)
|
return listOfObjects(bucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSignedUrl = async (fileName, bucketName) => {
|
const parseFileAndBucket = (input, defaultBucket = 'properties') => {
|
||||||
if (!fileName) {
|
if (!input) return { fileName: '', bucketName: defaultBucket }
|
||||||
return
|
const raw = typeof input === 'object' && input !== null
|
||||||
|
? (input.fileName || input.name || input.url || '')
|
||||||
|
: String(input || '')
|
||||||
|
|
||||||
|
const clean = raw.split('?')[0]
|
||||||
|
const match = clean.match(/(?:^|\/)(properties|documents|temp|belongings|belonging-images|belonging-documents|property-images|property-documents)\/([^/]+)$/i)
|
||||||
|
if (match) {
|
||||||
|
let b = match[1]
|
||||||
|
if (b === 'property-images' || b === 'belonging-images') b = 'properties'
|
||||||
|
if (b === 'property-documents' || b === 'belonging-documents') b = 'documents'
|
||||||
|
return { bucketName: b, fileName: decodeURIComponent(match[2]) }
|
||||||
}
|
}
|
||||||
return signedUrl(bucketName, fileName)
|
const parts = clean.split('/')
|
||||||
|
const fileName = decodeURIComponent(parts.pop() || '')
|
||||||
|
return { bucketName: defaultBucket, fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMultipleSignedUrls = async (fileNameArray, bucketName) => {
|
const getSignedUrl = async (input, defaultBucket = 'properties') => {
|
||||||
const urls = []
|
if (!input) {
|
||||||
for (let fileName of fileNameArray) {
|
return null
|
||||||
urls.push({ url: await getSignedUrl(fileName, bucketName), fileName: fileName})
|
|
||||||
}
|
}
|
||||||
return urls
|
const { fileName, bucketName } = parseFileAndBucket(input, defaultBucket)
|
||||||
|
if (!fileName) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await signedUrl(bucketName, fileName)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error generating signed URL for ${bucketName}/${fileName}:`, err.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMultipleSignedUrls = async (fileNameArray, defaultBucket = 'properties') => {
|
||||||
|
if (!fileNameArray || !Array.isArray(fileNameArray)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const results = await Promise.all(
|
||||||
|
fileNameArray.map(async (item) => {
|
||||||
|
const { fileName, bucketName } = parseFileAndBucket(item, defaultBucket)
|
||||||
|
if (!fileName) return null
|
||||||
|
const url = await getSignedUrl(item, bucketName)
|
||||||
|
return url ? { url, fileName, bucketName } : null
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return results.filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveFromTempToMain = async (files, targetBucket) => {
|
const moveFromTempToMain = async (files, targetBucket) => {
|
||||||
|
|||||||
@@ -68,11 +68,11 @@ const belongingSchema = new mongoose.Schema(
|
|||||||
belongingSchema.plugin(AutoIncrement, { inc_field: 'belongingCode' })
|
belongingSchema.plugin(AutoIncrement, { inc_field: 'belongingCode' })
|
||||||
|
|
||||||
belongingSchema.methods.getDocumentsSignedUrls = function () {
|
belongingSchema.methods.getDocumentsSignedUrls = function () {
|
||||||
return getMultipleSignedUrls(this.documentUrls, 'belonging-documents')
|
return getMultipleSignedUrls(this.documentUrls, 'documents')
|
||||||
}
|
}
|
||||||
|
|
||||||
belongingSchema.methods.getMainImagedSignedUrl = function () {
|
belongingSchema.methods.getMainImagedSignedUrl = function () {
|
||||||
return getMultipleSignedUrls(this.mainImageUrls, 'belonging-images')
|
return getMultipleSignedUrls(this.mainImageUrls, 'properties')
|
||||||
}
|
}
|
||||||
|
|
||||||
belongingSchema.index({ location: '2dsphere' }) // Enable geospatial queries
|
belongingSchema.index({ location: '2dsphere' }) // Enable geospatial queries
|
||||||
|
|||||||
@@ -116,11 +116,11 @@ const propertySchema = new mongoose.Schema(
|
|||||||
propertySchema.plugin(AutoIncrement, { inc_field: 'propertyCode' })
|
propertySchema.plugin(AutoIncrement, { inc_field: 'propertyCode' })
|
||||||
|
|
||||||
propertySchema.methods.getDocumentsSignedUrls = function () {
|
propertySchema.methods.getDocumentsSignedUrls = function () {
|
||||||
return getMultipleSignedUrls(this.documentUrls, 'property-documents')
|
return getMultipleSignedUrls(this.documentUrls, 'documents')
|
||||||
}
|
}
|
||||||
|
|
||||||
propertySchema.methods.getMainImagedSignedUrl = function () {
|
propertySchema.methods.getMainImagedSignedUrl = function () {
|
||||||
return getMultipleSignedUrls(this.mainImageUrls, 'property-images')
|
return getMultipleSignedUrls(this.mainImageUrls, 'properties')
|
||||||
}
|
}
|
||||||
|
|
||||||
propertySchema.index({ location: '2dsphere' })
|
propertySchema.index({ location: '2dsphere' })
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const Building = require('./buildingModel')
|
|||||||
const AppError = require('../../globalErrorHandler/AppError')
|
const AppError = require('../../globalErrorHandler/AppError')
|
||||||
const { client } = require('../../setup/typesense')
|
const { client } = require('../../setup/typesense')
|
||||||
const TsApiFeatures = require('../../utils/TsApiFeatures')
|
const TsApiFeatures = require('../../utils/TsApiFeatures')
|
||||||
const { moveFromTempToMain } = require('../file/fileService')
|
const { moveFromTempToMain, getSignedUrl } = require('../file/fileService')
|
||||||
|
|
||||||
const getAllProperties = async () => {
|
const getAllProperties = async () => {
|
||||||
return Property.find({}, null, null).populate('category').populate('status')
|
return Property.find({}, null, null).populate('category').populate('status')
|
||||||
@@ -153,8 +153,12 @@ const searchPropertiesMongo = async (inputQuery = {}) => {
|
|||||||
.skip((page - 1) * per_page)
|
.skip((page - 1) * per_page)
|
||||||
.limit(per_page)
|
.limit(per_page)
|
||||||
|
|
||||||
const items = properties.map((prop) => {
|
const items = await Promise.all(properties.map(async (prop) => {
|
||||||
const combinedTags = Array.isArray(prop.tags) ? prop.tags.join(' ') : (prop.tags || '')
|
const combinedTags = Array.isArray(prop.tags) ? prop.tags.join(' ') : (prop.tags || '')
|
||||||
|
let signedThumb = null
|
||||||
|
if (prop.mainImageUrls && prop.mainImageUrls.length > 0) {
|
||||||
|
signedThumb = await getSignedUrl(prop.mainImageUrls[0], 'properties').catch(() => null)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
_id: prop._id.toString(),
|
_id: prop._id.toString(),
|
||||||
id: prop.propertyCode ? prop.propertyCode.toString() : prop._id.toString(),
|
id: prop.propertyCode ? prop.propertyCode.toString() : prop._id.toString(),
|
||||||
@@ -164,7 +168,7 @@ const searchPropertiesMongo = async (inputQuery = {}) => {
|
|||||||
nativeCategory: prop.category?.nativeName || '',
|
nativeCategory: prop.category?.nativeName || '',
|
||||||
categoryObj: prop.category,
|
categoryObj: prop.category,
|
||||||
status: prop.status,
|
status: prop.status,
|
||||||
imageUrl: prop.mainImageUrls?.[0] || 'https://cloud.mbmanager.ir/images/item-no-image.png',
|
imageUrl: signedThumb || 'https://cloud.mbmanager.ir/images/item-no-image.png',
|
||||||
mainImageUrls: prop.mainImageUrls || [],
|
mainImageUrls: prop.mainImageUrls || [],
|
||||||
documents: prop.documentUrls || [],
|
documents: prop.documentUrls || [],
|
||||||
documentUrls: prop.documentUrls || [],
|
documentUrls: prop.documentUrls || [],
|
||||||
@@ -183,7 +187,7 @@ const searchPropertiesMongo = async (inputQuery = {}) => {
|
|||||||
isActive: prop.isActive !== false,
|
isActive: prop.isActive !== false,
|
||||||
features: prop.features || [],
|
features: prop.features || [],
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
const pagesCount = Math.ceil(totalCount / per_page) || 1
|
const pagesCount = Math.ceil(totalCount / per_page) || 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user