Repository navigation
Conversation
…hook deliveries, AudioBitsStellar#826 redis scan, AudioBitsStellar#834 jest open handles
|
@peacebas Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #814
Closes #824
Closes #826
Closes #834
1. Route Song & Artist Stats Endpoints
SongStatsServiceprovided comprehensive aggregation queries poweringSongController.getSongStatsandSongController.getArtistStats, but neither handler was routed. Artist dashboard analytics had no endpoints to query.GET /api/song/:id/statswith optional authentication.GET /api/song/artist/:artistId/statsandGET /api/artist/:id/statsfor artist catalog analytics.playsandsaves). Confidential financial metrics (revenueStroops, denominated in atomic Stellar stroops representinguniqueListeners) are strictly restricted to the verified song/artist owner, platform admins, and moderators.24h,7d,30d,90d,all-time) returning structured 400 Bad Request (INVALID_WINDOW) errors for unsupported values.Cache-Control: private, max-age=300for creators andCache-Control: public, max-age=300for public metrics, combined withVary: Authorizationto prevent cache poisoning across intermediate proxies.2. Record Webhook Delivery Attempts & Expose Redelivery Endpoint
WebhookDeliveryentity recordingsubscriptionId,eventId,eventType,attempt,status (pending|succeeded|failed),responseStatus,durationMs,error, andpayloadwith database indexes onsubscriptionId,createdAt, andeventId.consecutiveFailurescounter toWebhookSubscriptionand created database migration1757700000000-AddWebhookDeliveriesAndFailureTracking.ts.WebhookService.deliver(), each HTTP attempt writes an audit row. Successful deliveries resetconsecutiveFailures = 0.consecutiveFailuresincrements. If consecutive failures reach the configurable threshold (WEBHOOK_MAX_CONSECUTIVE_FAILURES, default 5), the subscription is automatically disabled (isActive = false) and an alert notification is dispatched to the owner viaNotificationService.GET /api/webhooks/:id/deliveries(paginated, newest first).POST /api/webhooks/:id/deliveries/:deliveryId/redeliver(triggers retry using original signed payload).cleanupOldDeliveries(30)) and standalone jobsrc/jobs/WebhookRetentionJob.ts.3. Use SCAN Instead of KEYS in CacheService.invalidatePattern
CacheService.invalidatePatternexecutedredis.keys(pattern).KEYSis a blockingAccountDeletionServiceinvokedCacheService.invalidatePattern('song:*'), scanning and dropping all song cache keys across the entire platform whenever any single user deleted their account.CacheService.invalidatePatternusing non-blocking cursor iteration viaredis.scanStream({ match: pattern, count: 500 }).redis.unlink(...keys)in chunks of 500.UNLINKremoves keys from the keyspace inAccountDeletionServiceto selectively query the deleting artist's songs (await songRepo.find({ where: { artistId: user.id }, select: ['id'] })) and surgically clear only their keys viaCacheService.clearSong(song.id).redis.keys(remain insrc/. Added unit tests with mocked scan streams.4. Run Jest with --detectOpenHandles in CI & Fix Leaking Timers
setIntervalandsetTimeouttimers initialized at module import or constructor time inPrivySessionService,DatabaseConnectionManager,DbPoolMonitor, andJobQueueWorker, as well as background Redis socket reconnect timers, kept Node.js event loops alive. Jest test suites could not exit cleanly, leading to hanging workers or masking leaks via--forceExit.PrivySessionService; introduced explicitstart(),stop(), and.unref()handling.stopConnectionStateLogger()and made circuit breaker health check intervals opt-in (autoStart = false) inDatabaseConnectionManager.src/config/redis.ts(lazyConnect: true,retryStrategy: () => null) to prevent background socket connection loops during tests.tests/setup/jestTeardown.tsto stop remaining background timers and disconnect Redis sockets cleanly..github/workflows/ci.ymlto executenpm test -- --detectOpenHandleswithout--forceExit.Changed
GET /api/song/:id/stats,GET /api/song/artist/:artistId/stats, andGET /api/artist/:id/statswithoptionalAuthinsrc/routes/SongRoutes.tsandsrc/routes/artistRoutes.ts.src/controllers/SongController.ts, strippinguniqueListenersandrevenueStroopsfor public consumers and non-owners.AppError.validation(400 Bad Request) on invalid windows.Cache-Control: private/public, max-age=300,Vary: Authorization).WebhookDeliveryentity insrc/entities/WebhookDelivery.tsand registered it insrc/config/db.ts.consecutiveFailurestracking column tosrc/entities/WebhookSubscription.tsand created database migrationsrc/migrations/1757700000000-AddWebhookDeliveriesAndFailureTracking.ts.src/services/WebhookService.tsto log per-attempt deliveries, auto-disable subscriptions exceeding consecutive failure thresholds, and notify owners viaNotificationService.GET /api/webhooks/:id/deliveriesandPOST /api/webhooks/:id/deliveries/:deliveryId/redeliverinsrc/controllers/WebhookController.tsandsrc/routes/webhookRoutes.ts.cleanupOldDeliveries(days = 30)inWebhookServiceand createdsrc/jobs/WebhookRetentionJob.tsfor automated pruning.redis.keys()insrc/services/CacheService.tswithredis.scanStream({ match, count: 500 })and batchredis.unlink().src/services/AccountDeletionService.tsto clear only the specific deleted user's songs instead of globalsong:*.PrivySessionService,DatabaseConnectionManager,DbPoolMonitor, andJobQueueWorkerto explicitstart()/stop()lifecycles with.unref().lazyConnect: trueandretryStrategy: () => nullin test mode forsrc/config/redis.ts.tests/setup/jestTeardown.tsand configuredjest.config.jswithsetupFilesAfterEnv..github/workflows/ci.ymltest command to runnpm test -- --detectOpenHandleswithout--forceExit.docs/openapi.yamlwith OpenAPI 3.0 path and component schemas.src/__tests__/SongArtistStatsRoutes.test.ts,src/__tests__/WebhookDeliveriesRoutes.test.ts, andsrc/__tests__/CacheService.test.ts.