You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: TROUBLESHOOTING.md
+46Lines changed: 46 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -69,3 +69,49 @@ Welcome to the troubleshooting guide for Peer Learning! If you encounter problem
69
69
- For OAuth, verify that the Client ID and Secret match the ones configured in your OAuth provider's developer console, and that the callback URL matches your Supabase project's redirect URL.
70
70
- After updating environment variables, restart the development server before testing authentication again.
71
71
- If you encounter a "Failed to fetch" error during signup, verify that your `.env` file contains valid Supabase credentials and that the application has been restarted after any configuration changes.
72
+
73
+
## 6. Push Notifications Not Delivering
74
+
75
+
### Symptom: Cron runs but `sent` is always 0
76
+
77
+
**Check VAPID configuration.** The response body will contain `{ "error": "Missing VAPID push server env" }` if `VAPID_PUBLIC_KEY` or `VAPID_PRIVATE_KEY` are not set in `backend/.env`. Generate keys and set them:
78
+
79
+
```bash
80
+
npx web-push generate-vapid-keys
81
+
```
82
+
83
+
Add to `backend/.env`:
84
+
```env
85
+
VAPID_PUBLIC_KEY=<generated>
86
+
VAPID_PRIVATE_KEY=<generated>
87
+
VAPID_SUBJECT=mailto:admin@yourdomain.com
88
+
```
89
+
90
+
> Changing these keys invalidates all existing browser subscriptions. Users must re-subscribe.
91
+
92
+
### Symptom: Cron returns `HTTP 401` or `403`
93
+
94
+
`CRON_SECRET` is missing or does not match what the scheduler is sending. Verify the env var is set on the server and that the scheduler is passing `Authorization: Bearer <CRON_SECRET>`. Check `[AUDIT]` lines in server logs to confirm the secret type being presented.
95
+
96
+
### Symptom: Cron returns `HTTP 429` immediately
97
+
98
+
The 60-second per-route cooldown is active. The cron fired twice within 60 seconds (Vercel's at-least-once guarantee can cause this). Wait 60 seconds and retry. If this blocks manual drain, see the Manual Drain Procedure in `docs/smart-notifications.md`.
99
+
100
+
### Symptom: Notifications accumulate but are never delivered (queue keeps growing)
101
+
102
+
1. Confirm the cron scheduler is running and reaching the server (look for `[AUDIT]` log lines).
103
+
2. Check queue depth:
104
+
```sql
105
+
SELECTCOUNT(*) FROM notifications WHERE push_sent_at IS NULL;
106
+
```
107
+
3. If the queue is large, manually drain it — see `docs/smart-notifications.md` → **Manual drain procedure**.
108
+
109
+
### Symptom: `sent` is much lower than `processed` on every run
110
+
111
+
Most subscriptions in `push_subscriptions` are likely expired (browser was uninstalled, permission was revoked, or VAPID keys changed). The cron absorbs individual push failures silently. Check:
112
+
113
+
```sql
114
+
SELECTCOUNT(*) FROM push_subscriptions;
115
+
```
116
+
117
+
If zero or very low, users need to re-subscribe by visiting the site and granting notification permission again.
Copy file name to clipboardExpand all lines: docs/api.md
+106Lines changed: 106 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,3 +66,109 @@ Generates an AI summary of a chat session.
66
66
**Security & Rate Limiting**:
67
67
- Requires a valid Supabase JWT token.
68
68
- Protected by a custom, in-house rate limiter middleware (`backend/middlewares/rateLimiter.js`) to prevent abuse.
69
+
70
+
## 🔔 Cron Endpoints
71
+
72
+
All cron endpoints require `Authorization: Bearer <CRON_SECRET>` and are subject to a 5 req/min per-IP rate limit and a 60-second per-route cooldown. Invocations within the cooldown window return `HTTP 429`.
73
+
74
+
---
75
+
76
+
### `POST /api/cron/dispatch-notifications`
77
+
78
+
Dequeues up to 100 pending push notifications (`push_sent_at IS NULL`, oldest first) and delivers them to all registered browser push subscriptions for each recipient.
79
+
80
+
**Auth:**`Authorization: Bearer <CRON_SECRET>`
81
+
82
+
**Response `200`:**
83
+
```json
84
+
{
85
+
"sent": 42,
86
+
"processed": 45
87
+
}
88
+
```
89
+
90
+
| Field | Description |
91
+
|---|---|
92
+
|`processed`| Number of notification rows fetched (max 100) |
93
+
|`sent`| Number of individual push deliveries that succeeded |
94
+
95
+
`processed - sent` reflects push failures (expired subscriptions, VAPID misconfiguration, etc.). Individual failures are absorbed and do not block delivery to other recipients. Each notification row is stamped with `push_sent_at` regardless of delivery outcome.
96
+
97
+
**Error responses:**
98
+
99
+
| Status | Condition |
100
+
|---|---|
101
+
|`401`| Missing or malformed `Authorization` header |
102
+
|`403`| Secret mismatch |
103
+
|`429`| Rate limit or 60-second cooldown exceeded |
104
+
|`500`| VAPID keys not configured, or Supabase error |
105
+
|`503`|`CRON_SECRET` env var not set on the server |
106
+
107
+
---
108
+
109
+
### `POST /api/cron/reminders`
110
+
111
+
Inserts `session_reminder` notifications for all sessions with `status = 'upcoming'` whose `start_time` falls within the next 14–16 minutes. Idempotent via `upsert` on `(user_id, entity_id, type)`.
112
+
113
+
**Auth:**`Authorization: Bearer <CRON_SECRET>`
114
+
115
+
**Response `200`:**
116
+
```json
117
+
{ "inserted": 12 }
118
+
```
119
+
120
+
**Error responses:** same as `/api/cron/dispatch-notifications`.
121
+
122
+
---
123
+
124
+
### `POST /api/cron/mentorship-reminders`
125
+
126
+
Inserts `mentorship_reminder` notifications for incomplete milestones due within 24 hours or already overdue. Notifies both the mentor and mentee. Idempotent via `upsert` on `(user_id, entity_id, type)`.
127
+
128
+
**Auth:**`Authorization: Bearer <CRON_SECRET>`
129
+
130
+
**Response `200`:**
131
+
```json
132
+
{ "inserted": 4 }
133
+
```
134
+
135
+
**Error responses:** same as `/api/cron/dispatch-notifications`.
136
+
137
+
---
138
+
139
+
## 🔔 Notification Endpoints
140
+
141
+
### `POST /api/notifications/send-push`
142
+
143
+
Delivers a push notification to a single user's registered browser subscriptions. Stale subscriptions (HTTP 410/404 from the push service) are automatically deleted.
144
+
145
+
**Auth:**`Authorization: Bearer <WEBHOOK_SECRET>`
146
+
147
+
> This endpoint uses a **separate secret** (`WEBHOOK_SECRET`) from the cron endpoints (`CRON_SECRET`). See the Secrets Reference in `docs/smart-notifications.md` for rotation guidance.
148
+
149
+
**Request body:**
150
+
```json
151
+
{
152
+
"user_id": "uuid",
153
+
"title": "string (max 100 chars)",
154
+
"body": "string (max 500 chars)",
155
+
"action_url": "/optional/path"
156
+
}
157
+
```
158
+
159
+
**Response `200`:**
160
+
```json
161
+
{
162
+
"sent": 1,
163
+
"failed": 0
164
+
}
165
+
```
166
+
167
+
**Error responses:**
168
+
169
+
| Status | Condition |
170
+
|---|---|
171
+
|`400`| Missing `user_id`, `title`, or `body`; payload type invalid; content too long |
172
+
|`401`| Missing or invalid `Authorization` header |
173
+
|`404`| No push subscriptions found for the given `user_id`|
174
+
|`500`| VAPID keys not configured, or Supabase error |
0 commit comments