time-tracker/backend/internal/api/handlers/timeentry_handler.go

317 lines
11 KiB
Go

package handlers
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/timetracker/backend/internal/api/dto"
"github.com/timetracker/backend/internal/api/responses"
"github.com/timetracker/backend/internal/api/utils"
"github.com/timetracker/backend/internal/models"
"github.com/timetracker/backend/internal/types"
)
// TimeEntryHandler handles time entry-related API endpoints
type TimeEntryHandler struct{}
// NewTimeEntryHandler creates a new TimeEntryHandler
func NewTimeEntryHandler() *TimeEntryHandler {
return &TimeEntryHandler{}
}
// GetTimeEntries handles GET /time-entries
//
// @Summary Get all time entries
// @Description Get a list of all time entries
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} utils.Response{data=[]dto.TimeEntryDto}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries [get]
func (h *TimeEntryHandler) GetTimeEntries(c *gin.Context) {
utils.HandleGetAll(c, models.GetAllTimeEntries, convertTimeEntryToDTO, "time entries")
}
// GetTimeEntryByID handles GET /time-entries/:id
//
// @Summary Get time entry by ID
// @Description Get a time entry by its ID
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Time Entry ID"
// @Success 200 {object} utils.Response{data=dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 404 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/{id} [get]
func (h *TimeEntryHandler) GetTimeEntryByID(c *gin.Context) {
utils.HandleGetByID(c, models.GetTimeEntryByID, convertTimeEntryToDTO, "time entry")
}
// GetTimeEntriesByUserID handles GET /time-entries/user/:userId
//
// @Summary Get time entries by user ID
// @Description Get a list of time entries for a specific user
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userId path string true "User ID"
// @Success 200 {object} utils.Response{data=[]dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/user/{userId} [get]
func (h *TimeEntryHandler) GetTimeEntriesByUserID(c *gin.Context) {
utils.HandleGetByFilter(c, models.GetTimeEntriesByUserID, convertTimeEntryToDTO, "time entries", "userId")
}
// GetMyTimeEntries handles GET /time-entries/me
//
// @Summary Get current user's time entries
// @Description Get a list of time entries for the currently authenticated user
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} utils.Response{data=[]dto.TimeEntryDto}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/me [get]
func (h *TimeEntryHandler) GetMyTimeEntries(c *gin.Context) {
utils.HandleGetByUserID(c, models.GetTimeEntriesByUserID, convertTimeEntryToDTO, "time entries")
}
// GetTimeEntriesByProjectID handles GET /time-entries/project/:projectId
//
// @Summary Get time entries by project ID
// @Description Get a list of time entries for a specific project
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param projectId path string true "Project ID"
// @Success 200 {object} utils.Response{data=[]dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/project/{projectId} [get]
func (h *TimeEntryHandler) GetTimeEntriesByProjectID(c *gin.Context) {
utils.HandleGetByFilter(c, models.GetTimeEntriesByProjectID, convertTimeEntryToDTO, "time entries", "projectId")
}
// GetTimeEntriesByDateRange handles GET /time-entries/range
//
// @Summary Get time entries by date range
// @Description Get a list of time entries within a specific date range
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param start query string true "Start date (ISO 8601 format)"
// @Param end query string true "End date (ISO 8601 format)"
// @Success 200 {object} utils.Response{data=[]dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/range [get]
func (h *TimeEntryHandler) GetTimeEntriesByDateRange(c *gin.Context) {
utils.HandleGetByDateRange(c, models.GetTimeEntriesByDateRange, convertTimeEntryToDTO, "time entries")
}
// CreateTimeEntry handles POST /time-entries
//
// @Summary Create a new time entry
// @Description Create a new time entry
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param timeEntry body dto.TimeEntryCreateDto true "Time Entry data"
// @Success 201 {object} utils.Response{data=dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries [post]
func (h *TimeEntryHandler) CreateTimeEntry(c *gin.Context) {
utils.HandleCreate(c, createTimeEntryWrapper, convertTimeEntryToDTO, "time entry")
}
// UpdateTimeEntry handles PUT /time-entries/:id
//
// @Summary Update a time entry
// @Description Update an existing time entry
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Time Entry ID"
// @Param timeEntry body dto.TimeEntryUpdateDto true "Time Entry data"
// @Success 200 {object} utils.Response{data=dto.TimeEntryDto}
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 404 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/{id} [put]
func (h *TimeEntryHandler) UpdateTimeEntry(c *gin.Context) {
utils.HandleUpdate(c, models.UpdateTimeEntry, convertTimeEntryToDTO, prepareTimeEntryUpdate, "time entry")
}
// DeleteTimeEntry handles DELETE /time-entries/:id
//
// @Summary Delete a time entry
// @Description Delete a time entry by its ID
// @Tags time-entries
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Time Entry ID"
// @Success 204 {object} utils.Response
// @Failure 400 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /time-entries/{id} [delete]
func (h *TimeEntryHandler) DeleteTimeEntry(c *gin.Context) {
utils.HandleDelete(c, models.DeleteTimeEntry, "time entry")
}
// Helper functions for DTO conversion
func convertTimeEntryToDTO(timeEntry *models.TimeEntry) dto.TimeEntryDto {
return dto.TimeEntryDto{
ID: timeEntry.ID.String(),
CreatedAt: timeEntry.CreatedAt,
UpdatedAt: timeEntry.UpdatedAt,
UserID: timeEntry.UserID.String(), // Simplified conversion
ProjectID: timeEntry.ProjectID.String(), // Simplified conversion
ActivityID: timeEntry.ActivityID.String(), // Simplified conversion
Start: timeEntry.Start,
End: timeEntry.End,
Description: timeEntry.Description,
Billable: timeEntry.Billable,
}
}
func convertCreateTimeEntryDTOToModel(dto dto.TimeEntryCreateDto) (models.TimeEntryCreate, error) {
// Convert IDs from int to ULID (this is a simplification, adjust as needed)
userID, err := types.ULIDFromString(dto.UserID)
if err != nil {
return models.TimeEntryCreate{}, fmt.Errorf("invalid user ID: %w", err)
}
projectID, err := types.ULIDFromString(dto.ProjectID)
if err != nil {
return models.TimeEntryCreate{}, fmt.Errorf("invalid project ID: %w", err)
}
activityID, err := types.ULIDFromString(dto.ActivityID)
if err != nil {
return models.TimeEntryCreate{}, fmt.Errorf("invalid activity ID: %w", err)
}
return models.TimeEntryCreate{
UserID: userID,
ProjectID: projectID,
ActivityID: activityID,
Start: dto.Start,
End: dto.End,
Description: dto.Description,
Billable: dto.Billable,
}, nil
}
func convertUpdateTimeEntryDTOToModel(dto dto.TimeEntryUpdateDto, id types.ULID) (models.TimeEntryUpdate, error) {
update := models.TimeEntryUpdate{
ID: id,
}
if dto.UserID != nil {
userID, err := types.ULIDFromString(*dto.UserID)
if err != nil {
return models.TimeEntryUpdate{}, fmt.Errorf("invalid user ID: %w", err)
}
update.UserID = &userID
}
if dto.ProjectID != nil {
projectID, err := types.ULIDFromString(*dto.ProjectID)
if err != nil {
return models.TimeEntryUpdate{}, fmt.Errorf("invalid project ID: %w", err)
}
update.ProjectID = &projectID
}
if dto.ActivityID != nil {
activityID, err := types.ULIDFromString(*dto.ActivityID)
if err != nil {
return models.TimeEntryUpdate{}, fmt.Errorf("invalid activity ID: %w", err)
}
update.ActivityID = &activityID
}
if dto.Start != nil {
update.Start = dto.Start
}
if dto.End != nil {
update.End = dto.End
}
if dto.Description != nil {
update.Description = dto.Description
}
if dto.Billable != nil {
update.Billable = dto.Billable
}
return update, nil
}
// prepareTimeEntryUpdate prepares the time entry update object by parsing the ID, binding the JSON, and converting the DTO to a model
func prepareTimeEntryUpdate(c *gin.Context) (models.TimeEntryUpdate, error) {
// Parse ID from URL
idStr := c.Param("id")
id, err := types.ULIDFromString(idStr)
if err != nil {
responses.BadRequestResponse(c, "Invalid time entry ID format")
return models.TimeEntryUpdate{}, err
}
// Parse request body
var timeEntryUpdateDTO dto.TimeEntryUpdateDto
if err := utils.BindJSON(c, &timeEntryUpdateDTO); err != nil {
responses.BadRequestResponse(c, err.Error())
return models.TimeEntryUpdate{}, err
}
// Convert DTO to model
timeEntryUpdate, err := convertUpdateTimeEntryDTOToModel(timeEntryUpdateDTO, id)
if err != nil {
responses.BadRequestResponse(c, err.Error())
return models.TimeEntryUpdate{}, err
}
return timeEntryUpdate, nil
}
// createTimeEntryWrapper is a wrapper function for models.CreateTimeEntry that takes a DTO as input
func createTimeEntryWrapper(ctx context.Context, createDTO dto.TimeEntryCreateDto) (*models.TimeEntry, error) {
// Convert DTO to model
timeEntryCreate, err := convertCreateTimeEntryDTOToModel(createDTO)
if err != nil {
return nil, err
}
// Call the original function
return models.CreateTimeEntry(ctx, timeEntryCreate)
}