internal/store/diffcomments.go

3af6fd1aaf69413d258a4131a627d048d2f1d290
gitbay/internal/store/diffcomments.go history · blame · raw

130 lines · 3834 bytes

  1package store
  2
  3import (
  4	"database/sql"
  5	"errors"
  6	"fmt"
  7)
  8
  9type DiffComment struct {
 10	ID         int64
 11	Author     string
 12	HeadSHA    string
 13	Path       string
 14	Side       string
 15	Line       int64
 16	Body       string
 17	ReplyTo    int64 // 0 for thread roots
 18	ResolvedBy string
 19	CreatedAt  string
 20}
 21
 22// AddDiffComment creates a thread root (replyTo 0) or a reply. Replies
 23// inherit the root's anchor and must belong to the same MR.
 24func (s *Store) AddDiffComment(mrID, authorID int64, headSHA, path, side string, line int64, body string, replyTo int64) (int64, error) {
 25	if replyTo != 0 {
 26		var rootMR int64
 27		var rootReply sql.NullInt64
 28		err := s.DB.QueryRow(
 29			"SELECT mr_id, reply_to FROM mr_diff_comments WHERE id = ?", replyTo).Scan(&rootMR, &rootReply)
 30		if errors.Is(err, sql.ErrNoRows) {
 31			return 0, fmt.Errorf("no thread %d: %w", replyTo, ErrNotFound)
 32		}
 33		if err != nil {
 34			return 0, err
 35		}
 36		if rootMR != mrID {
 37			return 0, fmt.Errorf("thread %d belongs to a different merge request", replyTo)
 38		}
 39		if rootReply.Valid {
 40			return 0, fmt.Errorf("reply to the thread root %d, not to a reply", rootReply.Int64)
 41		}
 42		err = s.DB.QueryRow(
 43			"SELECT head_sha, path, side, line FROM mr_diff_comments WHERE id = ?", replyTo).
 44			Scan(&headSHA, &path, &side, &line)
 45		if err != nil {
 46			return 0, err
 47		}
 48	}
 49	var reply any
 50	if replyTo != 0 {
 51		reply = replyTo
 52	}
 53	res, err := s.DB.Exec(`
 54		INSERT INTO mr_diff_comments (mr_id, author_id, head_sha, path, side, line, body, reply_to)
 55		VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
 56		mrID, authorID, headSHA, path, side, line, body, reply)
 57	if err != nil {
 58		return 0, err
 59	}
 60	return res.LastInsertId()
 61}
 62
 63// ListDiffComments returns every diff comment on an MR, roots and replies,
 64// oldest first.
 65func (s *Store) ListDiffComments(mrID int64) ([]DiffComment, error) {
 66	rows, err := s.DB.Query(`
 67		SELECT c.id, u.username, c.head_sha, c.path, c.side, c.line, c.body,
 68		       COALESCE(c.reply_to, 0), COALESCE(r.username, ''), c.created_at
 69		FROM mr_diff_comments c
 70		JOIN users u ON u.id = c.author_id
 71		LEFT JOIN users r ON r.id = c.resolved_by
 72		WHERE c.mr_id = ? ORDER BY c.id`, mrID)
 73	if err != nil {
 74		return nil, err
 75	}
 76	defer rows.Close()
 77	var out []DiffComment
 78	for rows.Next() {
 79		var c DiffComment
 80		if err := rows.Scan(&c.ID, &c.Author, &c.HeadSHA, &c.Path, &c.Side, &c.Line, &c.Body,
 81			&c.ReplyTo, &c.ResolvedBy, &c.CreatedAt); err != nil {
 82			return nil, err
 83		}
 84		out = append(out, c)
 85	}
 86	return out, rows.Err()
 87}
 88
 89// SetThreadResolved resolves or unresolves a thread root.
 90func (s *Store) SetThreadResolved(mrID, rootID, byUser int64, resolved bool) error {
 91	var q string
 92	var args []any
 93	if resolved {
 94		q = `UPDATE mr_diff_comments SET resolved_at = strftime('%Y-%m-%dT%H:%M:%fZ','now'), resolved_by = ?
 95		     WHERE id = ? AND mr_id = ? AND reply_to IS NULL`
 96		args = []any{byUser, rootID, mrID}
 97	} else {
 98		q = `UPDATE mr_diff_comments SET resolved_at = NULL, resolved_by = NULL
 99		     WHERE id = ? AND mr_id = ? AND reply_to IS NULL`
100		args = []any{rootID, mrID}
101	}
102	res, err := s.DB.Exec(q, args...)
103	if err != nil {
104		return err
105	}
106	if n, _ := res.RowsAffected(); n == 0 {
107		return ErrNotFound
108	}
109	return nil
110}
111
112// DiffCommentAuthor returns the author id of one comment.
113func (s *Store) DiffCommentAuthor(mrID, id int64) (int64, error) {
114	var author int64
115	err := s.DB.QueryRow(
116		"SELECT author_id FROM mr_diff_comments WHERE id = ? AND mr_id = ?", id, mrID).Scan(&author)
117	if errors.Is(err, sql.ErrNoRows) {
118		return 0, ErrNotFound
119	}
120	return author, err
121}
122
123// UnresolvedThreadCount counts unresolved thread roots on an MR.
124func (s *Store) UnresolvedThreadCount(mrID int64) (int, error) {
125	var n int
126	err := s.DB.QueryRow(
127		"SELECT COUNT(*) FROM mr_diff_comments WHERE mr_id = ? AND reply_to IS NULL AND resolved_at IS NULL",
128		mrID).Scan(&n)
129	return n, err
130}