package model import ( "context" "fmt" "forge.cadoles.com/Cadoles/guesstimate/internal/orm" "github.com/jinzhu/gorm" "github.com/pkg/errors" ) type ProjectRepository struct { db *gorm.DB } func (r *ProjectRepository) Create(ctx context.Context, title string, ownerID int64) (*Project, error) { project := &Project{ Title: &title, Tasks: make([]*Task, 0), TaskCategories: NewDefaultTaskCategories(), Params: NewDefaultProjectParams(), } err := orm.WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error { if err := tx.Save(project).Error; err != nil { return errors.WithStack(err) } err := tx.Model(project).Association("ACL").Append(&Access{ Level: LevelOwner, UserID: ownerID, }).Error if err != nil { return errors.WithStack(err) } err = tx.Model(project). Preload("ACL"). Preload("ACL.User"). Preload("Tasks"). Preload("Tasks.Category"). Preload("TaskCategories"). Find(project). Error if err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.Wrap(err, "could not create user") } return project, nil } func (r *ProjectRepository) UpdateTitle(ctx context.Context, projectID int64, title string) (*Project, error) { project := &Project{} project.ID = projectID err := r.db.Transaction(func(tx *gorm.DB) error { if err := tx.Model(project).Update("title", title).Error; err != nil { return errors.WithStack(err) } err := tx.Model(project). Preload("ACL"). Preload("ACL.User"). Preload("Tasks"). Preload("Tasks.Category"). Preload("TaskCategories"). First(project, "id = ?", projectID). Error if err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.WithStack(err) } return project, nil } func (r *ProjectRepository) Search(ctx context.Context, filter *ProjectsFilter) ([]*Project, error) { projects := make([]*Project, 0) projectTableName := r.db.NewScope(&Project{}).TableName() query := r.db.Table(projectTableName). Preload("ACL"). Preload("ACL.User"). Preload("Tasks"). Preload("Tasks.Category"). Preload("TaskCategories") if filter != nil { if len(filter.Ids) > 0 { query = query.Where(fmt.Sprintf("%s.id in (?)", projectTableName), filter.Ids) } if len(filter.OwnerIds) > 0 { accessTableName := r.db.NewScope(&Access{}).TableName() query = query. Joins( fmt.Sprintf( "left join %s on %s.project_id = %s.id", accessTableName, accessTableName, projectTableName, ), ). Where(fmt.Sprintf("%s.level = ?", accessTableName), LevelOwner). Where(fmt.Sprintf("%s.user_id IN (?)", accessTableName), filter.OwnerIds) } if filter.Limit != nil { query = query.Limit(*filter.Limit) } if filter.Offset != nil { query = query.Offset(*filter.Offset) } } if err := query.Find(&projects).Error; err != nil { return nil, errors.WithStack(err) } return projects, nil } func (r *ProjectRepository) AddTask(ctx context.Context, projectID int64, changes ProjectTaskChanges) (*Task, error) { project := &Project{} project.ID = projectID task := &Task{} err := r.db.Transaction(func(tx *gorm.DB) error { if err := updateTaskWithChanges(tx, task, changes); err != nil { return errors.WithStack(err) } if err := tx.Save(task).Error; err != nil { return errors.Wrap(err, "could not create task") } err := tx.Model(project).Association("Tasks").Append(task).Error if err != nil { return errors.Wrap(err, "could not add task") } return nil }) if err != nil { return nil, errors.Wrap(err, "could not add task") } return task, nil } func (r *ProjectRepository) RemoveTask(ctx context.Context, projectID int64, taskID int64) error { project := &Project{} project.ID = projectID err := r.db.Transaction(func(tx *gorm.DB) error { task := &Task{} task.ID = taskID err := tx.Model(project).Association("Tasks").Delete(task).Error if err != nil { return errors.Wrap(err, "could not remove task relationship") } err = tx.Delete(task, "id = ? AND project_id = ?", taskID, projectID).Error if err != nil { return errors.Wrap(err, "could not delete task") } return nil }) if err != nil { return errors.Wrap(err, "could not remove task") } return nil } func (r *ProjectRepository) UpdateTask(ctx context.Context, projectID, taskID int64, changes ProjectTaskChanges) (*Task, error) { task := &Task{} err := r.db.Transaction(func(tx *gorm.DB) error { err := tx.Model(task). Preload("Category"). First(task, "id = ? AND project_id = ?", taskID, projectID). Error if err != nil { return errors.WithStack(err) } if err := updateTaskWithChanges(tx, task, changes); err != nil { return errors.WithStack(err) } if err := tx.Save(task).Error; err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.Wrap(err, "could not update task") } return task, nil } func (r *ProjectRepository) UpdateParams(ctx context.Context, projectID int64, changes ProjectParamsChanges) (*Project, error) { project := &Project{} project.ID = projectID err := r.db.Transaction(func(tx *gorm.DB) error { err := tx.Model(project). Preload("ACL"). Preload("ACL.User"). Preload("Tasks"). Preload("Tasks.Category"). Preload("TaskCategories"). First(project, "id = ?", projectID). Error if err != nil { return errors.WithStack(err) } if project.Params == nil { project.Params = &ProjectParams{} } if changes.Currency != nil { project.Params.Currency = *changes.Currency } if changes.HideFinancialPreviewOnPrint != nil { project.Params.HideFinancialPreviewOnPrint = *changes.HideFinancialPreviewOnPrint } if changes.RoundUpEstimations != nil { project.Params.RoundUpEstimations = *changes.RoundUpEstimations } if changes.TimeUnit != nil { if project.Params.TimeUnit == nil { project.Params.TimeUnit = &TimeUnit{} } if changes.TimeUnit.Acronym != nil { project.Params.TimeUnit.Acronym = *changes.TimeUnit.Acronym } if changes.TimeUnit.Label != nil { project.Params.TimeUnit.Label = *changes.TimeUnit.Label } } if err := tx.Save(project).Error; err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.Wrap(err, "could not update project params") } return project, nil } func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error { if changes.Label != nil { task.Label = changes.Label } if changes.CategoryID != nil { taskCategory := &TaskCategory{} taskCategory.ID = *changes.CategoryID task.Category = taskCategory } if changes.Estimations == nil { return nil } if task.Estimations == nil { task.Estimations = &Estimations{} } if changes.Estimations.Pessimistic != nil { task.Estimations.Pessimistic = *changes.Estimations.Pessimistic } if changes.Estimations.Likely != nil { task.Estimations.Likely = *changes.Estimations.Likely } if changes.Estimations.Optimistic != nil { task.Estimations.Optimistic = *changes.Estimations.Optimistic } if task.Estimations.Likely < task.Estimations.Optimistic { task.Estimations.Likely = task.Estimations.Optimistic } if task.Estimations.Pessimistic < task.Estimations.Likely { task.Estimations.Pessimistic = task.Estimations.Likely } if changes.CategoryID != nil { taskCategory := &TaskCategory{} if err := db.Find(taskCategory, "id = ?", *changes.CategoryID).Error; err != nil { return errors.Wrap(err, "could not find task category") } task.Category = taskCategory } return nil } func NewProjectRepository(db *gorm.DB) *ProjectRepository { return &ProjectRepository{db} }