internal/store/milestones.go

babb6e8da233aba1c5246ad4ec56802c2f7553bd
gitbay/internal/store/milestones.go history · blame · raw

115 lines · 3284 bytes

  1package store
  2
  3import (
  4	"database/sql"
  5	"errors"
  6	"fmt"
  7)
  8
  9type Milestone struct {
 10	ID          int64
 11	RepoID      int64
 12	Title       string
 13	Description string
 14	DueDate     string
 15	State       string // open | closed
 16	CreatedAt   string
 17	OpenItems   int // open issues + open MRs attached
 18	ClosedItems int // closed issues + merged/closed MRs attached
 19}
 20
 21func (s *Store) CreateMilestone(repoID int64, title, description, due string) (int64, error) {
 22	res, err := s.DB.Exec(
 23		"INSERT INTO milestones (repo_id, title, description, due_date) VALUES (?, ?, ?, ?)",
 24		repoID, title, description, due)
 25	if err != nil {
 26		if isUniqueErr(err) {
 27			return 0, fmt.Errorf("milestone %q already exists", title)
 28		}
 29		return 0, err
 30	}
 31	return res.LastInsertId()
 32}
 33
 34const milestoneSelect = `
 35	SELECT m.id, m.repo_id, m.title, m.description, m.due_date, m.state, m.created_at,
 36	       (SELECT COUNT(*) FROM issues i WHERE i.milestone_id = m.id AND i.state = 'open')
 37	     + (SELECT COUNT(*) FROM merge_requests r WHERE r.milestone_id = m.id AND r.state IN ('open','source_gone')),
 38	       (SELECT COUNT(*) FROM issues i WHERE i.milestone_id = m.id AND i.state = 'closed')
 39	     + (SELECT COUNT(*) FROM merge_requests r WHERE r.milestone_id = m.id AND r.state IN ('merged','closed'))
 40	FROM milestones m`
 41
 42func scanMilestone(row interface{ Scan(...any) error }) (Milestone, error) {
 43	var m Milestone
 44	err := row.Scan(&m.ID, &m.RepoID, &m.Title, &m.Description, &m.DueDate, &m.State,
 45		&m.CreatedAt, &m.OpenItems, &m.ClosedItems)
 46	return m, err
 47}
 48
 49func (s *Store) MilestoneByTitle(repoID int64, title string) (Milestone, error) {
 50	m, err := scanMilestone(s.DB.QueryRow(
 51		milestoneSelect+" WHERE m.repo_id = ? AND m.title = ?", repoID, title))
 52	if errors.Is(err, sql.ErrNoRows) {
 53		return m, ErrNotFound
 54	}
 55	return m, err
 56}
 57
 58func (s *Store) ListMilestones(repoID int64, state string) ([]Milestone, error) {
 59	q := milestoneSelect + " WHERE m.repo_id = ?"
 60	args := []any{repoID}
 61	if state != "all" {
 62		q += " AND m.state = ?"
 63		args = append(args, state)
 64	}
 65	q += " ORDER BY m.due_date = '', m.due_date, m.title"
 66	rows, err := s.DB.Query(q, args...)
 67	if err != nil {
 68		return nil, err
 69	}
 70	defer rows.Close()
 71	var out []Milestone
 72	for rows.Next() {
 73		m, err := scanMilestone(rows)
 74		if err != nil {
 75			return nil, err
 76		}
 77		out = append(out, m)
 78	}
 79	return out, rows.Err()
 80}
 81
 82func (s *Store) SetMilestoneState(id int64, state string) error {
 83	res, err := s.DB.Exec("UPDATE milestones SET state = ? WHERE id = ?", state, id)
 84	if err != nil {
 85		return err
 86	}
 87	if n, _ := res.RowsAffected(); n == 0 {
 88		return ErrNotFound
 89	}
 90	return nil
 91}
 92
 93// SetIssueMilestone attaches (or with milestoneID 0 clears) a milestone.
 94func (s *Store) SetIssueMilestone(issueID, milestoneID int64) error {
 95	return s.setItemMilestone("issues", issueID, milestoneID)
 96}
 97
 98func (s *Store) SetMRMilestone(mrID, milestoneID int64) error {
 99	return s.setItemMilestone("merge_requests", mrID, milestoneID)
100}
101
102func (s *Store) setItemMilestone(table string, id, milestoneID int64) error {
103	var v any
104	if milestoneID != 0 {
105		v = milestoneID
106	}
107	res, err := s.DB.Exec("UPDATE "+table+" SET milestone_id = ? WHERE id = ?", v, id)
108	if err != nil {
109		return err
110	}
111	if n, _ := res.RowsAffected(); n == 0 {
112		return ErrNotFound
113	}
114	return nil
115}