diff --git a/src/config/upload.js b/src/config/upload.js new file mode 100644 index 0000000..474f3c5 --- /dev/null +++ b/src/config/upload.js @@ -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); +} diff --git a/src/routes/ai-kurzy.routes.js b/src/routes/ai-kurzy.routes.js index 2aa488c..cd90bfb 100644 --- a/src/routes/ai-kurzy.routes.js +++ b/src/routes/ai-kurzy.routes.js @@ -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 diff --git a/src/routes/company.routes.js b/src/routes/company.routes.js index 8c8c035..c0a1e1b 100644 --- a/src/routes/company.routes.js +++ b/src/routes/company.routes.js @@ -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(); diff --git a/src/routes/project.routes.js b/src/routes/project.routes.js index 0841563..1fcee12 100644 --- a/src/routes/project.routes.js +++ b/src/routes/project.routes.js @@ -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(); diff --git a/src/routes/service.routes.js b/src/routes/service.routes.js index 57614aa..74abaa7 100644 --- a/src/routes/service.routes.js +++ b/src/routes/service.routes.js @@ -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(), diff --git a/src/routes/timesheet.routes.js b/src/routes/timesheet.routes.js index 170f2d5..d969700 100644 --- a/src/routes/timesheet.routes.js +++ b/src/routes/timesheet.routes.js @@ -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.', }); /**