feat: Introduce Undefined function for Nullable type and refactor DTOs to use Nullable directly
This commit is contained in:
		
							parent
							
								
									4170eb5fbd
								
							
						
					
					
						commit
						b47c29cf5a
					
				@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/oklog/ulid/v2"
 | 
						"github.com/oklog/ulid/v2"
 | 
				
			||||||
	"github.com/timetracker/backend/internal/models"
 | 
						"github.com/timetracker/backend/internal/models"
 | 
				
			||||||
 | 
						"github.com/timetracker/backend/internal/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
@ -101,7 +102,7 @@ func testUserModel(ctx context.Context) {
 | 
				
			|||||||
		Email:      "test@example.com",
 | 
							Email:      "test@example.com",
 | 
				
			||||||
		Password:   "Test@123456",
 | 
							Password:   "Test@123456",
 | 
				
			||||||
		Role:       models.RoleUser,
 | 
							Role:       models.RoleUser,
 | 
				
			||||||
		CompanyID:  companyID,
 | 
							CompanyID:  &companyID,
 | 
				
			||||||
		HourlyRate: 50.0,
 | 
							HourlyRate: 50.0,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -196,7 +197,7 @@ func testRelationships(ctx context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Test invalid ID
 | 
						// Test invalid ID
 | 
				
			||||||
	invalidID := ulid.MustNew(ulid.Timestamp(time.Now()), ulid.DefaultEntropy())
 | 
						invalidID := ulid.MustNew(ulid.Timestamp(time.Now()), ulid.DefaultEntropy())
 | 
				
			||||||
	invalidUser, err := models.GetUserByID(ctx, models.FromULID(invalidID))
 | 
						invalidUser, err := models.GetUserByID(ctx, types.FromULID(invalidID))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf("Error getting user with invalid ID: %v", err)
 | 
							log.Fatalf("Error getting user with invalid ID: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -147,7 +147,7 @@ func (h *CompanyHandler) CreateCompany(c *gin.Context) {
 | 
				
			|||||||
func (h *CompanyHandler) UpdateCompany(c *gin.Context) {
 | 
					func (h *CompanyHandler) UpdateCompany(c *gin.Context) {
 | 
				
			||||||
	// Parse ID from URL
 | 
						// Parse ID from URL
 | 
				
			||||||
	idStr := c.Param("id")
 | 
						idStr := c.Param("id")
 | 
				
			||||||
	id, err := ulid.Parse(idStr)
 | 
						id, err := types.ULIDFromString(idStr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, "Invalid company ID format")
 | 
							utils.BadRequestResponse(c, "Invalid company ID format")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -160,11 +160,8 @@ func (h *CompanyHandler) UpdateCompany(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set ID from URL
 | 
					 | 
				
			||||||
	companyUpdateDTO.ID = id.String()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Convert DTO to model
 | 
						// Convert DTO to model
 | 
				
			||||||
	companyUpdate := convertUpdateCompanyDTOToModel(companyUpdateDTO)
 | 
						companyUpdate := convertUpdateCompanyDTOToModel(companyUpdateDTO, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update company in the database
 | 
						// Update company in the database
 | 
				
			||||||
	company, err := models.UpdateCompany(c.Request.Context(), companyUpdate)
 | 
						company, err := models.UpdateCompany(c.Request.Context(), companyUpdate)
 | 
				
			||||||
@ -234,10 +231,9 @@ func convertCreateCompanyDTOToModel(dto dto.CompanyCreateDto) models.CompanyCrea
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertUpdateCompanyDTOToModel(dto dto.CompanyUpdateDto) models.CompanyUpdate {
 | 
					func convertUpdateCompanyDTOToModel(dto dto.CompanyUpdateDto, id types.ULID) models.CompanyUpdate {
 | 
				
			||||||
	id, _ := ulid.Parse(dto.ID)
 | 
					 | 
				
			||||||
	update := models.CompanyUpdate{
 | 
						update := models.CompanyUpdate{
 | 
				
			||||||
		ID: types.FromULID(id),
 | 
							ID: id,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dto.Name != nil {
 | 
						if dto.Name != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -319,16 +319,14 @@ func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) (models.Customer
 | 
				
			|||||||
		update.Name = dto.Name
 | 
							update.Name = dto.Name
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dto.CompanyID != nil {
 | 
						if dto.CompanyID.Valid {
 | 
				
			||||||
		if dto.CompanyID.Valid {
 | 
							companyID, err := types.ULIDFromString(*dto.CompanyID.Value)
 | 
				
			||||||
			companyID, err := types.ULIDFromString(*dto.CompanyID.Value)
 | 
							if err != nil {
 | 
				
			||||||
			if err != nil {
 | 
								return models.CustomerUpdate{}, fmt.Errorf("invalid company ID: %w", err)
 | 
				
			||||||
				return models.CustomerUpdate{}, fmt.Errorf("invalid company ID: %w", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			update.CompanyID = &companyID
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			update.CompanyID = nil
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							update.CompanyID = &companyID
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							update.CompanyID = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return update, nil
 | 
						return update, nil
 | 
				
			||||||
 | 
				
			|||||||
@ -220,7 +220,7 @@ func (h *ProjectHandler) CreateProject(c *gin.Context) {
 | 
				
			|||||||
func (h *ProjectHandler) UpdateProject(c *gin.Context) {
 | 
					func (h *ProjectHandler) UpdateProject(c *gin.Context) {
 | 
				
			||||||
	// Parse ID from URL
 | 
						// Parse ID from URL
 | 
				
			||||||
	idStr := c.Param("id")
 | 
						idStr := c.Param("id")
 | 
				
			||||||
	id, err := ulid.Parse(idStr)
 | 
						id, err := types.ULIDFromString(idStr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, "Invalid project ID format")
 | 
							utils.BadRequestResponse(c, "Invalid project ID format")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -233,11 +233,8 @@ func (h *ProjectHandler) UpdateProject(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set ID from URL
 | 
					 | 
				
			||||||
	projectUpdateDTO.ID = id.String()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Convert DTO to model
 | 
						// Convert DTO to model
 | 
				
			||||||
	projectUpdate, err := convertUpdateProjectDTOToModel(projectUpdateDTO)
 | 
						projectUpdate, err := convertUpdateProjectDTOToModel(projectUpdateDTO, id)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, err.Error())
 | 
							utils.BadRequestResponse(c, err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -297,49 +294,51 @@ func (h *ProjectHandler) DeleteProject(c *gin.Context) {
 | 
				
			|||||||
// Helper functions for DTO conversion
 | 
					// Helper functions for DTO conversion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertProjectToDTO(project *models.Project) dto.ProjectDto {
 | 
					func convertProjectToDTO(project *models.Project) dto.ProjectDto {
 | 
				
			||||||
 | 
						customerId := project.CustomerID.String()
 | 
				
			||||||
	return dto.ProjectDto{
 | 
						return dto.ProjectDto{
 | 
				
			||||||
		ID:         project.ID.String(),
 | 
							ID:         project.ID.String(),
 | 
				
			||||||
		CreatedAt:  project.CreatedAt,
 | 
							CreatedAt:  project.CreatedAt,
 | 
				
			||||||
		UpdatedAt:  project.UpdatedAt,
 | 
							UpdatedAt:  project.UpdatedAt,
 | 
				
			||||||
		Name:       project.Name,
 | 
							Name:       project.Name,
 | 
				
			||||||
		CustomerID: project.CustomerID.String(),
 | 
							CustomerID: &customerId,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) {
 | 
					func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) {
 | 
				
			||||||
 | 
						create := models.ProjectCreate{Name: dto.Name}
 | 
				
			||||||
	// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
 | 
						// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
 | 
				
			||||||
	customerID, err := types.ULIDFromString(dto.CustomerID)
 | 
						if dto.CustomerID != nil {
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return models.ProjectCreate{
 | 
							customerID, err := types.ULIDFromString(*dto.CustomerID)
 | 
				
			||||||
		Name:       dto.Name,
 | 
							if err != nil {
 | 
				
			||||||
		CustomerID: customerID,
 | 
								return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err)
 | 
				
			||||||
	}, nil
 | 
							}
 | 
				
			||||||
 | 
							create.CustomerID = &customerID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return create, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto) (models.ProjectUpdate, error) {
 | 
					func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto, id types.ULID) (models.ProjectUpdate, error) {
 | 
				
			||||||
	id, err := ulid.Parse(dto.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return models.ProjectUpdate{}, fmt.Errorf("invalid project ID: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	update := models.ProjectUpdate{
 | 
						update := models.ProjectUpdate{
 | 
				
			||||||
		ID: types.FromULID(id),
 | 
							ID: id,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dto.Name != nil {
 | 
						if dto.Name != nil {
 | 
				
			||||||
		update.Name = dto.Name
 | 
							update.Name = dto.Name
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dto.CustomerID != nil {
 | 
						if dto.CustomerID.Valid {
 | 
				
			||||||
		// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
 | 
							if dto.CustomerID.Value == nil {
 | 
				
			||||||
		customerID, err := types.ULIDFromString(*dto.CustomerID)
 | 
								update.CustomerID = nil
 | 
				
			||||||
		if err != nil {
 | 
					
 | 
				
			||||||
			return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
 | 
							} else {
 | 
				
			||||||
 | 
								// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
 | 
				
			||||||
 | 
								customerID, err := types.ULIDFromString(*dto.CustomerID.Value)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								update.CustomerID = &customerID
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		update.CustomerID = &customerID
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return update, nil
 | 
						return update, nil
 | 
				
			||||||
 | 
				
			|||||||
@ -326,7 +326,7 @@ func (h *TimeEntryHandler) CreateTimeEntry(c *gin.Context) {
 | 
				
			|||||||
func (h *TimeEntryHandler) UpdateTimeEntry(c *gin.Context) {
 | 
					func (h *TimeEntryHandler) UpdateTimeEntry(c *gin.Context) {
 | 
				
			||||||
	// Parse ID from URL
 | 
						// Parse ID from URL
 | 
				
			||||||
	idStr := c.Param("id")
 | 
						idStr := c.Param("id")
 | 
				
			||||||
	id, err := ulid.Parse(idStr)
 | 
						id, err := types.ULIDFromString(idStr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, "Invalid time entry ID format")
 | 
							utils.BadRequestResponse(c, "Invalid time entry ID format")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -339,11 +339,8 @@ func (h *TimeEntryHandler) UpdateTimeEntry(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set ID from URL
 | 
					 | 
				
			||||||
	timeEntryUpdateDTO.ID = id.String()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Convert DTO to model
 | 
						// Convert DTO to model
 | 
				
			||||||
	timeEntryUpdate, err := convertUpdateTimeEntryDTOToModel(timeEntryUpdateDTO)
 | 
						timeEntryUpdate, err := convertUpdateTimeEntryDTOToModel(timeEntryUpdateDTO, id)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, err.Error())
 | 
							utils.BadRequestResponse(c, err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -445,13 +442,10 @@ func convertCreateTimeEntryDTOToModel(dto dto.TimeEntryCreateDto) (models.TimeEn
 | 
				
			|||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertUpdateTimeEntryDTOToModel(dto dto.TimeEntryUpdateDto) (models.TimeEntryUpdate, error) {
 | 
					func convertUpdateTimeEntryDTOToModel(dto dto.TimeEntryUpdateDto, id types.ULID) (models.TimeEntryUpdate, error) {
 | 
				
			||||||
	id, err := ulid.Parse(dto.ID)
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return models.TimeEntryUpdate{}, fmt.Errorf("invalid time entry ID: %w", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	update := models.TimeEntryUpdate{
 | 
						update := models.TimeEntryUpdate{
 | 
				
			||||||
		ID: types.FromULID(id),
 | 
							ID: id,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dto.UserID != nil {
 | 
						if dto.UserID != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -148,7 +148,7 @@ func (h *UserHandler) CreateUser(c *gin.Context) {
 | 
				
			|||||||
func (h *UserHandler) UpdateUser(c *gin.Context) {
 | 
					func (h *UserHandler) UpdateUser(c *gin.Context) {
 | 
				
			||||||
	// Parse ID from URL
 | 
						// Parse ID from URL
 | 
				
			||||||
	idStr := c.Param("id")
 | 
						idStr := c.Param("id")
 | 
				
			||||||
	id, err := ulid.Parse(idStr)
 | 
						id, err := types.ULIDFromString(idStr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		utils.BadRequestResponse(c, "Invalid user ID format")
 | 
							utils.BadRequestResponse(c, "Invalid user ID format")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -161,13 +161,9 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Set ID from URL
 | 
					 | 
				
			||||||
	userUpdateDTO.ID = id.String()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Convert DTO to Model
 | 
						// Convert DTO to Model
 | 
				
			||||||
	idWrapper := types.FromULID(id)
 | 
					 | 
				
			||||||
	update := models.UserUpdate{
 | 
						update := models.UserUpdate{
 | 
				
			||||||
		ID: idWrapper,
 | 
							ID: id,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if userUpdateDTO.Email != nil {
 | 
						if userUpdateDTO.Email != nil {
 | 
				
			||||||
@ -179,22 +175,23 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
 | 
				
			|||||||
	if userUpdateDTO.Role != nil {
 | 
						if userUpdateDTO.Role != nil {
 | 
				
			||||||
		update.Role = userUpdateDTO.Role
 | 
							update.Role = userUpdateDTO.Role
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if userUpdateDTO.CompanyID != nil {
 | 
					
 | 
				
			||||||
		if userUpdateDTO.CompanyID.Valid {
 | 
						if userUpdateDTO.CompanyID.Valid {
 | 
				
			||||||
 | 
							if userUpdateDTO.CompanyID.Value != nil {
 | 
				
			||||||
			companyID, err := types.ULIDFromString(*userUpdateDTO.CompanyID.Value)
 | 
								companyID, err := types.ULIDFromString(*userUpdateDTO.CompanyID.Value)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				utils.BadRequestResponse(c, "Invalid company ID format")
 | 
									utils.BadRequestResponse(c, "Invalid company ID format")
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			update.CompanyID = &companyID
 | 
								update.CompanyID = types.NewNullable(companyID)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			update.CompanyID = nil
 | 
								update.CompanyID = types.Null[types.ULID]()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if userUpdateDTO.HourlyRate != nil {
 | 
						if userUpdateDTO.HourlyRate != nil {
 | 
				
			||||||
		update.HourlyRate = userUpdateDTO.HourlyRate
 | 
							update.HourlyRate = userUpdateDTO.HourlyRate
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update user in the database
 | 
						// Update user in the database
 | 
				
			||||||
	user, err := models.UpdateUser(c.Request.Context(), update)
 | 
						user, err := models.UpdateUser(c.Request.Context(), update)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,9 +17,7 @@ type CompanyCreateDto struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CompanyUpdateDto struct {
 | 
					type CompanyUpdateDto struct {
 | 
				
			||||||
	ID           string     `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
						CreatedAt *time.Time `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	CreatedAt    *time.Time `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt *time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	UpdatedAt    *time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						Name      *string    `json:"name" example:"Acme Corp"`
 | 
				
			||||||
	LastEditorID *string    `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
					 | 
				
			||||||
	Name         *string    `json:"name" example:"Acme Corp"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -22,11 +22,11 @@ type CustomerCreateDto struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CustomerUpdateDto struct {
 | 
					type CustomerUpdateDto struct {
 | 
				
			||||||
	ID           string                  `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
						ID           string                 `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	CreatedAt    *time.Time              `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
						CreatedAt    *time.Time             `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	UpdatedAt    *time.Time              `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt    *time.Time             `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	LastEditorID *string                 `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
						LastEditorID *string                `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	Name         *string                 `json:"name" example:"John Doe"`
 | 
						Name         *string                `json:"name" example:"John Doe"`
 | 
				
			||||||
	CompanyID    *types.Nullable[string] `json:"companyId" example:"01HGW2BBG0000000000000000"`
 | 
						CompanyID    types.Nullable[string] `json:"companyId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	OwnerUserID  *types.Nullable[string] `json:"owningUserID" example:"01HGW2BBG0000000000000000"`
 | 
						OwnerUserID  types.Nullable[string] `json:"owningUserID" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,8 @@ package dto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/timetracker/backend/internal/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectDto struct {
 | 
					type ProjectDto struct {
 | 
				
			||||||
@ -10,19 +12,17 @@ type ProjectDto struct {
 | 
				
			|||||||
	UpdatedAt    time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt    time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	LastEditorID string    `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
						LastEditorID string    `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	Name         string    `json:"name" example:"Time Tracking App"`
 | 
						Name         string    `json:"name" example:"Time Tracking App"`
 | 
				
			||||||
	CustomerID   string    `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
						CustomerID   *string   `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectCreateDto struct {
 | 
					type ProjectCreateDto struct {
 | 
				
			||||||
	Name       string `json:"name" example:"Time Tracking App"`
 | 
						Name       string  `json:"name" example:"Time Tracking App"`
 | 
				
			||||||
	CustomerID string `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
						CustomerID *string `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectUpdateDto struct {
 | 
					type ProjectUpdateDto struct {
 | 
				
			||||||
	ID           string     `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
						CreatedAt  *time.Time             `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	CreatedAt    *time.Time `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt  *time.Time             `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	UpdatedAt    *time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						Name       *string                `json:"name" example:"Time Tracking App"`
 | 
				
			||||||
	LastEditorID *string    `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
						CustomerID types.Nullable[string] `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	Name         *string    `json:"name" example:"Time Tracking App"`
 | 
					 | 
				
			||||||
	CustomerID   *string    `json:"customerId" example:"01HGW2BBG0000000000000000"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -29,15 +29,13 @@ type TimeEntryCreateDto struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TimeEntryUpdateDto struct {
 | 
					type TimeEntryUpdateDto struct {
 | 
				
			||||||
	ID           string     `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
						CreatedAt   *time.Time `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	CreatedAt    *time.Time `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt   *time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	UpdatedAt    *time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						UserID      *string    `json:"userId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	LastEditorID *string    `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
						ProjectID   *string    `json:"projectId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	UserID       *string    `json:"userId" example:"01HGW2BBG0000000000000000"`
 | 
						ActivityID  *string    `json:"activityId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	ProjectID    *string    `json:"projectId" example:"01HGW2BBG0000000000000000"`
 | 
						Start       *time.Time `json:"start" example:"2024-01-01T08:00:00Z"`
 | 
				
			||||||
	ActivityID   *string    `json:"activityId" example:"01HGW2BBG0000000000000000"`
 | 
						End         *time.Time `json:"end" example:"2024-01-01T17:00:00Z"`
 | 
				
			||||||
	Start        *time.Time `json:"start" example:"2024-01-01T08:00:00Z"`
 | 
						Description *string    `json:"description" example:"Working on the Time Tracking App"`
 | 
				
			||||||
	End          *time.Time `json:"end" example:"2024-01-01T17:00:00Z"`
 | 
						Billable    *int       `json:"billable" example:"100"` // Percentage (0-100)
 | 
				
			||||||
	Description  *string    `json:"description" example:"Working on the Time Tracking App"`
 | 
					 | 
				
			||||||
	Billable     *int       `json:"billable" example:"100"` // Percentage (0-100)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,13 +26,12 @@ type UserCreateDto struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserUpdateDto struct {
 | 
					type UserUpdateDto struct {
 | 
				
			||||||
	ID           string                  `json:"id" example:"01HGW2BBG0000000000000000"`
 | 
						CreatedAt    *time.Time             `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	CreatedAt    *time.Time              `json:"createdAt" example:"2024-01-01T00:00:00Z"`
 | 
						UpdatedAt    *time.Time             `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
				
			||||||
	UpdatedAt    *time.Time              `json:"updatedAt" example:"2024-01-01T00:00:00Z"`
 | 
						LastEditorID *string                `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	LastEditorID *string                 `json:"lastEditorID" example:"01HGW2BBG0000000000000000"`
 | 
						Email        *string                `json:"email" example:"test@example.com"`
 | 
				
			||||||
	Email        *string                 `json:"email" example:"test@example.com"`
 | 
						Password     *string                `json:"password" example:"password123"`
 | 
				
			||||||
	Password     *string                 `json:"password" example:"password123"`
 | 
						Role         *string                `json:"role" example:"admin"`
 | 
				
			||||||
	Role         *string                 `json:"role" example:"admin"`
 | 
						CompanyID    types.Nullable[string] `json:"companyId" example:"01HGW2BBG0000000000000000"`
 | 
				
			||||||
	CompanyID    *types.Nullable[string] `json:"companyId" example:"01HGW2BBG0000000000000000"`
 | 
						HourlyRate   *float64               `json:"hourlyRate" example:"50.00"`
 | 
				
			||||||
	HourlyRate   *float64                `json:"hourlyRate" example:"50.00"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -24,7 +23,6 @@ func (eb *EntityBase) BeforeCreate(tx *gorm.DB) error {
 | 
				
			|||||||
		entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
 | 
							entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
 | 
				
			||||||
		newID := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
 | 
							newID := ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
 | 
				
			||||||
		eb.ID = types.ULID{ULID: newID}
 | 
							eb.ID = types.ULID{ULID: newID}
 | 
				
			||||||
		fmt.Println("Generated ID:", eb.ID)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,8 @@ import (
 | 
				
			|||||||
// Project represents a project in the system
 | 
					// Project represents a project in the system
 | 
				
			||||||
type Project struct {
 | 
					type Project struct {
 | 
				
			||||||
	EntityBase
 | 
						EntityBase
 | 
				
			||||||
	Name       string     `gorm:"column:name;not null"`
 | 
						Name       string      `gorm:"column:name;not null"`
 | 
				
			||||||
	CustomerID types.ULID `gorm:"column:customer_id;type:bytea;not null"`
 | 
						CustomerID *types.ULID `gorm:"column:customer_id;type:bytea;not null"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Relationships (for Eager Loading)
 | 
						// Relationships (for Eager Loading)
 | 
				
			||||||
	Customer *Customer `gorm:"foreignKey:CustomerID"`
 | 
						Customer *Customer `gorm:"foreignKey:CustomerID"`
 | 
				
			||||||
@ -28,7 +28,7 @@ func (Project) TableName() string {
 | 
				
			|||||||
// ProjectCreate contains the fields for creating a new project
 | 
					// ProjectCreate contains the fields for creating a new project
 | 
				
			||||||
type ProjectCreate struct {
 | 
					type ProjectCreate struct {
 | 
				
			||||||
	Name       string
 | 
						Name       string
 | 
				
			||||||
	CustomerID types.ULID
 | 
						CustomerID *types.ULID
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ProjectUpdate contains the updatable fields of a project
 | 
					// ProjectUpdate contains the updatable fields of a project
 | 
				
			||||||
@ -122,12 +122,14 @@ func CreateProject(ctx context.Context, create ProjectCreate) (*Project, error)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if the customer exists
 | 
						// Check if the customer exists
 | 
				
			||||||
	customer, err := GetCustomerByID(ctx, create.CustomerID)
 | 
						if create.CustomerID == nil {
 | 
				
			||||||
	if err != nil {
 | 
							customer, err := GetCustomerByID(ctx, *create.CustomerID)
 | 
				
			||||||
		return nil, fmt.Errorf("error checking the customer: %w", err)
 | 
							if err != nil {
 | 
				
			||||||
	}
 | 
								return nil, fmt.Errorf("error checking the customer: %w", err)
 | 
				
			||||||
	if customer == nil {
 | 
							}
 | 
				
			||||||
		return nil, errors.New("the specified customer does not exist")
 | 
							if customer == nil {
 | 
				
			||||||
 | 
								return nil, errors.New("the specified customer does not exist")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project := Project{
 | 
						project := Project{
 | 
				
			||||||
 | 
				
			|||||||
@ -63,12 +63,12 @@ type UserCreate struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UserUpdate contains the updatable fields of a user
 | 
					// UserUpdate contains the updatable fields of a user
 | 
				
			||||||
type UserUpdate struct {
 | 
					type UserUpdate struct {
 | 
				
			||||||
	ID         types.ULID  `gorm:"-"` // Exclude from updates
 | 
						ID         types.ULID                 `gorm:"-"` // Exclude from updates
 | 
				
			||||||
	Email      *string     `gorm:"column:email"`
 | 
						Email      *string                    `gorm:"column:email"`
 | 
				
			||||||
	Password   *string     `gorm:"-"` // Not stored directly in DB
 | 
						Password   *string                    `gorm:"-"` // Not stored directly in DB
 | 
				
			||||||
	Role       *string     `gorm:"column:role"`
 | 
						Role       *string                    `gorm:"column:role"`
 | 
				
			||||||
	CompanyID  *types.ULID `gorm:"column:company_id"`
 | 
						CompanyID  types.Nullable[types.ULID] `gorm:"column:company_id"`
 | 
				
			||||||
	HourlyRate *float64    `gorm:"column:hourly_rate"`
 | 
						HourlyRate *float64                   `gorm:"column:hourly_rate"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PasswordData contains the data for password hash and salt
 | 
					// PasswordData contains the data for password hash and salt
 | 
				
			||||||
@ -448,13 +448,15 @@ func UpdateUser(ctx context.Context, update UserUpdate) (*User, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If CompanyID is updated, check if it exists
 | 
							// If CompanyID is updated, check if it exists
 | 
				
			||||||
		if update.CompanyID != nil && (user.CompanyID == nil || update.CompanyID.Compare(*user.CompanyID) != 0) {
 | 
							if update.CompanyID.Valid && update.CompanyID.Value != nil {
 | 
				
			||||||
			var companyCount int64
 | 
								if user.CompanyID == nil || *update.CompanyID.Value != *user.CompanyID {
 | 
				
			||||||
			if err := tx.Model(&Company{}).Where("id = ?", *update.CompanyID).Count(&companyCount).Error; err != nil {
 | 
									var companyCount int64
 | 
				
			||||||
				return fmt.Errorf("error checking company: %w", err)
 | 
									if err := tx.Model(&Company{}).Where("id = ?", *update.CompanyID.Value).Count(&companyCount).Error; err != nil {
 | 
				
			||||||
			}
 | 
										return fmt.Errorf("error checking company: %w", err)
 | 
				
			||||||
			if companyCount == 0 {
 | 
									}
 | 
				
			||||||
				return errors.New("the specified company does not exist")
 | 
									if companyCount == 0 {
 | 
				
			||||||
 | 
										return errors.New("the specified company does not exist")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -484,8 +486,13 @@ func UpdateUser(ctx context.Context, update UserUpdate) (*User, error) {
 | 
				
			|||||||
		if update.Role != nil {
 | 
							if update.Role != nil {
 | 
				
			||||||
			updates["role"] = *update.Role
 | 
								updates["role"] = *update.Role
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if update.CompanyID != nil {
 | 
							if update.CompanyID.Valid {
 | 
				
			||||||
			updates["company_id"] = *update.CompanyID
 | 
								if update.CompanyID.Value == nil {
 | 
				
			||||||
 | 
									updates["company_id"] = nil
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									updates["company_id"] = *update.CompanyID.Value
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if update.HourlyRate != nil {
 | 
							if update.HourlyRate != nil {
 | 
				
			||||||
			updates["hourly_rate"] = *update.HourlyRate
 | 
								updates["hourly_rate"] = *update.HourlyRate
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,10 @@ func NewNullable[T any](value T) Nullable[T] {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Null erstellt eine leere Nullable-Instanz (ungesetzt)
 | 
					// Null erstellt eine leere Nullable-Instanz (ungesetzt)
 | 
				
			||||||
func Null[T any]() Nullable[T] {
 | 
					func Null[T any]() Nullable[T] {
 | 
				
			||||||
 | 
						return Nullable[T]{Valid: true}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Undefined[T any]() Nullable[T] {
 | 
				
			||||||
	return Nullable[T]{Valid: false}
 | 
						return Nullable[T]{Valid: false}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user