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) }