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:
richardtekula
2025-11-21 08:35:30 +01:00
parent 05be898259
commit bb851639b8
27 changed files with 2847 additions and 532 deletions

View File

@@ -5,15 +5,29 @@ import * as emailAccountService from '../services/email-account.service.js';
import { logger } from '../utils/logger.js';
/**
* Get all contacts for authenticated user
* GET /api/contacts?accountId=xxx (optional)
* Get all contacts for an email account
* GET /api/contacts?accountId=xxx (required)
*/
export const getContacts = async (req, res) => {
try {
const userId = req.userId;
const { accountId } = req.query;
const contacts = await contactService.getUserContacts(userId, accountId || null);
if (!accountId) {
return res.status(400).json({
success: false,
error: {
message: 'accountId je povinný parameter',
statusCode: 400,
},
});
}
// Verify user has access to this email account
await emailAccountService.getEmailAccountById(accountId, userId);
// Get contacts for email account
const contacts = await contactService.getContactsForEmailAccount(accountId);
res.status(200).json({
success: true,
@@ -70,7 +84,7 @@ export const discoverContacts = async (req, res) => {
const potentialContacts = await discoverContactsFromJMAP(
jmapConfig,
userId,
emailAccount.id, // emailAccountId
search,
parseInt(limit)
);
@@ -134,12 +148,12 @@ export const addContact = async (req, res) => {
const jmapConfig = getJmapConfigFromAccount(emailAccount);
const contact = await contactService.addContact(
userId,
emailAccount.id,
jmapConfig,
email,
name,
notes
notes,
userId // addedByUserId
);
res.status(201).json({
@@ -155,14 +169,28 @@ export const addContact = async (req, res) => {
/**
* Remove a contact
* DELETE /api/contacts/:contactId
* DELETE /api/contacts/:contactId?accountId=xxx
*/
export const removeContact = async (req, res) => {
try {
const userId = req.userId;
const { contactId } = req.params;
const { accountId } = req.query;
const result = await contactService.removeContact(userId, contactId);
if (!accountId) {
return res.status(400).json({
success: false,
error: {
message: 'accountId je povinný parameter',
statusCode: 400,
},
});
}
// Verify user has access to this email account
await emailAccountService.getEmailAccountById(accountId, userId);
const result = await contactService.removeContact(contactId, accountId);
res.status(200).json({
success: true,
@@ -176,15 +204,29 @@ export const removeContact = async (req, res) => {
/**
* Update a contact
* PATCH /api/contacts/:contactId
* PATCH /api/contacts/:contactId?accountId=xxx
*/
export const updateContact = async (req, res) => {
try {
const userId = req.userId;
const { contactId } = req.params;
const { accountId } = req.query;
const { name, notes } = req.body;
const updated = await contactService.updateContact(userId, contactId, { name, notes });
if (!accountId) {
return res.status(400).json({
success: false,
error: {
message: 'accountId je povinný parameter',
statusCode: 400,
},
});
}
// Verify user has access to this email account
await emailAccountService.getEmailAccountById(accountId, userId);
const updated = await contactService.updateContact(contactId, accountId, { name, notes });
res.status(200).json({
success: true,