Files
kandev/apps/backend/internal/gitlab/store_watch_reset.go
T
2026-09-05 15:30:20 +00:00

185 lines
6.6 KiB
Go

package gitlab
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
)
type watchInvalidation struct {
Generation int64
TaskIDs []string
Missing bool
}
func (s *Store) BeginReviewWatchReset(ctx context.Context, watchID string) (*watchInvalidation, error) {
return s.beginWatchInvalidation(ctx, "gitlab_review_watches", "gitlab_review_mr_tasks", "review_watch_id", watchID, false)
}
func (s *Store) BeginIssueWatchReset(ctx context.Context, watchID string) (*watchInvalidation, error) {
return s.beginWatchInvalidation(ctx, "gitlab_issue_watches", "gitlab_issue_watch_tasks", "issue_watch_id", watchID, false)
}
func (s *Store) BeginReviewWatchDelete(ctx context.Context, watchID string) (*watchInvalidation, error) {
return s.beginWatchInvalidation(ctx, "gitlab_review_watches", "gitlab_review_mr_tasks", "review_watch_id", watchID, true)
}
func (s *Store) BeginIssueWatchDelete(ctx context.Context, watchID string) (*watchInvalidation, error) {
return s.beginWatchInvalidation(ctx, "gitlab_issue_watches", "gitlab_issue_watch_tasks", "issue_watch_id", watchID, true)
}
func (s *Store) beginWatchInvalidation(ctx context.Context, watchTable, taskTable, foreignKey, watchID string, deleting bool) (*watchInvalidation, error) {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback() }()
set := "generation = generation + 1, updated_at = CURRENT_TIMESTAMP"
if deleting {
set += ", deleting = TRUE, enabled = FALSE"
}
result, err := tx.ExecContext(ctx, tx.Rebind(
fmt.Sprintf("UPDATE %s SET %s WHERE id = ? AND deleting = FALSE", watchTable, set)), watchID)
if err != nil {
return nil, err
}
rows, err := result.RowsAffected()
if err != nil {
return nil, err
}
if rows == 0 {
if !deleting {
return nil, ErrWatchNotFound
}
return s.resumeWatchDelete(ctx, tx, watchTable, taskTable, foreignKey, watchID)
}
var generation int64
if err := tx.GetContext(ctx, &generation, tx.Rebind(
fmt.Sprintf("SELECT generation FROM %s WHERE id = ?", watchTable)), watchID); err != nil {
return nil, err
}
var taskIDs []string
if err := tx.SelectContext(ctx, &taskIDs, tx.Rebind(
fmt.Sprintf("SELECT task_id FROM %s WHERE %s = ? AND generation < ? AND task_id <> '' ORDER BY created_at", taskTable, foreignKey)),
watchID, generation); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return &watchInvalidation{Generation: generation, TaskIDs: taskIDs}, nil
}
func (s *Store) resumeWatchDelete(ctx context.Context, tx *sqlx.Tx, watchTable, taskTable, foreignKey, watchID string) (*watchInvalidation, error) {
var state struct {
Generation int64 `db:"generation"`
Deleting bool `db:"deleting"`
}
err := tx.GetContext(ctx, &state, tx.Rebind(
fmt.Sprintf("SELECT generation, deleting FROM %s WHERE id = ?", watchTable)), watchID)
if errors.Is(err, sql.ErrNoRows) {
return &watchInvalidation{Missing: true}, nil
}
if err != nil {
return nil, err
}
if !state.Deleting {
return nil, ErrWatchOwnershipLost
}
var taskIDs []string
if err := tx.SelectContext(ctx, &taskIDs, tx.Rebind(
fmt.Sprintf("SELECT task_id FROM %s WHERE %s = ? AND generation < ? AND task_id <> '' ORDER BY created_at", taskTable, foreignKey)),
watchID, state.Generation); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return &watchInvalidation{Generation: state.Generation, TaskIDs: taskIDs}, nil
}
func (s *Store) FinishReviewWatchReset(ctx context.Context, watchID string, generation int64) error {
return s.finishWatchReset(ctx, "gitlab_review_watches", "gitlab_review_mr_tasks", "review_watch_id", watchID, generation)
}
func (s *Store) FinishIssueWatchReset(ctx context.Context, watchID string, generation int64) error {
return s.finishWatchReset(ctx, "gitlab_issue_watches", "gitlab_issue_watch_tasks", "issue_watch_id", watchID, generation)
}
func (s *Store) finishWatchReset(ctx context.Context, watchTable, taskTable, foreignKey, watchID string, generation int64) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
if _, err := tx.ExecContext(ctx, tx.Rebind(
fmt.Sprintf("DELETE FROM %s WHERE %s = ? AND generation < ?", taskTable, foreignKey)), watchID, generation); err != nil {
return err
}
result, err := tx.ExecContext(ctx, tx.Rebind(
fmt.Sprintf("UPDATE %s SET last_polled_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND generation = ? AND deleting = FALSE", watchTable)),
watchID, generation)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return ErrWatchOwnershipLost
}
return tx.Commit()
}
func (s *Store) ListReviewMRTaskIDsByWatch(ctx context.Context, watchID string) ([]string, error) {
var ids []string
if err := s.ro.SelectContext(ctx, &ids, s.ro.Rebind(
`SELECT task_id FROM gitlab_review_mr_tasks WHERE review_watch_id = ? ORDER BY created_at`), watchID); err != nil {
return nil, err
}
return ids, nil
}
func (s *Store) ResetReviewWatchState(ctx context.Context, watchID string) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
if _, err := tx.ExecContext(ctx, tx.Rebind(`DELETE FROM gitlab_review_mr_tasks WHERE review_watch_id = ?`), watchID); err != nil {
return fmt.Errorf("clear review watch tasks: %w", err)
}
if _, err := tx.ExecContext(ctx, tx.Rebind(`UPDATE gitlab_review_watches SET last_polled_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?`), watchID); err != nil {
return fmt.Errorf("clear review watch poll state: %w", err)
}
return tx.Commit()
}
func (s *Store) ListIssueWatchTaskIDsByWatch(ctx context.Context, watchID string) ([]string, error) {
var ids []string
if err := s.ro.SelectContext(ctx, &ids, s.ro.Rebind(
`SELECT task_id FROM gitlab_issue_watch_tasks WHERE issue_watch_id = ? ORDER BY created_at`), watchID); err != nil {
return nil, err
}
return ids, nil
}
func (s *Store) ResetIssueWatchState(ctx context.Context, watchID string) error {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
if _, err := tx.ExecContext(ctx, tx.Rebind(`DELETE FROM gitlab_issue_watch_tasks WHERE issue_watch_id = ?`), watchID); err != nil {
return fmt.Errorf("clear issue watch tasks: %w", err)
}
if _, err := tx.ExecContext(ctx, tx.Rebind(`UPDATE gitlab_issue_watches SET last_polled_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?`), watchID); err != nil {
return fmt.Errorf("clear issue watch poll state: %w", err)
}
return tx.Commit()
}