234 lines
6.2 KiB
Go

package models
import (
"context"
"errors"
"fmt"
"github.com/oklog/ulid/v2"
"github.com/timetracker/backend/internal/types"
"gorm.io/gorm"
)
// Project represents a project in the system
type Project struct {
EntityBase
Name string `gorm:"column:name;not null"`
CustomerID *types.ULID `gorm:"column:customer_id;type:bytea;not null"`
// Relationships (for Eager Loading)
Customer *Customer `gorm:"foreignKey:CustomerID"`
}
// TableName specifies the table name for GORM
func (Project) TableName() string {
return "projects"
}
// ProjectCreate contains the fields for creating a new project
type ProjectCreate struct {
Name string
CustomerID *types.ULID
}
// ProjectUpdate contains the updatable fields of a project
type ProjectUpdate struct {
ID types.ULID `gorm:"-"` // Exclude from updates
Name *string `gorm:"column:name"`
CustomerID *types.ULID `gorm:"column:customer_id"`
}
// Validate checks if the Create struct contains valid data
func (pc *ProjectCreate) Validate() error {
if pc.Name == "" {
return errors.New("project name cannot be empty")
}
// Check for valid CustomerID
if pc.CustomerID.Compare(types.ULID{}) == 0 {
return errors.New("customerID cannot be empty")
}
return nil
}
// Validate checks if the Update struct contains valid data
func (pu *ProjectUpdate) Validate() error {
if pu.Name != nil && *pu.Name == "" {
return errors.New("project name cannot be empty")
}
return nil
}
// GetProjectByID finds a project by its ID
func GetProjectByID(ctx context.Context, id types.ULID) (*Project, error) {
var project Project
result := GetEngine(ctx).Where("id = ?", id).First(&project)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &project, nil
}
// GetProjectWithCustomer loads a project with the associated customer information
func GetProjectWithCustomer(ctx context.Context, id ulid.ULID) (*Project, error) {
var project Project
result := GetEngine(ctx).Preload("Customer").Where("id = ?", id).First(&project)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, result.Error
}
return &project, nil
}
// GetAllProjects returns all projects
func GetAllProjects(ctx context.Context) ([]Project, error) {
var projects []Project
result := GetEngine(ctx).Find(&projects)
if result.Error != nil {
return nil, result.Error
}
return projects, nil
}
// GetAllProjectsWithCustomers returns all projects with customer information
func GetAllProjectsWithCustomers(ctx context.Context) ([]Project, error) {
var projects []Project
result := GetEngine(ctx).Preload("Customer").Find(&projects)
if result.Error != nil {
return nil, result.Error
}
return projects, nil
}
// GetProjectsByCustomerID returns all projects of a specific customer
func GetProjectsByCustomerID(ctx context.Context, customerID ulid.ULID) ([]Project, error) {
var projects []Project
result := GetEngine(ctx).Where("customer_id = ?", customerID).Find(&projects)
if result.Error != nil {
return nil, result.Error
}
return projects, nil
}
// CreateProject creates a new project with validation
func CreateProject(ctx context.Context, create ProjectCreate) (*Project, error) {
// Validation
if err := create.Validate(); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
// Check if the customer exists
if create.CustomerID == nil {
customer, err := GetCustomerByID(ctx, *create.CustomerID)
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")
}
}
project := Project{
Name: create.Name,
CustomerID: create.CustomerID,
}
result := GetEngine(ctx).Create(&project)
if result.Error != nil {
return nil, fmt.Errorf("error creating the project: %w", result.Error)
}
return &project, nil
}
// UpdateProject updates an existing project with validation
func UpdateProject(ctx context.Context, update ProjectUpdate) (*Project, error) {
// Validation
if err := update.Validate(); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
project, err := GetProjectByID(ctx, update.ID)
if err != nil {
return nil, err
}
if project == nil {
return nil, errors.New("project not found")
}
// If CustomerID is updated, check if the customer exists
if update.CustomerID != nil {
customer, err := GetCustomerByID(ctx, *update.CustomerID)
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")
}
}
// Use generic update function
if err := UpdateModel(ctx, project, update); err != nil {
return nil, fmt.Errorf("error updating the project: %w", err)
}
// Load updated data from the database
return GetProjectByID(ctx, update.ID)
}
// DeleteProject deletes a project by its ID
func DeleteProject(ctx context.Context, id ulid.ULID) error {
// Here you could check if dependent entities exist
result := GetEngine(ctx).Delete(&Project{}, id)
if result.Error != nil {
return fmt.Errorf("error deleting the project: %w", result.Error)
}
return nil
}
// CreateProjectWithTransaction creates a project within a transaction
func CreateProjectWithTransaction(ctx context.Context, create ProjectCreate) (*Project, error) {
// Validation
if err := create.Validate(); err != nil {
return nil, fmt.Errorf("validation error: %w", err)
}
var project *Project
// Start transaction
err := GetEngine(ctx).Transaction(func(tx *gorm.DB) error {
// Customer check within the transaction
var customer Customer
if err := tx.Where("id = ?", create.CustomerID).First(&customer).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("the specified customer does not exist")
}
return err
}
// Create project
newProject := Project{
Name: create.Name,
CustomerID: create.CustomerID,
}
if err := tx.Create(&newProject).Error; err != nil {
return err
}
// Save project for return
project = &newProject
return nil
})
if err != nil {
return nil, fmt.Errorf("transaction error: %w", err)
}
return project, nil
}