Disk-based multer plus an authenticated content endpoint match the working Node.js uploader and avoid browser signed-URL failures on SeaweedFS.
145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
// /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;
|
|
try {
|
|
item.signedUrl = await generatePresignedUrl(item.fileKey, item.bucket || config.S3_DOCUMENTS_BUCKET);
|
|
} catch {
|
|
item.signedUrl = null;
|
|
}
|
|
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 = `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 = `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
|
|
};
|