-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement pending subscriptions for one-click private repo setup #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis PR introduces a pending subscriptions feature to defer GitHub repository subscription activation when the required GitHub App installation is unavailable. The changes add a new database table Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–25 minutes
Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (4)
🧰 Additional context used🧠 Learnings (1)📚 Learning: 2025-11-18T23:35:49.436ZApplied to files:
🧬 Code graph analysis (1)src/services/subscription-service.ts (3)
🔇 Additional comments (9)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/constants.ts (1)
41-51: Consider more frequent cleanup interval.The cleanup interval matches the expiration time (both 1 hour), which means expired pending subscriptions could persist for up to 2 hours before removal. For comparison,
PENDING_MESSAGE_CLEANUP_INTERVAL_MSruns twice as frequently (30s) asPENDING_MESSAGE_MAX_AGE_MS(60s).Consider setting
PENDING_SUBSCRIPTION_CLEANUP_INTERVAL_MSto 30 minutes (30 * 60 * 1000) to clean up expired entries more promptly./** * Pending subscription cleanup interval (1 hour) * How often to check for and remove expired pending subscriptions */ -export const PENDING_SUBSCRIPTION_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; +export const PENDING_SUBSCRIPTION_CLEANUP_INTERVAL_MS = 30 * 60 * 1000; // 30 minutessrc/services/subscription-service.ts (2)
92-96: Interval-based cleanup is fine; consider lifecycle management if multiple instances existThe additional
setIntervalforcleanupExpiredPendingSubscriptionsis reasonable, but ifSubscriptionServiceis ever instantiated more than once per process, each instance will schedule its own cleanup loop hitting the DB. If this service might be non-singleton, consider centralizing interval scheduling or keeping the timer handle on the instance with adispose()to clear it when no longer needed.
160-167: Pending subscription storage matches the flow; be aware of re-subscribe/unsubscribe edge casesStoring a single pending row per
(channelId, repoFullName)via.onConflictDoNothing()is a good way to dedupe repeated/subscribecalls while the app isn’t installed. One behavioral edge case to be aware of: if a user changes their mind (e.g., runs/github unsubscribeor re-subscribes with differenteventTypes) before installation completes, the existing pending record will still be used and auto-subscribed with the originaleventTypes. If that’s undesirable, you may want to (in a follow-up) either:
- Clear any pending row from
pendingSubscriptionswhen handlingunsubscribe, and/or- Allow updating
eventTypeson an existing pending row instead of ignoring later requests.Not blocking, but worth confirming this matches intended UX.
Also applies to: 207-214
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
drizzle/0003_wooden_romulus.sql(1 hunks)drizzle/0004_sticky_black_tarantula.sql(1 hunks)drizzle/meta/0003_snapshot.json(1 hunks)drizzle/meta/0004_snapshot.json(1 hunks)drizzle/meta/_journal.json(1 hunks)src/constants.ts(1 hunks)src/db/schema.ts(1 hunks)src/github-app/installation-service.ts(2 hunks)src/services/subscription-service.ts(7 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: shuhuiluo
Repo: HereNotThere/bot-github PR: 26
File: src/db/index.ts:61-69
Timestamp: 2025-11-18T23:35:49.436Z
Learning: In `webhook_deliveries` table (src/db/index.ts), the `installation_id` column should NOT have a FOREIGN KEY constraint because the table serves as an immutable audit log for idempotency tracking. Records must persist independently even after installations are deleted, and a foreign key would create race conditions when webhooks arrive before installation records are created. The field is intentionally nullable to support webhooks without installation context.
📚 Learning: 2025-11-18T23:35:49.436Z
Learnt from: shuhuiluo
Repo: HereNotThere/bot-github PR: 26
File: src/db/index.ts:61-69
Timestamp: 2025-11-18T23:35:49.436Z
Learning: In `webhook_deliveries` table (src/db/index.ts), the `installation_id` column should NOT have a FOREIGN KEY constraint because the table serves as an immutable audit log for idempotency tracking. Records must persist independently even after installations are deleted, and a foreign key would create race conditions when webhooks arrive before installation records are created. The field is intentionally nullable to support webhooks without installation context.
Applied to files:
src/db/schema.ts
🧬 Code graph analysis (1)
src/services/subscription-service.ts (3)
src/constants.ts (2)
PENDING_SUBSCRIPTION_CLEANUP_INTERVAL_MS(51-51)PENDING_SUBSCRIPTION_EXPIRATION_MS(45-45)src/db/index.ts (1)
db(57-57)src/db/schema.ts (1)
pendingSubscriptions(200-227)
🔇 Additional comments (5)
src/github-app/installation-service.ts (1)
272-292: LGTM! Clean integration of pending subscription completion.The flow correctly completes pending subscriptions after upgrading existing subscriptions to webhook mode. Error handling and logging are consistent with existing patterns.
drizzle/0003_wooden_romulus.sql (1)
1-15: LGTM! Well-structured migration with appropriate constraints.The table structure, foreign key cascade behavior, and indexes are well-designed:
- Cascade delete on
towns_user_idis correct since pending subscriptions are tied to user lifecycle- Indexes support the expected query patterns (cleanup by expiration, lookup by user/repo)
src/db/schema.ts (1)
195-227: LGTM! Schema definition correctly matches SQL migrations.The Drizzle schema accurately reflects the database structure created by migrations 0003 and 0004, with appropriate foreign key cascade behavior and indexes.
src/services/subscription-service.ts (2)
288-321: Pending subscription persistence looks correct and idempotent
storePendingSubscriptioncorrectly:
- Derives
expiresAtfromPENDING_SUBSCRIPTION_EXPIRATION_MS.- Inserts a single row tied to
townsUserId(leveraging the FK for referential integrity).- Uses
.onConflictDoNothing()so concurrent or repeated/subscribecalls for the same(channelId, repoFullName)don’t create duplicates.No changes needed here.
716-734: Expiry cleanup logic is sound; uses DB time and expiry indexThe
cleanupExpiredPendingSubscriptionsimplementation looks good:
- It relies on
NOW()in the database, which avoids skew with the app server clock.- It leverages the
expiresAtcolumn (with an index in the schema) as the filter.- Errors are caught and logged so the interval doesn’t crash on exception.
No functional changes required here.
Enables automatic subscription completion after GitHub App installation, eliminating the need for users to run the subscribe command twice. When a user tries to subscribe to a private repo without GitHub App installed: 1. Subscription is stored as pending in the database 2. User is prompted to install the GitHub App 3. Installation webhook automatically completes the pending subscription 4. Success message is sent to Towns channel Features: - Add pendingSubscriptions table with unique constraint on (space_id, channel_id, repo_full_name) - Store pending subscription when private repo requires installation - Auto-complete pending subscriptions on installation webhook - Add periodic cleanup task for expired pending subscriptions (1 hour TTL) - Handle duplicate pending subscriptions atomically with onConflictDoNothing - Update InstallationService to trigger pending subscription completion Database migration: 0003_wet_beyonder.sql 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
3acc851 to
99bca45
Compare
Enables automatic subscription completion after GitHub App installation, eliminating the need for users to run the subscribe command twice.
When a user tries to subscribe to a private repo without GitHub App installed:
Features:
Database migrations:
🤖 Generated with Claude Code
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.