Initial LinkedSign platform

This commit is contained in:
2026-04-06 20:33:59 -05:00
commit 1074c7c9d2
159 changed files with 19072 additions and 0 deletions
@@ -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 };