Initial LinkedSign platform
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
const documentsService = require('./documents.service');
|
||||
const { sendSuccess } = require('../../utils/apiResponse');
|
||||
|
||||
const getCtx = (req) => ({ ip: req.ip?.replace('::ffff:', ''), userAgent: req.headers['user-agent'] });
|
||||
|
||||
const upload = async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
const err = new Error('No file uploaded.'); err.statusCode = 400; throw err;
|
||||
}
|
||||
const doc = await documentsService.upload(req.file, req.user.id, getCtx(req));
|
||||
return sendSuccess(res, doc, 201);
|
||||
} catch (err) { return next(err); }
|
||||
};
|
||||
|
||||
const list = async (req, res, next) => {
|
||||
try {
|
||||
const result = await documentsService.list(req.user.id, req.query);
|
||||
return sendSuccess(res, result, 200);
|
||||
} catch (err) { return next(err); }
|
||||
};
|
||||
|
||||
const getOne = async (req, res, next) => {
|
||||
try {
|
||||
const doc = await documentsService.getOne(Number(req.params.id), req.user.id);
|
||||
return sendSuccess(res, doc, 200);
|
||||
} catch (err) { return next(err); }
|
||||
};
|
||||
|
||||
const download = async (req, res, next) => {
|
||||
try {
|
||||
const doc = await documentsService.getOne(Number(req.params.id), req.user.id);
|
||||
res.download(doc.storage_path, doc.filename);
|
||||
} catch (err) { return next(err); }
|
||||
};
|
||||
|
||||
const remove = async (req, res, next) => {
|
||||
try {
|
||||
await documentsService.remove(Number(req.params.id), req.user.id, getCtx(req));
|
||||
return sendSuccess(res, { message: 'Document deleted.' }, 200);
|
||||
} catch (err) { return next(err); }
|
||||
};
|
||||
|
||||
module.exports = { upload, list, getOne, download, remove };
|
||||
@@ -0,0 +1,40 @@
|
||||
const db = require('../../config/db');
|
||||
|
||||
const create = async ({ userId, title, filename, storagePath, fileSize, hashSha256 }) => {
|
||||
const [result] = await db.execute(
|
||||
'INSERT INTO documents (user_id, title, filename, storage_path, file_size, hash_sha256) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[userId, title, filename, storagePath, fileSize, hashSha256 || null]
|
||||
);
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
const findById = async (id) => {
|
||||
const [rows] = await db.execute(
|
||||
'SELECT d.*, u.name as owner_name, u.email as owner_email FROM documents d JOIN users u ON d.user_id = u.id WHERE d.id = ?',
|
||||
[id]
|
||||
);
|
||||
return rows[0] || null;
|
||||
};
|
||||
|
||||
const findByUser = async (userId, { limit = 50, offset = 0 } = {}) => {
|
||||
const [rows] = await db.execute(
|
||||
'SELECT * FROM documents WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?',
|
||||
[userId, limit, offset]
|
||||
);
|
||||
return rows;
|
||||
};
|
||||
|
||||
const countByUser = async (userId) => {
|
||||
const [rows] = await db.execute('SELECT COUNT(*) as total FROM documents WHERE user_id = ?', [userId]);
|
||||
return rows[0].total;
|
||||
};
|
||||
|
||||
const updateStatus = async (id, status) => {
|
||||
await db.execute('UPDATE documents SET status = ? WHERE id = ?', [status, id]);
|
||||
};
|
||||
|
||||
const remove = async (id) => {
|
||||
await db.execute('DELETE FROM documents WHERE id = ?', [id]);
|
||||
};
|
||||
|
||||
module.exports = { create, findById, findByUser, countByUser, updateStatus, remove };
|
||||
@@ -0,0 +1,16 @@
|
||||
const express = require('express');
|
||||
const { uploadDocument } = require('../../config/multer');
|
||||
const authenticate = require('../../middlewares/authenticate');
|
||||
const documentsController = require('./documents.controller');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(authenticate);
|
||||
|
||||
router.post('/', uploadDocument.single('file'), documentsController.upload);
|
||||
router.get('/', documentsController.list);
|
||||
router.get('/:id', documentsController.getOne);
|
||||
router.get('/:id/download', documentsController.download);
|
||||
router.delete('/:id', documentsController.remove);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,59 @@
|
||||
const fs = require('fs/promises');
|
||||
const crypto = require('crypto');
|
||||
const documentsRepository = require('./documents.repository');
|
||||
const auditService = require('../audit/audit.service');
|
||||
|
||||
const upload = async (file, userId, context = {}) => {
|
||||
const buffer = await fs.readFile(file.path);
|
||||
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||
|
||||
const title = file.originalname.replace(/\.pdf$/i, '');
|
||||
const docId = await documentsRepository.create({
|
||||
userId,
|
||||
title,
|
||||
filename: file.filename,
|
||||
storagePath: file.path,
|
||||
fileSize: file.size,
|
||||
hashSha256: hash,
|
||||
});
|
||||
|
||||
await auditService.logEvent({
|
||||
type: 'DOCUMENT_UPLOAD',
|
||||
userId,
|
||||
ip: context.ip,
|
||||
userAgent: context.userAgent,
|
||||
metadata: { docId, filename: file.originalname, size: file.size },
|
||||
});
|
||||
|
||||
return documentsRepository.findById(docId);
|
||||
};
|
||||
|
||||
const list = async (userId, query = {}) => {
|
||||
const limit = Math.min(Number(query.limit) || 20, 100);
|
||||
const offset = Number(query.offset) || 0;
|
||||
const [rows, total] = await Promise.all([
|
||||
documentsRepository.findByUser(userId, { limit, offset }),
|
||||
documentsRepository.countByUser(userId),
|
||||
]);
|
||||
return { rows, total, limit, offset };
|
||||
};
|
||||
|
||||
const getOne = async (id, userId) => {
|
||||
const doc = await documentsRepository.findById(id);
|
||||
if (!doc) {
|
||||
const err = new Error('Document not found.'); err.statusCode = 404; throw err;
|
||||
}
|
||||
if (doc.user_id !== userId) {
|
||||
const err = new Error('Access denied.'); err.statusCode = 403; throw err;
|
||||
}
|
||||
return doc;
|
||||
};
|
||||
|
||||
const remove = async (id, userId, context = {}) => {
|
||||
const doc = await getOne(id, userId);
|
||||
try { await fs.unlink(doc.storage_path); } catch { /* already deleted */ }
|
||||
await documentsRepository.remove(id);
|
||||
await auditService.logEvent({ type: 'DOCUMENT_DELETE', userId, ip: context.ip, metadata: { docId: id } });
|
||||
};
|
||||
|
||||
module.exports = { upload, list, getOne, remove };
|
||||
Reference in New Issue
Block a user