Add Time Tracking backend API
Implementovaný kompletný backend pre time tracking: - Nová tabuľka time_entries s foreign keys na users, projects, todos, companies - Service layer s business logikou pre CRUD operácie - Controller pre všetky endpointy - Validačné schémy pomocou Zod - Routes s autentifikáciou a validáciou - Endpointy: * POST /api/time-tracking/start - Spustenie timeru * POST /api/time-tracking/:id/stop - Zastavenie timeru * GET /api/time-tracking/running - Získanie bežiaceho záznamu * GET /api/time-tracking/month/:year/:month - Mesačné záznamy * GET /api/time-tracking/stats/monthly/:year/:month - Mesačné štatistiky * PATCH /api/time-tracking/:id - Aktualizácia záznamu * DELETE /api/time-tracking/:id - Zmazanie záznamu - Podpora pre isEdited flag pri editácii - Kalkulácia duration v minútach 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
246
src/controllers/time-tracking.controller.js
Normal file
246
src/controllers/time-tracking.controller.js
Normal file
@@ -0,0 +1,246 @@
|
||||
import * as timeTrackingService from '../services/time-tracking.service.js';
|
||||
import { formatErrorResponse } from '../utils/errors.js';
|
||||
|
||||
/**
|
||||
* Start a new time entry
|
||||
* POST /api/time-tracking/start
|
||||
*/
|
||||
export const startTimeEntry = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { projectId, todoId, companyId, description } = req.body;
|
||||
|
||||
const entry = await timeTrackingService.startTimeEntry(userId, {
|
||||
projectId,
|
||||
todoId,
|
||||
companyId,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
message: 'Časovač bol spustený',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop a running time entry
|
||||
* POST /api/time-tracking/:entryId/stop
|
||||
*/
|
||||
export const stopTimeEntry = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { entryId } = req.params;
|
||||
const { projectId, todoId, companyId, description } = req.body;
|
||||
|
||||
const entry = await timeTrackingService.stopTimeEntry(entryId, userId, {
|
||||
projectId,
|
||||
todoId,
|
||||
companyId,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
message: 'Časovač bol zastavený',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get running time entry for current user
|
||||
* GET /api/time-tracking/running
|
||||
*/
|
||||
export const getRunningTimeEntry = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
|
||||
const entry = await timeTrackingService.getRunningTimeEntry(userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all time entries for current user with filters
|
||||
* GET /api/time-tracking?projectId=xxx&todoId=xxx&companyId=xxx&startDate=xxx&endDate=xxx
|
||||
*/
|
||||
export const getAllTimeEntries = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { projectId, todoId, companyId, startDate, endDate } = req.query;
|
||||
|
||||
const filters = {
|
||||
projectId,
|
||||
todoId,
|
||||
companyId,
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
|
||||
const entries = await timeTrackingService.getAllTimeEntries(userId, filters);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: entries.length,
|
||||
data: entries,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get time entries for a specific month
|
||||
* GET /api/time-tracking/month/:year/:month
|
||||
*/
|
||||
export const getMonthlyTimeEntries = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { year, month } = req.params;
|
||||
|
||||
const entries = await timeTrackingService.getMonthlyTimeEntries(
|
||||
userId,
|
||||
parseInt(year),
|
||||
parseInt(month)
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: entries.length,
|
||||
data: entries,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get time entry by ID
|
||||
* GET /api/time-tracking/:entryId
|
||||
*/
|
||||
export const getTimeEntryById = async (req, res) => {
|
||||
try {
|
||||
const { entryId } = req.params;
|
||||
|
||||
const entry = await timeTrackingService.getTimeEntryById(entryId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get time entry with related data
|
||||
* GET /api/time-tracking/:entryId/details
|
||||
*/
|
||||
export const getTimeEntryWithRelations = async (req, res) => {
|
||||
try {
|
||||
const { entryId } = req.params;
|
||||
|
||||
const entry = await timeTrackingService.getTimeEntryWithRelations(entryId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update time entry
|
||||
* PATCH /api/time-tracking/:entryId
|
||||
*/
|
||||
export const updateTimeEntry = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { entryId } = req.params;
|
||||
const { startTime, endTime, projectId, todoId, companyId, description } = req.body;
|
||||
|
||||
const entry = await timeTrackingService.updateTimeEntry(entryId, userId, {
|
||||
startTime,
|
||||
endTime,
|
||||
projectId,
|
||||
todoId,
|
||||
companyId,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: entry,
|
||||
message: 'Záznam bol aktualizovaný',
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete time entry
|
||||
* DELETE /api/time-tracking/:entryId
|
||||
*/
|
||||
export const deleteTimeEntry = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { entryId } = req.params;
|
||||
|
||||
const result = await timeTrackingService.deleteTimeEntry(entryId, userId);
|
||||
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get monthly statistics
|
||||
* GET /api/time-tracking/stats/monthly/:year/:month
|
||||
*/
|
||||
export const getMonthlyStats = async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const { year, month } = req.params;
|
||||
|
||||
const stats = await timeTrackingService.getMonthlyStats(
|
||||
userId,
|
||||
parseInt(year),
|
||||
parseInt(month)
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: stats,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorResponse = formatErrorResponse(error, process.env.NODE_ENV === 'development');
|
||||
res.status(error.statusCode || 500).json(errorResponse);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user