317 lines
11 KiB
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)
|
|
}
|