# Notifications (DB + WebSocket) - doi_gov_api

This repo implements:

- `tbl_notifications` rows for user/global notifications
- a WebSocket push mechanism so the `doi_gov` frontend receives `NEW_NOTIFICATION` in real time

## Key constants and what they mean

Defined in `config/<env>/config.php` (example: `config/local/config.php`):

- `NOTIFICATION_WS_BROADCAST_URL`
  - Where PHP sends an internal POST after inserting a notification.
  - Example: `http://127.0.0.1:3010/internal/broadcast`

- `NOTIFICATION_WS_INTERNAL_SECRET`
  - Shared secret between PHP and the Node relay.
  - PHP sends it as HTTP header `X-Internal-Secret`.
  - Node must be started with the same secret (`NOTIFICATION_WS_INTERNAL_SECRET` env).

- `DOI_UPLOAD_TOKEN_PATH`
  - **Not a notification feature by itself.**
  - It is the directory used by the existing **file-based auth token system**.
  - Tokens are stored as files:
    - `DOI_UPLOAD_TOKEN_PATH/frontend/<token>.json`
    - Content: `{"user_id": <numeric_db_user_id>, "expiry": <unix_ts>}`

### Why `DOI_UPLOAD_TOKEN_PATH` matters for WebSocket

In `doi_gov` frontend, the auth cookie (`app_data`) stores:

- `user_id = <raw_token_string>`

That same token string is used as the `userId` in the WS connection:

- `ws://.../ws?userId=<raw_token_string>`

However, `tbl_notifications.user_id` is the **numeric DB user id**.

To deliver WS messages to the correct browser session(s), the backend resolves:

`numeric_user_id` → `active token file(s)` → `raw_token_string(s)`

This resolution is implemented in:

- `common/class.notification_ws_push.php` (`resolveActiveTokensForUser()`)

## Notification creation flow

1) Code creates notification:

- `common/class.notification.php` → `NotificationService::addNotification(...)`

2) Insert into DB:

- `tbl_notifications` row inserted (includes `creation_date`, `priority`, etc.)

3) WS push:

- `NotificationWsPush::dispatchAfterInsert(...)` is called
- It POSTs to `NOTIFICATION_WS_BROADCAST_URL`
- Targets `userIds` as **token strings** (not numeric user ids) so the frontend receives it

## WebSocket payload contract

Sent to the browser:

```json
{
  "type": "NEW_NOTIFICATION",
  "payload": {
    "id": 15,
    "type": "evaluation_reminder",
    "notification_type": "warning",
    "category": "evaluation_reminder",
    "title": "Project Evaluation to: Panoptic Evaluation",
    "message": "....",
    "creation_date": "2026-03-31 07:12:20",
    "is_read": false,
    "starred": false,
    "priority": "medium",
    "is_important": true,
    "action_url": "projects_detail_page.php?project_id=181",
    "is_starred": false,
    "acknowledged_at": null,
    "acknowledged": false,
    "extra": {
      "projectShortName": "PAN-001",
      "projectId": 181,
      "projectDueDate": "2026-04-07",
      "evaluationStatus": "Pending",
      "evaluatorEvalStatus": "pending",
      "evaluationProgress": 40
    }
  }
}
```

## APIs

File: `api/notifications.php` (serviceName: `notifications`)

- `getUnreadNotifications`: user unread + global unread
- `getAllNotifications`: user + global (supports `only_unread`, `limit`, `offset`)
- `updateNotificationFlags`: set `is_read` and/or `is_important`

## Production: running the Node relay

The WS relay lives in `ws-notification-server/` and must be running for realtime pushes.

### Dependencies

- Node.js **18+**
- NPM

Install once:

```bash
cd /var/www/doi_gov_api/ws-notification-server
npm install
```

### Run locally (dev)

```bash
cd /var/www/doi_gov_api/ws-notification-server
NOTIFICATION_WS_PORT=3010 \
NOTIFICATION_WS_INTERNAL_SECRET="doi-gov-ws-internal-dev" \
node server.js
```

### Run on server (PM2)

Use PM2 to keep the WebSocket relay alive and restart on reboot.

Install PM2 globally (once):

```bash
sudo npm install -g pm2
```

Start the relay with environment variables:

```bash
cd /var/www/doi_gov_api/ws-notification-server
NOTIFICATION_WS_PORT=3010 \
NOTIFICATION_WS_INTERNAL_SECRET="CHANGE_ME_LONG_RANDOM" \
pm2 start server.js --name doi-notification-ws
```

Common PM2 commands:

```bash
pm2 status
pm2 logs doi-notification-ws
pm2 restart doi-notification-ws
pm2 stop doi-notification-ws
```

Enable startup on reboot + save process list:

```bash
pm2 startup
pm2 save
```

### Apache + AWS EC2 deployment checklist (production)

If you are deploying on Apache/LAMP, use this checklist:

- Apache modules:
  - No proxy module is required for direct PHP SSE endpoint.
  - Keep `headers` module enabled when you set CORS/cache headers.
- KeepAlive/timeout for long-lived stream requests:
  - `KeepAlive On`
  - `KeepAliveTimeout` reasonably high (for example 60+)
  - `Timeout` should not be too low
- PHP runtime for stream endpoint:
  - `max_execution_time` should be high enough (or unlimited for this endpoint)
  - avoid delayed flush due to buffering/compression (`output_buffering`, `zlib.output_compression`)
- HTTPS only:
  - frontend must call stream over `https://` when site is HTTPS (mixed content is blocked)
- CORS:
  - if frontend and API domains differ, use explicit allowed origin for notification endpoints
- EC2 security group:
  - allow inbound `443` (and `80` only for redirect to HTTPS)
  - allow outbound DB access if DB is external
- ALB timeout (if ALB is used):
  - idle timeout should be higher than your stream cycle
  - current stream reconnect pattern (~25s) is generally safe
- Capacity planning:
  - SSE keeps one open HTTP connection per client; tune Apache worker limits (`MaxRequestWorkers`)
- Logs:
  - stream traffic can increase access logs; ensure logrotate is configured

Frontend can use:
- `wss://<your-domain>/ws?userId=<token>` (for WebSocket relay)
- `https://<your-domain>/api/notification_stream.php?user_id=<id>&token=<token>&last_id=<id>` (for SSE endpoint)

### Health / debugging endpoints

- `GET /internal/stats` (requires header `X-Internal-Secret`)

```bash
curl -X GET "http://127.0.0.1:3010/internal/stats" \
  -H "X-Internal-Secret: <secret>"
```

