Skip to main content

Overview

vLife DGO uses Multer for handling file uploads with a 20MB file size limit. Documents are stored with UUID-based filenames for security and organization. Different evaluation types require different sets of documents.

Multer Configuration

Storage Setup

Documents are stored in the public/uploads directory with UUID-based naming:
import multer from "multer";
import { v4 as uuidv4 } from 'uuid';
import * as path from "path";

const storage = multer.diskStorage({
  destination: path.join(__dirname, 'public/uploads'),
  filename: (req, file, cb) => {
    const fecha = new Date();
    const mes = fecha.getMonth() + 1;
    const dia = fecha.getDay();
    cb(null, uuidv4() + dia + mes + '.pdf');
  }
});
UUID (Universally Unique Identifier) ensures each uploaded file has a unique name, preventing conflicts and enhancing security.

File Size Limit

A 20MB limit is enforced with custom error handling:
const fileSizeLimitErrorHandler = (err, req, res, next) => {
  if (err) {
    req.flash('message', 'El archivo excede los 20MB');
    res.redirect('back');
  } else {
    next();
  }
};

app.use(multer({
  storage: storage,
  limits: { fileSize: 20000000 },
}).single('file'), fileSizeLimitErrorHandler);
The 20MB limit (20,000,000 bytes) is a hard limit. Files exceeding this size will be rejected with an error message.

Document Requirements by Evaluation Type

Permanencia (tipo_eval = 1)

Mandatory Documents (5):
Document IDDocument NameDescription
1Acta de NacimientoBirth certificate
2INEOfficial identification
3Comprobante de DomicilioProof of residence
4Comprobante de EstudiosEducational certificates
5Comprobante de IngresosProof of income
Optional Documents:
Document IDDocument NameDescription
6Estado de Cuenta BancariaBank statements
7Cartilla MilitarMilitary service card
8Reporte de CréditoCredit report
9Cuenta DepartamentalDepartmental accounts
10Comprobante Pago ParejaSpouse payment proof
11Fotografía FachadaProperty facade photo
12Documentos de InmueblesReal estate documents
13Licencia Portación de ArmaWeapon carrying license
14Tarjeta de CirculaciónVehicle registration
15Procedimientos LegalesLegal proceedings
16Actividades InformalesInformal activities

Nuevo Ingreso (tipo_eval = 2)

Mandatory Documents (6):
Document IDDocument NameDescription
1Acta de NacimientoBirth certificate
2INEOfficial identification
3Comprobante de DomicilioProof of residence
4Comprobante de EstudiosEducational certificates
5Cartilla MilitarMilitary service card
-Motivo de IngresoReason for joining (text field)
The “Motivo de Ingreso” is captured as text rather than an uploaded document, but counts toward completion requirements.

Upload Process

File Upload Flow

1

Navigate to Upload View

User selects which document to upload from evaluation dashboard.
2

Select File

User chooses PDF file from their device (max 20MB).
3

Upload and Process

Multer handles the upload, generates UUID filename, and stores in uploads directory.
4

Save to Database

File reference is saved to database linked to evaluation and document type.
5

Return to Evaluation

User is redirected back to evaluation view with success message.

Upload View Controller

import ncrypt from "ncrypt-js";
import { PoolvLife, keyDecrypt } from "../database/connection.js";

const FileUploadView = async (req, res) => {
  const { documentoID, enctyptedData } = req.params;
  
  try {
    const ncryptObjet = new ncrypt(keyDecrypt.key);
    const decryptedData = ncryptObjet.decrypt(enctyptedData);
    
    // Get document information
    const [GetDocument] = await PoolvLife.query(
      FileUploadModel.GetDocument,
      documentoID
    );
    
    const numLeyenda = parseInt(documentoID);
    
    res.render("fileUpload/fileUploadView", {
      user: req.session.name,
      enctyptedData,
      decryptedData,
      documentoID,
      InfoDocument: GetDocument[0],
      numLeyenda
    });
  } catch (e) {
    req.flash("message", "Algo salio mal !");
    res.redirect("back");
  }
};

Upload Handler

const UploadFile = async (req, res) => {
  const { evalID, documentoID, enctyptedData } = req.body;
  const uploadFileName = req.file["filename"];
  
  const dataPacket = { evalID, documentoID, uploadFileName };
  
  try {
    await PoolvLife.query(FileUploadModel.SaveDocument, [dataPacket]);
    req.flash("success", "Documento cargado con exito");
    res.redirect(`/evaluacionvLife/evaluacionView/${enctyptedData}`);
  } catch (e) {
    req.flash("message", "Algo salio mal !");
    res.redirect("back");
    console.log(e);
  }
};
The uploaded filename is generated by Multer and accessed via req.file["filename"]. This filename is stored in the database for later retrieval.

File Naming Convention

UUID-Based Naming

Each uploaded file receives a unique name:
filename: (req, file, cb) => {
  const fecha = new Date();
  const mes = fecha.getMonth() + 1;  // Month (1-12)
  const dia = fecha.getDay();         // Day of week (0-6)
  cb(null, uuidv4() + dia + mes + '.pdf');
}
Example filename:
a8f5e3c2-6d4b-4a1c-9e8f-2b5c7d9e1f3a25.pdf
Breakdown:
  • a8f5e3c2-6d4b-4a1c-9e8f-2b5c7d9e1f3a - UUID
  • 2 - Day of week
  • 5 - Month
  • .pdf - Extension
The naming convention uses day of week (0-6) rather than day of month. This is a potential area for improvement to use full date information.

Storage Organization

Directory Structure

project-root/
└── src/
    └── public/
        └── uploads/
            ├── a8f5e3c2-6d4b-4a1c-9e8f-2b5c7d9e1f3a25.pdf
            ├── b9e6f4d3-7e5c-5b2d-af9g-3c6d8e0f2g4b36.pdf
            └── ...
All uploaded documents are stored in a single directory. The database maintains the relationship between files and evaluations.

Database Storage

The database stores:
  • evalID - Links file to specific evaluation
  • documentoID - Identifies document type
  • uploadFileName - UUID-based filename
  • uploadID - Unique identifier for the upload record

Document Validation

Required vs Optional

The evaluation view checks for required documents:
// For Permanencia
const ActaPerma = GetActaPerma.length;
const InePerma = GetINEPerma.length;
const DomicilioPerma = GetDomicilioPerma.length;
const EstudiosPerma = GetEstudiosPerma.length;
const IngresosPerma = GetIngresosPerma.length;

const TotalCargaPerma = ActaPerma + InePerma + DomicilioPerma + 
                         EstudiosPerma + IngresosPerma;

if (TotalCargaPerma === 5) {
  // All required documents uploaded
}

Completion Check

For evaluation finalization:
if (TotalCaptura === 6 && TotalCargaPerma === 5) {
  const ListoRevision = true;
  // Ready for review
}
Requires 6 data sections + 5 mandatory documents

Deleting Uploaded Documents

Employees can delete and re-upload documents:
const DeleteFile = async (req, res) => {
  const { uploadID } = req.body;
  
  try {
    await PoolvLife.query(FileUploadModel.DeleteFile, uploadID);
    req.flash("success", "Documento borrado con exito");
    res.redirect("back");
  } catch (e) {
    req.flash("message", "Algo salio mal !");
    res.redirect("back");
  }
};
Deleting from the database removes the file reference, but the physical file remains in the uploads directory. Consider implementing physical file cleanup for production.

Special Handling: Motivo de Ingreso

For Nuevo Ingreso evaluations, “Motivo de Ingreso” is text-based:
const IngresosMotivo = async (req, res) => {
  const { evalID, motivoDescripcion, enctyptedData } = req.body;
  
  const dataPacket = {
    evalID,
    motivoDescripcion,
  };
  
  try {
    await PoolvLife.query(FileUploadModel.IngresosMotivo, dataPacket);
    req.flash("success", "Se guardo motivo");
    res.redirect(`/evaluacionvLife/evaluacionView/${enctyptedData}`);
  } catch (e) {
    req.flash("message", "Algo salio mal !");
    res.redirect("back");
  }
};
This can also be deleted:
const DeleteMotivo = async (req, res) => {
  const { evalID } = req.body;
  
  try {
    await PoolvLife.query(FileUploadModel.DeleteMotivo, evalID);
    req.flash("success", "Motivo eliminado");
    res.redirect("back");
  } catch (e) {
    req.flash("message", "Algo salio mal !");
    res.redirect("back");
  }
};

File Format Requirements

Accepted Format

Currently, the system generates .pdf extensions for all uploads. Ensure your file upload form restricts to PDF format:
<input type="file" name="file" accept=".pdf" required>

File Size Best Practices

Document TypeRecommended SizeMax Size
Identification (INE)500KB - 2MB20MB
Birth Certificate500KB - 2MB20MB
Proof of Address200KB - 1MB20MB
Educational Docs1MB - 5MB20MB
Multi-page Documents2MB - 10MB20MB
While the hard limit is 20MB, encourage users to optimize PDFs before upload for better performance and storage efficiency.

Document Retrieval

Documents are retrieved for display in the evaluation view:
const [GetActaPerma] = await PoolvLife.query(
  EvaluationvLifeModel.GetActaPerma,
  decryptedData
);

res.render("evaluationvLifeViews/evaluationView", {
  ActaPerma: GetActaPerma[0],
  // Contains filename for display/download
});

Document Access URLs

Uploaded documents are publicly accessible:
http://localhost:4001/uploads/a8f5e3c2-6d4b-4a1c-9e8f-2b5c7d9e1f3a25.pdf
The public directory is served by Express:
app.use(express.static(path.join(__dirname, "public")));
All uploaded documents are publicly accessible via direct URL. For production, consider implementing:
  • Authentication checks before serving files
  • Private storage with signed URLs
  • Access logging and monitoring

Error Handling

Common Upload Errors

  1. File Too Large
    req.flash('message', 'El archivo excede los 20MB');
    
  2. Invalid File Type Handled by frontend file input restrictions
  3. Storage Failure
    req.flash("message", "Algo salio mal !");
    

Upload Validation

Consider adding additional validation:
// Example validation (not in current implementation)
const validateUpload = (file) => {
  if (!file) {
    throw new Error('No file provided');
  }
  if (file.mimetype !== 'application/pdf') {
    throw new Error('Only PDF files are allowed');
  }
  if (file.size > 20000000) {
    throw new Error('File size exceeds 20MB limit');
  }
  return true;
};

Security Considerations

Important Security Measures for Production:
  1. File Type Validation: Verify MIME type on server-side
  2. Virus Scanning: Integrate antivirus scanning for uploads
  3. Access Control: Restrict document access to authorized users
  4. File Size Monitoring: Monitor total storage usage
  5. Content Security: Validate PDF content is not malicious
  6. Private Storage: Move uploads outside public directory
  7. Encrypted Storage: Consider encrypting files at rest
  • FileUploadController.js (src/controllers/FileUploadController.js:1) - Upload handling logic
  • app.js (src/app.js:64) - Multer configuration
  • EvaluationvLifeController.js (src/controllers/EvaluationvLifeController.js:99) - Document validation

Next Steps

Expediente Generation

Generate complete evaluation PDF with all data and documents

Evaluation Types

Learn about document requirements by evaluation type

Build docs developers (and LLMs) love