Skip to content

Title: feat: durable notification queue, gated content, expanded games API - #1539

Merged
aji70 merged 1 commit into
MyFanss:mainfrom
vitalis200:user
Jul 27, 2026
Merged

Title: feat: durable notification queue, gated content, expanded games API #1539
aji70 merged 1 commit into
MyFanss:mainfrom
vitalis200:user

Conversation

@vitalis200

Copy link
Copy Markdown
Contributor

Summary
Implements four backend tickets:

closes #1439 — Durable notification email queue: emails are now persisted to a new email_outbox table before delivery, with pluggable ConsoleEmailAdapter (default) and SmtpEmailAdapter (raw net/tls, no new deps) implementations selected via SMTP_HOST. Failed sends stay pending and are retried by a scheduled worker; sentEmails/getSentEmails() behavior is preserved for backward compatibility.
closes #1440 — Persist notification retry/digest state: the subscription-lifecycle retry queue and digest windows (previously only in-memory Maps on NotificationsService) are now mirrored to notification_retry_jobs and notification_digest_windows tables and rehydrated on onModuleInit, so pending retries/digests survive a restart. A NotificationRetryWorkerService ticks every 60s to drain the retry queue and re-attempt pending outbox emails. Email sends now check the recipient's email_subscription_renewal preference (via UsersService), failing open on lookup errors.
closes #1442 — Gated-content checks on content reads: new ContentAccessService gates GET /v1/content/:id on subscription_tier — public content and content owners get the full payload, active subscribers (via SubscriptionsService#isSubscriber) get the full payload, everyone else gets a teaser with description/ipfs_cid/ipfs_url stripped. A new OptionalJwtAuthGuard lets the route resolve an optional requester identity without requiring auth.
closes #1441 — Expand games API: adds POST /v1/games/:id/start (host-only, PENDING→IN_PROGRESS, needs ≥2 players), POST /v1/games/:id/leave (removes the player, reassigns host to the next-lowest turn order, or clears it), and POST /v1/games/:id/score (updates a player's balance while IN_PROGRESS). A new host_user_id column on Game is set to the first player to join. Invalid state transitions return 400; non-host start returns 403.
All new persistence/DI is wired as optional dependencies on NotificationsService so every existing unit test keeps passing unchanged — production NotificationsModule always supplies the durable stores, which is what gives the service its durability.

Notes / follow-ups
ContentAccessService's subscriber check reuses SubscriptionsService#isSubscriber, which is keyed on Stellar addresses on-chain; until platform user IDs are bridged to wallet addresses (see the existing caveat on HybridFanAuthGuard), this treats requesterId/creator_id as opaque identity strings.
GamesModule/ContentModule are still not registered in AppModule — pre-existing, left as-is (out of scope for these tickets).
SmtpEmailAdapter is an intentionally basic stub (no STARTTLS upgrade, assumes single-chunk server replies) — swap for a hardened library-backed client before production mail volume.
Test plan
npm test --prefix backend (new specs: games start/leave/score, ContentAccessService, OptionalJwtAuthGuard, email adapters, EmailOutboxService, retry/digest stores, retry worker, notifications persistence integration)
Run the new migration (1750000000000-CreateNotificationDurableState) against a scratch DB
Manually exercise GET /v1/content/:id as anonymous / non-subscriber / subscriber / owner
closes #

@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@vitalis200 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! 🚀

Learn more about application limits

@aji70
aji70 merged commit 81fb4bc into MyFanss:main Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants