// /components/documents/documentService.js const path = require('path'); const Document = require('./documentModel'); const User = require('../users/userModel'); const AppError = require('../../utils/AppError'); const config = require('../../config/config'); const { commitTempFile, generatePresignedUrl, deleteFromBucket } = require('../../utils/s3Client'); const { parsePaginationAndSort, buildFilterQuery, calculateMeta } = require('../../utils/pagination'); const BUCKET = 'documents'; const withAccessUrl = async (doc) => { const item = typeof doc.toObject === 'function' ? doc.toObject() : { ...doc }; item.fileUrl = item.fileUrl || null; item.signedUrl = await generatePresignedUrl(item.fileKey, item.bucket || config.S3_DOCUMENTS_BUCKET); item.presignedUrl = item.signedUrl; item.url = item.fileUrl || item.signedUrl; return item; }; const createDocument = async (data, uploadedBy = null) => { const user = await User.findById(data.user); if (!user) throw new AppError('USER_NOT_FOUND'); if (!data.tempFileName) throw new AppError('FILE_REQUIRED'); const safeName = path.basename(data.tempFileName); const targetKey = `documents/doc-${Date.now()}-${safeName}`; const { fileKey, bucket, fileUrl } = await commitTempFile(data.tempFileName, targetKey, BUCKET); const document = await Document.create({ user: data.user, title: data.title || data.originalName || safeName, description: data.description || '', fileKey, fileName: data.originalName || data.fileName || safeName, fileUrl: fileUrl || null, bucket, mimeType: data.mimeType || '', uploadedBy: uploadedBy || null }); if (Array.isArray(user.documents)) { user.documents.push(document._id); await user.save(); } return withAccessUrl(document); }; const getDocumentById = async (id) => { const document = await Document.findById(id).populate('user', 'name username nationalIdCode'); if (!document) throw new AppError('DOCUMENT_NOT_FOUND'); return withAccessUrl(document); }; const getDocumentsByUser = async (userId) => { const documents = await Document.find({ user: userId }).sort({ createdAt: -1 }); return Promise.all(documents.map(withAccessUrl)); }; const getAllDocuments = async (queryParams) => { const { page, limit, skip, sort } = parsePaginationAndSort(queryParams); const filter = buildFilterQuery(queryParams, ['title', 'fileName', 'description']); if (queryParams.userId) filter.user = queryParams.userId; const [documents, totalCount] = await Promise.all([ Document.find(filter) .populate('user', 'name username') .sort(sort) .skip(skip) .limit(limit), Document.countDocuments(filter) ]); const data = await Promise.all(documents.map(withAccessUrl)); return { data, meta: calculateMeta(totalCount, page, limit) }; }; const updateDocument = async (id, updateData) => { const document = await Document.findById(id); if (!document) throw new AppError('DOCUMENT_NOT_FOUND'); if (updateData.tempFileName) { const safeName = path.basename(updateData.tempFileName); const targetKey = `documents/doc-${Date.now()}-${safeName}`; const { fileKey, bucket, fileUrl } = await commitTempFile(updateData.tempFileName, targetKey, BUCKET); await deleteFromBucket(document.fileKey, document.bucket || config.S3_DOCUMENTS_BUCKET); document.fileKey = fileKey; document.bucket = bucket; document.fileUrl = fileUrl || null; if (updateData.originalName || updateData.fileName) { document.fileName = updateData.originalName || updateData.fileName; } if (updateData.mimeType) document.mimeType = updateData.mimeType; } if (updateData.title) document.title = updateData.title; if (updateData.description !== undefined) document.description = updateData.description; await document.save(); return withAccessUrl(document); }; const deleteDocument = async (id) => { const document = await Document.findById(id); if (!document) throw new AppError('DOCUMENT_NOT_FOUND'); await deleteFromBucket(document.fileKey, document.bucket || config.S3_DOCUMENTS_BUCKET); await User.findByIdAndUpdate(document.user, { $pull: { documents: document._id } }); await Document.findByIdAndDelete(id); return null; }; const getMyDocuments = async (userId, queryParams = {}) => { const filter = { user: userId, ...buildFilterQuery(queryParams, ['title', 'fileName']) }; const { page, limit, skip, sort } = parsePaginationAndSort(queryParams); const [documents, totalCount] = await Promise.all([ Document.find(filter).sort(sort).skip(skip).limit(limit), Document.countDocuments(filter) ]); const data = await Promise.all(documents.map(withAccessUrl)); return { data, meta: calculateMeta(totalCount, page, limit) }; }; module.exports = { createDocument, getDocumentById, getDocumentsByUser, getAllDocuments, updateDocument, deleteDocument, getMyDocuments };