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:NotificationTypeenum:- RunComplete
- CraftComplete
- GatheringComplete
- ItemSold
- MailReceived
- PvpMatchComplete
- RaidLobbyStarting
- GuildWarDeclared
- ReputationTierUp
- LevelUp
- AchievementEarned
Notificationstruct: 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) -> Notificationget_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 retentionget_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]pns2crate 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+LevelUpif 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
- Simulation worker (Feature 07):
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
- Body:
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
- Body:
7. Push Token Registration Routes
- Add to auth or settings routes:
POST /api/push/register:- Body:
{ platform, token } - Register push token for authenticated user
- Body:
DELETE /api/push/unregister:- Body:
{ platform } - Remove push token
- Body:
8. Scheduled Cleanup
- Add to scheduler worker:
cleanup-notifications: daily, delete notifications older than 30 dayscleanup-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/notificationsevery 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
- Register push token on app start via
Tests
Unit Tests
should_send_push: returns true when push enabled and user offline > 5 minshould_send_push: returns false when push disabled for typeshould_send_push: returns false when user recently activeNotificationTypeserializes/deserializes correctly
Integration Tests
GET /api/characters/:id/notificationsreturns unread notificationsGET /api/characters/:id/notifications?since=filters by timestampPOST /api/characters/:id/notifications/readmarks as read- Subsequent GET excludes read notifications
POST /api/characters/:id/notifications/read-allclears 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