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>
90 lines
2.0 KiB
JavaScript
90 lines
2.0 KiB
JavaScript
/**
|
|
* Custom error classes pre aplikáciu
|
|
*/
|
|
|
|
export class AppError extends Error {
|
|
constructor(message, statusCode = 500, details = null) {
|
|
super(message);
|
|
this.statusCode = statusCode;
|
|
this.details = details;
|
|
this.isOperational = true;
|
|
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
}
|
|
|
|
export class ValidationError extends AppError {
|
|
constructor(message, details = null) {
|
|
super(message, 400, details);
|
|
this.name = 'ValidationError';
|
|
}
|
|
}
|
|
|
|
export class BadRequestError extends AppError {
|
|
constructor(message = 'Zlá požiadavka') {
|
|
super(message, 400);
|
|
this.name = 'BadRequestError';
|
|
}
|
|
}
|
|
|
|
export class AuthenticationError extends AppError {
|
|
constructor(message = 'Neautorizovaný prístup') {
|
|
super(message, 401);
|
|
this.name = 'AuthenticationError';
|
|
}
|
|
}
|
|
|
|
export class ForbiddenError extends AppError {
|
|
constructor(message = 'Prístup zamietnutý') {
|
|
super(message, 403);
|
|
this.name = 'ForbiddenError';
|
|
}
|
|
}
|
|
|
|
export class NotFoundError extends AppError {
|
|
constructor(message = 'Nenájdené') {
|
|
super(message, 404);
|
|
this.name = 'NotFoundError';
|
|
}
|
|
}
|
|
|
|
export class ConflictError extends AppError {
|
|
constructor(message = 'Konflikt') {
|
|
super(message, 409);
|
|
this.name = 'ConflictError';
|
|
}
|
|
}
|
|
|
|
export class RateLimitError extends AppError {
|
|
constructor(message = 'Príliš veľa požiadaviek') {
|
|
super(message, 429);
|
|
this.name = 'RateLimitError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error response formatter
|
|
* @param {Error} error
|
|
* @param {boolean} includeStack - Či má zahrnúť stack trace (len development)
|
|
* @returns {Object} Formatted error response
|
|
*/
|
|
export const formatErrorResponse = (error, includeStack = false) => {
|
|
const response = {
|
|
success: false,
|
|
error: {
|
|
message: error.message || 'Interná chyba servera',
|
|
statusCode: error.statusCode || 500,
|
|
},
|
|
};
|
|
|
|
if (error.details) {
|
|
response.error.details = error.details;
|
|
}
|
|
|
|
if (includeStack && process.env.NODE_ENV === 'development') {
|
|
response.error.stack = error.stack;
|
|
}
|
|
|
|
return response;
|
|
};
|