diff --git a/internal/store/threads.go b/internal/store/threads.go index 5339f9f..87fbaef 100644 --- a/internal/store/threads.go +++ b/internal/store/threads.go @@ -160,6 +160,28 @@ func (s *Store) CloseThreadLocally(ctx context.Context, repoID int64, number int return nil } +func (s *Store) ReopenThreadLocally(ctx context.Context, repoID int64, number int) error { + if repoID <= 0 { + return fmt.Errorf("repo id must be positive") + } + if number <= 0 { + return fmt.Errorf("thread number must be positive") + } + updatedAt := time.Now().UTC().Format(timeLayout) + result, err := s.q().ExecContext(ctx, ` + update threads + set closed_at_local = null, close_reason_local = null, updated_at = ? + where repo_id = ? and number = ? + `, updatedAt, repoID, number) + if err != nil { + return fmt.Errorf("reopen thread locally: %w", err) + } + if affected, err := result.RowsAffected(); err == nil && affected == 0 { + return fmt.Errorf("thread #%d was not found", number) + } + return nil +} + func scanThread(rows interface { Scan(dest ...any) error }) (Thread, error) { diff --git a/internal/store/threads_test.go b/internal/store/threads_test.go index 02f08ed..d9b3c3c 100644 --- a/internal/store/threads_test.go +++ b/internal/store/threads_test.go @@ -99,6 +99,17 @@ func TestCloseThreadLocallyHidesThreadAndSurvivesUpsert(t *testing.T) { if len(openRows) != 0 { t.Fatalf("upsert should preserve local close, got %#v", openRows) } + + if err := st.ReopenThreadLocally(ctx, repoID, 42); err != nil { + t.Fatalf("reopen thread locally: %v", err) + } + openRows, err = st.ListThreads(ctx, repoID, false) + if err != nil { + t.Fatalf("list reopened threads: %v", err) + } + if len(openRows) != 1 || openRows[0].ClosedAtLocal != "" || openRows[0].CloseReasonLocal != "" { + t.Fatalf("reopened thread not visible/cleared: %#v", openRows) + } } func TestCloseThreadLocallyRequiresExistingThread(t *testing.T) { @@ -113,3 +124,16 @@ func TestCloseThreadLocallyRequiresExistingThread(t *testing.T) { t.Fatal("expected missing thread error") } } + +func TestReopenThreadLocallyRequiresExistingThread(t *testing.T) { + ctx := context.Background() + st, err := Open(ctx, filepath.Join(t.TempDir(), "gitcrawl.db")) + if err != nil { + t.Fatalf("open store: %v", err) + } + defer st.Close() + + if err := st.ReopenThreadLocally(ctx, 1, 404); err == nil { + t.Fatal("expected missing thread error") + } +}