Add Timesheets API with file upload and role-based access
Backend Features:
- Timesheets database table (id, userId, fileName, filePath, fileType, fileSize, year, month, timestamps)
- File upload with multer (memory storage, 10MB limit, PDF/Excel validation)
- Structured file storage: uploads/timesheets/{userId}/{year}/{month}/
- RESTful API endpoints:
* POST /api/timesheets/upload - Upload timesheet
* GET /api/timesheets/my - Get user's timesheets (with filters)
* GET /api/timesheets/all - Get all timesheets (admin only)
* GET /api/timesheets/:id/download - Download file
* DELETE /api/timesheets/:id - Delete timesheet
- Role-based permissions: users access own files, admins access all
- Proper error handling and file cleanup on errors
- Database migration for timesheets table
Technical:
- Uses req.user.role for permission checks
- Automatic directory creation for user/year/month structure
- Blob URL cleanup and proper file handling
- Integration with existing auth middleware
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
88
src/routes/timesheet.routes.js
Normal file
88
src/routes/timesheet.routes.js
Normal file
@@ -0,0 +1,88 @@
|
||||
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);
|
||||
|
||||
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: 10 * 1024 * 1024, // 10MB 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* All timesheet routes require authentication
|
||||
*/
|
||||
router.use(authenticate);
|
||||
|
||||
/**
|
||||
* Upload timesheet
|
||||
* POST /api/timesheets/upload
|
||||
*/
|
||||
router.post(
|
||||
'/upload',
|
||||
upload.single('file'),
|
||||
validateBody(z.object({
|
||||
year: z.string().regex(/^\d{4}$/, 'Rok musí byť 4-miestne číslo'),
|
||||
month: z.string().regex(/^([1-9]|1[0-2])$/, 'Mesiac musí byť číslo od 1 do 12'),
|
||||
})),
|
||||
timesheetController.uploadTimesheet
|
||||
);
|
||||
|
||||
/**
|
||||
* Get user's timesheets
|
||||
* GET /api/timesheets/my
|
||||
*/
|
||||
router.get('/my', timesheetController.getMyTimesheets);
|
||||
|
||||
/**
|
||||
* Get all timesheets (admin only)
|
||||
* GET /api/timesheets/all
|
||||
*/
|
||||
router.get('/all', requireAdmin, timesheetController.getAllTimesheets);
|
||||
|
||||
/**
|
||||
* Download timesheet
|
||||
* GET /api/timesheets/:timesheetId/download
|
||||
*/
|
||||
router.get(
|
||||
'/:timesheetId/download',
|
||||
validateParams(z.object({ timesheetId: z.string().uuid() })),
|
||||
timesheetController.downloadTimesheet
|
||||
);
|
||||
|
||||
/**
|
||||
* Delete timesheet
|
||||
* DELETE /api/timesheets/:timesheetId
|
||||
*/
|
||||
router.delete(
|
||||
'/:timesheetId',
|
||||
validateParams(z.object({ timesheetId: z.string().uuid() })),
|
||||
timesheetController.deleteTimesheet
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user