package models import ( "context" "errors" "fmt" "github.com/oklog/ulid/v2" "gorm.io/gorm" ) // Project represents a project in the system type Project struct { EntityBase Name string `gorm:"column:name;not null"` CustomerID ulid.ULID `gorm:"column:customer_id;type:uuid;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 ulid.ULID } // ProjectUpdate contains the updatable fields of a project type ProjectUpdate struct { ID ulid.ULID `gorm:"-"` // Exclude from updates Name *string `gorm:"column:name"` CustomerID *ulid.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(ulid.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 ulid.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 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 }