This directory contains the notification matching and delivery system for OboApp.
The notification system automatically notifies users when new messages are posted that match their areas of interest. It consists of:
- Client-side permission management - Requests notification permissions and manages FCM subscriptions
- Server-side matching - Matches messages with user interests based on geographic proximity
- Push notification delivery - Sends notifications via Firebase Cloud Messaging
- User logs in and adds their first interest circle, OR user logs in from a new device
- System automatically requests notification permission
- If granted, FCM token is generated and stored in Firebase
- User receives notifications when new messages match their interests
flowchart TD
A[New Message Ingested] --> B[Run npx tsx notify]
B --> C[Find Unprocessed Messages]
C --> D[Get All User Interests]
D --> E[Match Messages to Interests]
E --> F[Deduplicate Matches]
F --> G[Store in notificationMatches]
G --> H[Get Unnotified Matches]
H --> I[Send Push Notifications]
I --> J[Mark as Notified]
lib/notification-service.ts
- Requests notification permissions
- Manages FCM subscriptions
- Handles foreground messages
- Stores tokens in Firebase
lib/hooks/useInterests.ts
- Triggers notification permission request when circles are shown
- Checks if subscription is valid on login
public/firebase-messaging-sw.js
- Service worker for background push notifications
- Handles notification clicks
lib/notifications/match-and-notify.ts
- Main script that runs after ingestion
- Identifies unprocessed messages
- Matches messages with user interests using geographic intersection
- Deduplicates matches (one notification per user per message)
- Sends push notifications via FCM
- Marks matches as processed
app/api/notifications/subscription/route.ts
- API endpoint for managing notification subscriptions
- GET - Check if user has subscription
- POST - Create/update subscription
- DELETE - Remove subscription
Stores FCM tokens for push notifications.
{
id: string;
userId: string;
token: string; // FCM token
endpoint: string;
createdAt: Date;
updatedAt: Date;
deviceInfo?: {
userAgent?: string;
};
}Stores matches between messages and user interests.
{
id: string;
userId: string;
messageId: string;
interestId: string;
matchedAt: Date;
notified: boolean;
notifiedAt?: Date;
notificationError?: string;
distance?: number; // meters from interest center
}The notification script should be run after message ingestion:
# Run ingestion first
pnpm ingest
# Then run notifications
pnpm notifyFor automated workflows, combine them:
pnpm ingest && pnpm notifyAdd these to your .env.local:
# Firebase Cloud Messaging
NEXT_PUBLIC_FIREBASE_VAPID_KEY=your_vapid_key_here
# Optional: Custom app URL for notification links
APP_URL=https://your-domain.com- Go to Firebase Console → Project Settings → Cloud Messaging
- Generate a Web Push certificate (VAPID key)
- Add the VAPID key to your environment variables
- Enable Cloud Messaging API in Google Cloud Console
Update public/firebase-messaging-sw.js with your Firebase config:
firebase.initializeApp({
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
});- default - User hasn't been asked yet → Request permission
- granted - User has granted permission → Ensure subscription is valid
- denied - User has denied permission → Don't ask again
Messages are matched to user interests based on geographic intersection:
- Create a circle around each user interest (using specified radius)
- Check if message GeoJSON features intersect with the circle
- Calculate distance from interest center to closest point
- Store match with distance for potential future filtering
The system ensures users only receive one notification per message:
- Multiple interests belonging to the same user can match the same message
- Only the closest match (smallest distance) is kept
- Notification is sent once per user per message
- Failed notifications are logged with error message
- Matches are still marked as "notified" to prevent retry loops
- Expired or invalid FCM tokens should be cleaned up manually
- Auto-cleanup of expired subscriptions
- User preferences for notification frequency
- Digest notifications (batch multiple messages)
- Distance-based filtering (only notify if very close)
- Notification history UI