import { Router } from 'express'; import { body, param } from 'express-validator'; import multer from 'multer'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; import { tendersController } from './tenders.controller'; import { authenticate, authorize } from '../../shared/middleware/auth'; import { validate } from '../../shared/middleware/validation'; import { config } from '../../config'; const router = Router(); const uploadDir = path.join(config.upload.path, 'tenders'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } const storage = multer.diskStorage({ destination: (_req, _file, cb) => cb(null, uploadDir), filename: (_req, file, cb) => { const safeName = (file.originalname || 'file').replace(/[^a-zA-Z0-9.-]/g, '_'); cb(null, `${crypto.randomUUID()}-${safeName}`); }, }); const upload = multer({ storage, limits: { fileSize: config.upload.maxFileSize }, }); // View attachment router.get( '/attachments/:attachmentId/view', param('attachmentId').isUUID(), validate, tendersController.viewAttachment ) router.use(authenticate); // Enum/lookup routes (no resource id) - place before /:id routes router.get( '/source-values', authorize('tenders', 'tenders', 'read'), tendersController.getSourceValues ); router.get( '/announcement-type-values', authorize('tenders', 'tenders', 'read'), tendersController.getAnnouncementTypeValues ); router.get( '/directive-type-values', authorize('tenders', 'tenders', 'read'), tendersController.getDirectiveTypeValues ); router.post( '/check-duplicates', authorize('tenders', 'tenders', 'create'), [ body('issuingBodyName').optional().trim(), body('title').optional().trim(), body('tenderNumber').optional().trim(), body('termsValue').optional().isNumeric(), body('bondValue').optional().isNumeric(), body('announcementDate').optional().isISO8601(), body('closingDate').optional().isISO8601(), ], validate, tendersController.checkDuplicates ); // List & create tenders router.get( '/', authorize('tenders', 'tenders', 'read'), tendersController.findAll ); router.post( '/', authorize('tenders', 'tenders', 'create'), [ body('tenderNumber').notEmpty().trim(), body('issuingBodyName').notEmpty().trim(), body('title').notEmpty().trim(), body('termsValue').isNumeric(), body('bondValue').isNumeric(), body('announcementDate').isISO8601(), body('closingDate').isISO8601(), body('source').notEmpty(), body('announcementType').notEmpty(), ], validate, tendersController.create ); // Tender by id router.get( '/:id', authorize('tenders', 'tenders', 'read'), param('id').isUUID(), validate, tendersController.findById ); router.put( '/:id', authorize('tenders', 'tenders', 'update'), param('id').isUUID(), validate, tendersController.update ); // Tender history router.get( '/:id/history', authorize('tenders', 'tenders', 'read'), param('id').isUUID(), validate, tendersController.getHistory ); // Convert to deal router.post( '/:id/convert-to-deal', authorize('tenders', 'tenders', 'update'), [ param('id').isUUID(), body('contactId').isUUID(), body('pipelineId').isUUID(), body('ownerId').optional().isUUID(), ], validate, tendersController.convertToDeal ); // Directives router.post( '/:id/directives', authorize('tenders', 'directives', 'create'), [ param('id').isUUID(), body('type').isIn(['BUY_TERMS', 'VISIT_CLIENT', 'MEET_COMMITTEE', 'PREPARE_TO_BID']), body('assignedToEmployeeId').isUUID(), body('notes').optional().trim(), ], validate, tendersController.createDirective ); // Update directive (e.g. complete task) - route with directiveId router.put( '/directives/:directiveId', authorize('tenders', 'directives', 'update'), [ param('directiveId').isUUID(), body('status').optional().isIn(['PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED']), body('completionNotes').optional().trim(), ], validate, tendersController.updateDirective ); // File uploads router.post( '/:id/attachments', authorize('tenders', 'tenders', 'update'), param('id').isUUID(), validate, upload.single('file'), tendersController.uploadTenderAttachment ); router.post( '/directives/:directiveId/attachments', authorize('tenders', 'directives', 'update'), param('directiveId').isUUID(), validate, upload.single('file'), tendersController.uploadDirectiveAttachment ); export default router; // Delete attachment router.delete( '/attachments/:attachmentId', authorize('tenders', 'tenders', 'update'), param('attachmentId').isUUID(), validate, tendersController.deleteAttachment )