Files
mbmanager-api/components/file/fileService.js
T
2026-08-19 06:30:00 +03:30

270 lines
8.0 KiB
JavaScript

const File = require('./fileModel')
const AppError = require('../../globalErrorHandler/AppError')
const util = require('util')
const fs = require('fs')
const {
uploadObject,
deleteObject,
listOfObjects,
signedUrl,
copyObject,
removeObject,
} = require('../../utils/upload/bucket')
const deleteTempFile = util.promisify(fs.unlink)
const bucketSingleUpload = async (file, bucketName) => {
const fileData = {
originalName: file.originalname,
filename: file.filename,
path: file.path,
}
const result = await uploadObject(fileData, bucketName)
const data = {
fileURL: result.fileURL,
fileName: result.fileName,
fileEndpoint: result.fileEndpoint,
bucketName,
}
// Delete file from uploads folder
await deleteTempFile(file.path)
const fileRecord = await createFile(data)
return {
status: 'success',
fileRecord,
}
}
const bucketMultiUpload = async (files, bucketName) => {
const results = []
for (let file of files) {
const result = await bucketSingleUpload(file, bucketName)
results.push(result)
}
return results
}
const bucketDeleteFile = async (fileName, bucketName) => {
let raw = typeof fileName === 'object' && fileName !== null
? (fileName.fileName || fileName.url || fileName.name || '')
: fileName
let exportedFileName = getLastUrlSegment(String(raw || ''))
if (exportedFileName) {
await deleteObject(exportedFileName, bucketName).catch((err) => {
console.error('Error deleting object from MinIO bucket:', err)
})
await deleteFile(exportedFileName).catch((err) => {
console.error('Error deleting file record from DB:', err)
})
}
return { fileName: exportedFileName }
}
const getLastUrlSegment = (url) => {
if (!url || typeof url !== 'string') {
return ''
}
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 () => {
return File.find({}, null, null)
}
const getFile = async (fileId) => {
const file = await File.findById(fileId, null, null)
if (!file) {
throw new AppError('NoFileFound', 404, {})
}
return file
}
const createFile = async (data) => {
return File.create(data, null)
}
const updateFile = async (fileId, data) => {
const file = await File.findOneAndUpdate({ _id: fileId }, data, null)
if (!file) {
throw AppError('NoFileFound', 404, {})
}
return file
}
const deleteFile = async (fileName) => {
await File.findOneAndDelete({ fileName }, null)
return null
}
const bucketGetFiles = async (bucketName) => {
return listOfObjects(bucketName)
}
const parseFileAndBucket = (input, defaultBucket = 'properties') => {
if (!input) return { fileName: '', bucketName: defaultBucket }
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]) }
}
const parts = clean.split('/')
const fileName = decodeURIComponent(parts.pop() || '')
return { bucketName: defaultBucket, fileName }
}
const getSignedUrl = async (input, defaultBucket = 'properties') => {
if (!input) {
return null
}
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 tempBucket = 'temp'
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) => {
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,
getSignedUrl,
createFile,
updateFile,
deleteFile,
getAllFiles,
getFile,
bucketSingleUpload,
bucketMultiUpload,
bucketDeleteFile,
bucketGetFiles,
getImage,
}