f50b493df2
- Create notification management system with VNotifications class - Build notification API endpoints (get, mark read, delete, preferences) - Implement notification bell UI widget with real-time polling - Add email queue system with digest support (instant/hourly/daily/weekly) - Create notification trigger helpers for common events - Add fine-grained user preference controls - Implement cron script for email processing and cleanup - Document setup and usage in docs/NOTIFICATIONS.md Features: - In-app notifications with dropdown - Email delivery with batching - 6 notification types (comment, like, subscribe, upload, mention, system) - User preference controls per notification type - Lightweight polling (30s interval) - Auto-cleanup of old notifications (90 days) - Production-ready with proper error handling
400 lines
9.7 KiB
Markdown
400 lines
9.7 KiB
Markdown
# Notification System
|
|
|
|
EasyStream now includes a comprehensive notification system with in-app and email delivery.
|
|
|
|
## Features
|
|
|
|
- 🔔 **In-app notifications** with real-time updates
|
|
- 📧 **Email notifications** with digest support (instant, hourly, daily, weekly)
|
|
- 🎯 **Fine-grained preferences** - users control what they receive
|
|
- ⚡ **Lightweight polling** - updates every 30 seconds without performance impact
|
|
- 📱 **Modern UI** - notification bell with dropdown list
|
|
- 🎨 **Multiple notification types** - comments, likes, subscribes, uploads, mentions, system
|
|
|
|
## Setup
|
|
|
|
### 1. Database Migration
|
|
|
|
Run the migration to create notification tables:
|
|
|
|
```bash
|
|
docker-compose exec db mysql -u easystream -peasystream easystream < __install/migrations/002_add_notifications_system.sql
|
|
```
|
|
|
|
This creates:
|
|
- `db_notifications` - stores all notifications
|
|
- `db_notification_preferences` - user notification settings
|
|
- `db_email_queue` - queued emails for batched sending
|
|
|
|
### 2. Add Notification Bell to Header
|
|
|
|
Include the JavaScript and add the widget container to your header template:
|
|
|
|
```html
|
|
<!-- In your header template -->
|
|
<script src="/f_scripts/fe/js/notifications.js"></script>
|
|
|
|
<!-- Add the notification bell to your navigation -->
|
|
<div id="notification-widget"></div>
|
|
```
|
|
|
|
The widget will auto-initialize and start polling for updates.
|
|
|
|
### 3. Configure Email Sending
|
|
|
|
By default, the system uses PHP's `mail()` function. For production, integrate with a proper email service:
|
|
|
|
Edit `f_core/f_classes/class.notifications.php` → `sendEmail()` method to integrate:
|
|
- **SendGrid**
|
|
- **Mailgun**
|
|
- **Amazon SES**
|
|
- **SMTP**
|
|
|
|
### 4. Set Up Cron Job
|
|
|
|
Add a cron job to process the email queue:
|
|
|
|
```bash
|
|
# Process email queue every 15 minutes
|
|
*/15 * * * * cd /srv/easystream && php app_scripts/cron/process_notification_emails.php >> /var/log/notification-emails.log 2>&1
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Triggering Notifications
|
|
|
|
Use the `VNotificationTriggers` helper class for common events:
|
|
|
|
```php
|
|
require_once 'f_core/f_classes/class.notification_triggers.php';
|
|
$triggers = new VNotificationTriggers();
|
|
|
|
// When someone comments on a video
|
|
$triggers->notifyVideoComment(
|
|
$video_owner_id,
|
|
$commenter_id,
|
|
$commenter_name,
|
|
$video_id,
|
|
$video_title,
|
|
$comment_text
|
|
);
|
|
|
|
// When someone likes a video
|
|
$triggers->notifyVideoLike(
|
|
$video_owner_id,
|
|
$liker_id,
|
|
$liker_name,
|
|
$video_id,
|
|
$video_title
|
|
);
|
|
|
|
// When someone subscribes
|
|
$triggers->notifySubscribe(
|
|
$channel_owner_id,
|
|
$subscriber_id,
|
|
$subscriber_name
|
|
);
|
|
|
|
// When uploading a new video (notifies all subscribers)
|
|
$triggers->notifySubscribersNewVideo(
|
|
$channel_owner_id,
|
|
$channel_name,
|
|
$video_id,
|
|
$video_title
|
|
);
|
|
|
|
// When mentioning a user in a comment
|
|
$triggers->notifyMention(
|
|
$mentioned_user_id,
|
|
$mentioner_id,
|
|
$mentioner_name,
|
|
$video_id,
|
|
$comment_text
|
|
);
|
|
|
|
// System notification
|
|
$triggers->notifySystem(
|
|
$user_id,
|
|
'Your video was approved',
|
|
'Your video "Tutorial" is now live!',
|
|
'/watch?v=123'
|
|
);
|
|
```
|
|
|
|
### API Endpoints
|
|
|
|
#### Get Notifications
|
|
|
|
```http
|
|
GET /api/notifications.php?limit=20&offset=0&unread_only=true
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"notifications": [
|
|
{
|
|
"notification_id": 123,
|
|
"type": "comment",
|
|
"title": "John commented on your video",
|
|
"message": "Great tutorial!",
|
|
"link": "/watch?v=456#comments",
|
|
"is_read": 0,
|
|
"created_at": "2026-03-30 17:30:00",
|
|
"actor_username": "john",
|
|
"actor_avatar": "/avatars/john.jpg"
|
|
}
|
|
],
|
|
"unread_count": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Get Unread Count (Lightweight)
|
|
|
|
```http
|
|
GET /api/notifications_count.php
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"unread_count": 5
|
|
}
|
|
```
|
|
|
|
#### Mark as Read
|
|
|
|
```http
|
|
POST /api/notifications.php?action=mark_read
|
|
Content-Type: application/json
|
|
|
|
{"id": 123}
|
|
```
|
|
|
|
Or mark multiple:
|
|
```json
|
|
{"id": "123,124,125"}
|
|
```
|
|
|
|
#### Mark All as Read
|
|
|
|
```http
|
|
POST /api/notifications.php?action=mark_all_read
|
|
```
|
|
|
|
#### Delete Notification
|
|
|
|
```http
|
|
DELETE /api/notifications.php?id=123
|
|
```
|
|
|
|
### User Preferences
|
|
|
|
#### Get Preferences
|
|
|
|
```http
|
|
GET /api/notification_preferences.php
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"email_comments": 1,
|
|
"email_likes": 1,
|
|
"email_subscribes": 1,
|
|
"email_uploads": 1,
|
|
"email_mentions": 1,
|
|
"email_digest": "instant",
|
|
"push_enabled": 1,
|
|
"push_comments": 1,
|
|
"push_likes": 1,
|
|
"push_subscribes": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Update Preferences
|
|
|
|
```http
|
|
POST /api/notification_preferences.php
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email_digest": "daily",
|
|
"email_likes": 0,
|
|
"push_comments": 1
|
|
}
|
|
```
|
|
|
|
## Email Digest Modes
|
|
|
|
Users can choose how often they receive emails:
|
|
|
|
- **instant** - Send immediately when notification is created
|
|
- **hourly** - Batch and send once per hour
|
|
- **daily** - Send once per day (at midnight)
|
|
- **weekly** - Send once per week (Monday)
|
|
- **never** - No email notifications
|
|
|
|
## Notification Types
|
|
|
|
| Type | Description | Example |
|
|
|------|-------------|---------|
|
|
| `comment` | New comment on video or reply to comment | "John commented on your video" |
|
|
| `like` | Someone liked a video | "Sarah liked your video" |
|
|
| `subscribe` | New channel subscriber | "Mike subscribed to your channel" |
|
|
| `video_upload` | Subscribed channel uploaded new video | "TechChannel uploaded a new video" |
|
|
| `mention` | User mentioned in a comment | "@username mentioned you" |
|
|
| `system` | Admin/system messages | "Your video was approved" |
|
|
|
|
## Frontend Integration
|
|
|
|
The notification widget provides:
|
|
- Real-time bell icon with unread count badge
|
|
- Dropdown with last 20 notifications
|
|
- Mark individual or all as read
|
|
- Auto-polling every 30 seconds
|
|
- Click notification to navigate and mark as read
|
|
|
|
### JavaScript API
|
|
|
|
```javascript
|
|
// Access the widget instance
|
|
const widget = window.notificationWidget;
|
|
|
|
// Manually fetch notifications
|
|
widget.fetchNotifications();
|
|
|
|
// Manually fetch unread count
|
|
widget.fetchUnreadCount();
|
|
|
|
// Change poll interval (milliseconds)
|
|
widget.pollInterval = 60000; // 1 minute
|
|
widget.stopPolling();
|
|
widget.startPolling();
|
|
|
|
// Programmatically mark as read
|
|
widget.markAsRead(notification_id);
|
|
|
|
// Mark all as read
|
|
widget.markAllAsRead();
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### db_notifications
|
|
|
|
| Column | Type | Description |
|
|
|--------|------|-------------|
|
|
| notification_id | INT | Primary key |
|
|
| usr_id | INT | User receiving notification |
|
|
| type | ENUM | Notification type |
|
|
| title | VARCHAR(255) | Short title |
|
|
| message | TEXT | Optional detailed message |
|
|
| link | VARCHAR(512) | URL to navigate to |
|
|
| actor_id | INT | User who triggered notification |
|
|
| related_video_id | INT | Associated video |
|
|
| related_comment_id | INT | Associated comment |
|
|
| is_read | TINYINT | Read status |
|
|
| is_seen | TINYINT | Seen status (viewed in UI) |
|
|
| email_sent | TINYINT | Email delivery status |
|
|
| created_at | DATETIME | Creation timestamp |
|
|
| read_at | DATETIME | When marked as read |
|
|
|
|
### db_notification_preferences
|
|
|
|
| Column | Type | Description |
|
|
|--------|------|-------------|
|
|
| usr_id | INT | User ID (unique) |
|
|
| email_comments | TINYINT | Email for comments |
|
|
| email_likes | TINYINT | Email for likes |
|
|
| email_subscribes | TINYINT | Email for subscribes |
|
|
| email_uploads | TINYINT | Email for new uploads |
|
|
| email_mentions | TINYINT | Email for mentions |
|
|
| email_digest | ENUM | Digest frequency |
|
|
| push_enabled | TINYINT | Push notifications enabled |
|
|
| push_comments | TINYINT | Push for comments |
|
|
| push_likes | TINYINT | Push for likes |
|
|
| push_subscribes | TINYINT | Push for subscribes |
|
|
|
|
### db_email_queue
|
|
|
|
| Column | Type | Description |
|
|
|--------|------|-------------|
|
|
| queue_id | INT | Primary key |
|
|
| usr_id | INT | Recipient user |
|
|
| email | VARCHAR(255) | Email address |
|
|
| subject | VARCHAR(255) | Email subject |
|
|
| body | TEXT | Email HTML body |
|
|
| scheduled_for | DATETIME | When to send |
|
|
| sent_at | DATETIME | Actual send time |
|
|
| attempts | INT | Send attempts |
|
|
| last_error | TEXT | Last error message |
|
|
|
|
## Performance
|
|
|
|
- **Polling:** Lightweight count query every 30s (~1-2ms)
|
|
- **Dropdown:** Full notification list only when opened
|
|
- **Email queue:** Batched processing reduces database load
|
|
- **Auto-cleanup:** Old read notifications purged after 90 days
|
|
|
|
## Cleanup
|
|
|
|
The cron script automatically cleans up:
|
|
- Read notifications older than 90 days
|
|
- Sent emails from queue
|
|
|
|
Adjust retention in `app_scripts/cron/process_notification_emails.php`:
|
|
|
|
```php
|
|
$notifications->cleanup(90); // Days to keep
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Notifications not appearing
|
|
|
|
1. Check database tables exist
|
|
2. Verify JavaScript is loaded: `window.notificationWidget`
|
|
3. Check browser console for errors
|
|
4. Verify user is authenticated (session active)
|
|
|
|
### Emails not sending
|
|
|
|
1. Check cron job is running
|
|
2. View email queue: `SELECT * FROM db_email_queue WHERE sent_at IS NULL`
|
|
3. Check email configuration in `class.notifications.php`
|
|
4. View logs: `docker-compose logs php | grep email`
|
|
|
|
### High polling load
|
|
|
|
Increase poll interval in the frontend:
|
|
|
|
```javascript
|
|
// In notifications.js
|
|
this.pollInterval = 60000; // 60 seconds instead of 30
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
- [ ] WebSocket/SSE for true real-time updates (eliminate polling)
|
|
- [ ] Push notifications (web push API)
|
|
- [ ] Mobile app push notifications (FCM/APNS)
|
|
- [ ] Notification grouping (e.g., "5 people liked your video")
|
|
- [ ] Rich notification preview with thumbnails
|
|
- [ ] Notification filtering/search
|
|
- [ ] Mark as unread
|
|
- [ ] Snooze notifications
|
|
|
|
## Learn More
|
|
|
|
- [Web Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
|
|
- [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
- [Email Best Practices](https://developers.google.com/gmail/design/reference/supported_css)
|