Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Feature 13: Notifications & Push

Overview

The in-game notification system that replaces WebSockets. Workers write notification records to the database when events complete. The client polls for them. Mobile push notifications are sent via FCM/APNs when the player appears to be offline.

Dependencies

  • Feature 00 (Project Foundation) — Redis, database
  • Feature 01 (Authentication) — user sessions

Technical Tasks

1. Database Migration

  • Create migration 0012_create_notifications.sql:
    • notifications: id, character_id, type, title, body, data (JSONB for deep linking), is_read, created_at
    • Index: (character_id, created_at DESC) WHERE is_read = false
    • push_tokens: user_id, platform (‘ios’/‘android’/‘web’), token, created_at, updated_at
    • Index: user_id
    • notification_preferences: character_id, notification_type, in_app (bool), push (bool)
    • PK: character_id + notification_type

2. Notification Types (crates/types)

  • Create src/notification.rs:
    • NotificationType enum:
      • RunComplete
      • CraftComplete
      • GatheringComplete
      • ItemSold
      • MailReceived
      • PvpMatchComplete
      • RaidLobbyStarting
      • GuildWarDeclared
      • ReputationTierUp
      • LevelUp
      • AchievementEarned
    • Notification struct: id, character_id, notification_type, title, body, data (serde_json::Value), is_read, created_at

3. Notification Service (crates/db or shared)

  • Create src/queries/notifications.rs:
    • create_notification(pool, character_id, type, title, body, data) -> Notification
    • get_unread_notifications(pool, character_id, since: Option<DateTime>) -> Vec<Notification>
    • mark_read(pool, notification_ids: &[Uuid])
    • mark_all_read(pool, character_id)
    • delete_old_notifications(pool, older_than: DateTime) — cleanup, 30-day retention
    • get_notification_preferences(pool, character_id) -> Vec<NotificationPreference>
    • update_notification_preference(pool, character_id, type, in_app, push)

4. Push Notification Service

  • Create crates/api/src/push.rs (or shared crate):
    • should_send_push(pool, redis, character_id, notification_type) -> bool:
      • Check push preference enabled for this type
      • Check last API request from this user was > 5 minutes ago (likely offline)
    • send_push(token: &str, platform: &str, title: &str, body: &str, data: &Value):
      • FCM for Android (via HTTP v1 API)
      • APNs for iOS (via a]pns2 crate or HTTP/2 directly)
    • register_push_token(pool, user_id, platform, token)
    • unregister_push_token(pool, user_id, platform)

5. Integrate Notifications into Workers

  • Update all workers to create notifications on event completion:
    • Simulation worker (Feature 07): RunComplete + LevelUp if applicable
    • Crafting worker (Feature 08): CraftComplete
    • Gathering worker (Feature 08): GatheringComplete
    • Economy worker (Feature 09): ItemSold, MailReceived
    • PVP worker (Feature 11): PvpMatchComplete
    • After creating notification, check if push should be sent

6. Notification Routes (crates/api)

  • Create src/routes/notifications.rs:
    • GET /api/characters/:id/notifications?since=:
      • Return unread notifications since timestamp
      • Ordered by created_at DESC
      • Limit 50 per request
    • POST /api/characters/:id/notifications/read:
      • Body: { ids: [uuid] }
      • Mark specified notifications as read
    • POST /api/characters/:id/notifications/read-all:
      • Mark all as read
    • GET /api/characters/:id/notification-preferences:
      • Return per-type in_app + push settings
    • PUT /api/characters/:id/notification-preferences:
      • Body: { type, in_app, push }
      • Update preference

7. Push Token Registration Routes

  • Add to auth or settings routes:
    • POST /api/push/register:
      • Body: { platform, token }
      • Register push token for authenticated user
    • DELETE /api/push/unregister:
      • Body: { platform }
      • Remove push token

8. Scheduled Cleanup

  • Add to scheduler worker:
    • cleanup-notifications: daily, delete notifications older than 30 days
    • cleanup-read-notifications: daily, delete read notifications older than 7 days

9. Client — Notifications

  • Create notification bell component in the main nav:
    • Badge showing unread count
    • Dropdown panel listing recent notifications
    • Click notification → navigate to relevant screen (run result, mail, PVP result)
  • Polling: GET /api/characters/:id/notifications every 30 seconds
  • Create routes/(game)/settings/notifications/+page.svelte:
    • Per-type toggles for in-app and push notifications
  • Mobile (Capacitor):
    • Register push token on app start via @capacitor/push-notifications
    • Handle push tap → deep link to relevant content

Tests

Unit Tests

  • should_send_push: returns true when push enabled and user offline > 5 min
  • should_send_push: returns false when push disabled for type
  • should_send_push: returns false when user recently active
  • NotificationType serializes/deserializes correctly

Integration Tests

  • GET /api/characters/:id/notifications returns unread notifications
  • GET /api/characters/:id/notifications?since= filters by timestamp
  • POST /api/characters/:id/notifications/read marks as read
  • Subsequent GET excludes read notifications
  • POST /api/characters/:id/notifications/read-all clears all
  • Run completion creates RunComplete notification
  • Craft completion creates CraftComplete notification
  • Item sold creates ItemSold notification
  • Notification preferences are respected (disabled type → no notification created for in-app)
  • Push token registration and unregistration work
  • Cleanup job deletes old notifications