@@ -237,6 +237,40 @@ func (s *Store) CreateSubscription(ctx context.Context, sub Subscription) (strin
237237 return id , inserted , err
238238}
239239
240+ // FindSubscriptionByConfig returns the existing row that CreateSubscription
241+ // would reactivate. Callers use it to restore the prior status if webhook
242+ // synchronization fails after the upsert.
243+ func (s * Store ) FindSubscriptionByConfig (ctx context.Context , sub Subscription ) (Subscription , error ) {
244+ var existing Subscription
245+ err := s .pool .QueryRow (ctx , `
246+ SELECT s.id::text, s.telegram_user_id, s.destination_type, s.destination_chat_id,
247+ s.github_repo_id, s.repo_full_name, s.events, s.branch_mode, s.branch_name,
248+ s.branch_names, s.pull_request_actions, s.release_mode,
249+ s.status, s.pause_reason, r.default_branch, r.html_url
250+ FROM subscriptions s
251+ JOIN repositories r ON r.github_repo_id = s.github_repo_id
252+ WHERE s.telegram_user_id = $1
253+ AND s.destination_type = $2
254+ AND s.destination_chat_id = $3
255+ AND s.github_repo_id = $4
256+ AND s.events = $5
257+ AND s.branch_mode = $6
258+ AND s.branch_names = $7
259+ AND s.pull_request_actions = $8
260+ AND s.release_mode = $9
261+ ` , sub .TelegramUserID , sub .DestinationType , sub .DestinationChatID , sub .GitHubRepoID ,
262+ sub .Events , sub .BranchMode , sub .BranchNames , sub .PullRequestActions , sub .ReleaseMode ).Scan (
263+ & existing .ID , & existing .TelegramUserID , & existing .DestinationType , & existing .DestinationChatID ,
264+ & existing .GitHubRepoID , & existing .RepoFullName , & existing .Events , & existing .BranchMode ,
265+ & existing .BranchName , & existing .BranchNames , & existing .PullRequestActions ,
266+ & existing .ReleaseMode , & existing .Status , & existing .PauseReason , & existing .DefaultBranch , & existing .HTMLURL ,
267+ )
268+ if errors .Is (err , pgx .ErrNoRows ) {
269+ return Subscription {}, ErrNotFound
270+ }
271+ return existing , err
272+ }
273+
240274func (s * Store ) ListSubscriptionsByUser (ctx context.Context , telegramUserID int64 ) ([]Subscription , error ) {
241275 rows , err := s .pool .Query (ctx , `
242276 SELECT s.id::text, s.telegram_user_id, s.destination_type, s.destination_chat_id,
@@ -312,13 +346,13 @@ func (s *Store) ReconcileSubscriptionRepoName(ctx context.Context, repoID int64,
312346 return err
313347}
314348
315- func (s * Store ) ListActiveEventsForRepo (ctx context.Context , repoFullName string ) ([]string , error ) {
349+ func (s * Store ) ListActiveEventsForRepo (ctx context.Context , repoID int64 ) ([]string , error ) {
316350 rows , err := s .pool .Query (ctx , `
317351 SELECT DISTINCT unnest(events)
318352 FROM subscriptions
319- WHERE repo_full_name = $1 AND status = 'active'
353+ WHERE github_repo_id = $1 AND status = 'active'
320354 ORDER BY 1
321- ` , repoFullName )
355+ ` , repoID )
322356 if err != nil {
323357 return nil , err
324358 }
@@ -347,6 +381,18 @@ func (s *Store) UpdateSubscriptionStatus(ctx context.Context, telegramUserID int
347381 return requireAffected (tag , err )
348382}
349383
384+ // RestoreSubscriptionStatus is used by the subscription service's
385+ // compensating rollback. Unlike a user-driven status update it preserves the
386+ // automatic pause reason exactly as it was before the failed webhook sync.
387+ func (s * Store ) RestoreSubscriptionStatus (ctx context.Context , telegramUserID int64 , id , status , pauseReason string ) error {
388+ tag , err := s .pool .Exec (ctx , `
389+ UPDATE subscriptions
390+ SET status = $3, pause_reason = $4, updated_at = now()
391+ WHERE telegram_user_id = $1 AND id = $2
392+ ` , telegramUserID , id , status , pauseReason )
393+ return requireAffected (tag , err )
394+ }
395+
350396func (s * Store ) UpdateSubscriptionEventsAndSettings (ctx context.Context , telegramUserID int64 , id string , events []string , branchMode string , branchNames []string , pullRequestActions []string , releaseMode string ) error {
351397 tag , err := s .pool .Exec (ctx , `
352398 UPDATE subscriptions
@@ -406,6 +452,22 @@ func (s *Store) DeleteSubscription(ctx context.Context, telegramUserID int64, id
406452 return requireAffected (tag , err )
407453}
408454
455+ // RestoreSubscription reinstates a deleted row with its original id and
456+ // settings when the corresponding GitHub webhook mutation cannot be completed.
457+ func (s * Store ) RestoreSubscription (ctx context.Context , sub Subscription ) error {
458+ _ , err := s .pool .Exec (ctx , `
459+ INSERT INTO subscriptions (
460+ id, telegram_user_id, destination_type, destination_chat_id,
461+ github_repo_id, repo_full_name, events, branch_mode, branch_name,
462+ branch_names, pull_request_actions, release_mode, status, pause_reason
463+ )
464+ VALUES ($1::uuid, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
465+ ` , sub .ID , sub .TelegramUserID , sub .DestinationType , sub .DestinationChatID ,
466+ sub .GitHubRepoID , sub .RepoFullName , sub .Events , sub .BranchMode , sub .BranchName ,
467+ sub .BranchNames , sub .PullRequestActions , sub .ReleaseMode , sub .Status , sub .PauseReason )
468+ return err
469+ }
470+
409471func (s * Store ) UpsertRepoHook (ctx context.Context , repoID int64 , fullName string , hookID int64 , events []string , payloadURL string ) error {
410472 _ , err := s .pool .Exec (ctx , `
411473 INSERT INTO repo_hooks (github_repo_id, full_name, hook_id, events, payload_url)
0 commit comments