package mysqldriver import ( "context" "database/sql" "errors" "fmt" "git.aiterp.net/stufflog/server/internal/slerrors" "git.aiterp.net/stufflog/server/models" sq "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" "time" ) var counterKindIssueID = "NextIssueID" var counterKindIssueSubID = "NextIssueSubID" type issueRepository struct { db *sqlx.DB } func (r *issueRepository) Find(ctx context.Context, id string) (*models.Issue, error) { issue := models.Issue{} err := r.db.GetContext(ctx, &issue, "SELECT * FROM issue WHERE issue_id=?", id) if err != nil { if err == sql.ErrNoRows { return nil, slerrors.NotFound("Issue") } return nil, err } return &issue, nil } func (r *issueRepository) List(ctx context.Context, filter models.IssueFilter) ([]*models.Issue, error) { q := sq.Select("*").From("issue").OrderBy("updated_time DESC") if len(filter.IssueIDs) > 0 { q = q.Where(sq.Eq{"issue_id": filter.IssueIDs}) } if len(filter.ProjectIDs) > 0 { q = q.Where(sq.Eq{"project_id": filter.ProjectIDs}) } if len(filter.OwnerIDs) > 0 { q = q.Where(sq.Eq{"owner_id": filter.OwnerIDs}) } if len(filter.AssigneeIDs) > 0 { q = q.Where(sq.Eq{"assignee_id": filter.AssigneeIDs}) } if filter.Search != nil && *filter.Search != "" { q = q.Where("MATCH (name, title, description) AGAINST (?)", *filter.Search) } if filter.MinStage != nil { q = q.Where(sq.GtOrEq{"status_stage": *filter.MinStage}) } if filter.MaxStage != nil { q = q.Where(sq.LtOrEq{"status_stage": *filter.MaxStage}) } if filter.Limit != nil && *filter.Limit > 0 { q = q.Limit(uint64(*filter.Limit)) } query, args, err := q.ToSql() if err != nil { return nil, err } results := make([]*models.Issue, 0, 16) err = r.db.SelectContext(ctx, &results, query, args...) if err != nil { if err == sql.ErrNoRows { return []*models.Issue{}, nil } return nil, err } return results, nil } func (r *issueRepository) Insert(ctx context.Context, issue models.Issue) (*models.Issue, error) { if issue.ProjectID == "" { return nil, errors.New("missing project id") } if issue.CreatedTime.IsZero() { issue.CreatedTime = time.Now().Truncate(time.Second) issue.UpdatedTime = issue.CreatedTime } tx, err := r.db.BeginTxx(ctx, nil) if err != nil { return nil, err } nextID, err := incCounter(ctx, tx, counterKindIssueID, issue.ProjectID) if err != nil { _ = tx.Rollback() return nil, err } issue.ID = fmt.Sprintf("%s-%d", issue.ProjectID, nextID) _, err = tx.NamedExecContext(ctx, ` INSERT INTO issue ( issue_id, project_id, owner_id, assignee_id, status_stage, status_name, created_time, updated_time, due_time, name, title, description ) VALUES ( :issue_id, :project_id, :owner_id, :assignee_id, :status_stage, :status_name, :created_time, :updated_time, :due_time, :name, :title, :description ) `, issue) if err != nil { _ = tx.Rollback() return nil, err } err = tx.Commit() if err != nil { _ = tx.Rollback() return nil, err } return &issue, nil } func (r *issueRepository) Save(ctx context.Context, issue models.Issue) error { _, err := r.db.NamedExecContext(ctx, ` UPDATE issue SET assignee_id=:assignee_id, status_stage=:status_stage, status_name=:status_name, created_time=:created_time, updated_time=:updated_time, due_time=:due_time, name=:name, title=:title, description=:description WHERE issue_id=:issue_id `, issue) if err != nil { return err } return nil } func (r *issueRepository) Delete(ctx context.Context, issue models.Issue) error { tx, err := r.db.BeginTxx(ctx, nil) if err != nil { return err } _, err = tx.ExecContext(ctx, "DELETE FROM issue WHERE issue_id=?", issue.ID) if err != nil { _ = tx.Rollback() return err } // TODO: delete from issue_* err = tx.Commit() if err != nil { _ = tx.Rollback() return err } return nil }