refactor: Extract shared multer/upload config from routes

Create src/config/upload.js with createUpload() factory and shared
ALLOWED_FILE_TYPES constant. Replace duplicated multer configs in 5
route files with calls to the shared factory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2026-01-28 07:21:35 +01:00
parent 01ce2fc7ad
commit f463467264
6 changed files with 86 additions and 112 deletions

62
src/config/upload.js Normal file
View File

@@ -0,0 +1,62 @@
import multer from 'multer';
import path from 'path';
import fs from 'fs';
export const ALLOWED_FILE_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'text/plain',
'text/csv',
];
function createFileFilter(allowedTypes, errorMessage) {
return (req, file, cb) => {
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error(errorMessage));
}
};
}
function createDiskStorage(uploadsDir) {
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
return multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadsDir);
},
filename: (req, file, cb) => {
const sanitized = path.basename(file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, uniqueSuffix + '-' + sanitized);
},
});
}
export function createUpload({ maxSizeMB = 20, allowedTypes, errorMessage, diskPath } = {}) {
const opts = {
storage: diskPath ? createDiskStorage(diskPath) : multer.memoryStorage(),
limits: { fileSize: maxSizeMB * 1024 * 1024 },
};
if (allowedTypes) {
opts.fileFilter = createFileFilter(
allowedTypes,
errorMessage || 'Nepovolený typ súboru.',
);
}
return multer(opts);
}

View File

@@ -1,57 +1,19 @@
import express from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import * as aiKurzyController from '../controllers/ai-kurzy.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
import { validateBody, validateParams, validateQuery } from '../middlewares/security/validateInput.js';
import { z } from 'zod';
import { createUpload, ALLOWED_FILE_TYPES } from '../config/upload.js';
const router = express.Router();
// Configure multer for file uploads
const uploadsDir = path.join(process.cwd(), 'uploads', 'ai-kurzy');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
const ALLOWED_FILE_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'text/plain',
'text/csv',
];
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadsDir);
},
filename: (req, file, cb) => {
// Sanitize filename to prevent path traversal
const sanitized = path.basename(file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + sanitized);
}
});
const upload = multer({
storage,
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB max
fileFilter: (req, file, cb) => {
if (ALLOWED_FILE_TYPES.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Nepovolený typ súboru. Povolené: PDF, Word, Excel, obrázky, CSV, TXT.'));
}
},
const upload = createUpload({
maxSizeMB: 10,
allowedTypes: ALLOWED_FILE_TYPES,
errorMessage: 'Nepovolený typ súboru. Povolené: PDF, Word, Excel, obrázky, CSV, TXT.',
diskPath: path.join(process.cwd(), 'uploads', 'ai-kurzy'),
});
// Validation schemas

View File

@@ -1,5 +1,4 @@
import express from 'express';
import multer from 'multer';
import * as companyController from '../controllers/company.controller.js';
import * as personalContactController from '../controllers/personal-contact.controller.js';
import * as companyDocumentController from '../controllers/company-document.controller.js';
@@ -9,14 +8,9 @@ import { checkCompanyAccess } from '../middlewares/auth/resourceAccessMiddleware
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
import { createCompanySchema, updateCompanySchema, createCompanyReminderSchema, updateCompanyReminderSchema } from '../validators/crm.validators.js';
import { z } from 'zod';
import { createUpload } from '../config/upload.js';
// Configure multer for file uploads (memory storage)
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 50 * 1024 * 1024, // 50MB max
},
});
const upload = createUpload({ maxSizeMB: 50 });
const router = express.Router();

View File

@@ -1,5 +1,4 @@
import express from 'express';
import multer from 'multer';
import * as projectController from '../controllers/project.controller.js';
import * as projectDocumentController from '../controllers/project-document.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
@@ -8,36 +7,12 @@ import { checkProjectAccess } from '../middlewares/auth/resourceAccessMiddleware
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
import { createProjectSchema, updateProjectSchema } from '../validators/crm.validators.js';
import { z } from 'zod';
import { createUpload, ALLOWED_FILE_TYPES } from '../config/upload.js';
// Configure multer for file uploads (memory storage)
const ALLOWED_FILE_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'text/plain',
'text/csv',
];
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 20 * 1024 * 1024, // 20MB max
},
fileFilter: (req, file, cb) => {
if (ALLOWED_FILE_TYPES.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Nepovolený typ súboru. Povolené: PDF, Word, Excel, PowerPoint, obrázky, CSV, TXT.'));
}
},
const upload = createUpload({
maxSizeMB: 20,
allowedTypes: ALLOWED_FILE_TYPES,
errorMessage: 'Nepovolený typ súboru. Povolené: PDF, Word, Excel, PowerPoint, obrázky, CSV, TXT.',
});
const router = express.Router();

View File

@@ -1,5 +1,4 @@
import express from 'express';
import multer from 'multer';
import * as serviceController from '../controllers/service.controller.js';
import * as serviceFolderController from '../controllers/service-folder.controller.js';
import * as serviceDocumentController from '../controllers/service-document.controller.js';
@@ -8,13 +7,11 @@ import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
import { createServiceSchema, updateServiceSchema } from '../validators/crm.validators.js';
import { z } from 'zod';
import { createUpload } from '../config/upload.js';
const router = express.Router();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB limit
});
const upload = createUpload({ maxSizeMB: 50 });
const serviceIdSchema = z.object({
serviceId: z.string().uuid(),

View File

@@ -1,37 +1,21 @@
import express from 'express';
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';
import * as timesheetController from '../controllers/timesheet.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
import { z } from 'zod';
import fs from 'fs/promises';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
import { createUpload } from '../config/upload.js';
const router = express.Router();
// Create uploads directory if it doesn't exist
const uploadsDir = path.join(process.cwd(), 'uploads', 'timesheets');
await fs.mkdir(uploadsDir, { recursive: true });
// Configure multer for file uploads - use memory storage first
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
},
fileFilter: (req, file, cb) => {
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Neplatný typ súboru. Povolené sú iba PDF a Excel súbory.'), false);
}
}
const upload = createUpload({
maxSizeMB: 5,
allowedTypes: [
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
],
errorMessage: 'Neplatný typ súboru. Povolené sú iba PDF a Excel súbory.',
});
/**