# SWI API Documentation

Standard Work Instruction (SWI) backend for the EHS platform. REST JSON API over Express, MySQL schema **`hse`**.

---

## Base URL

| Mode | Command | Base URL |
|------|---------|----------|
| **Unified** (SAT + ORAC + SWI) | `npm start` from repo root | `http://localhost:5000/api/swi` |
| **SWI only** | `npm run dev-swi-api` | `http://localhost:5002/api/swi` |

Port overrides: `PORT` or `APP_PORT` in env (unified uses root / `Risk_assessment_backend/.env`; standalone uses `swi_backend/.env`, default **5002**).

All paths below are relative to **`/api/swi`**.

---

## Quick start

```bash
# From repo root
npm run install-swi-api
npm start

# Health (no auth)
curl http://localhost:5000/api/swi/health

# Login
curl -X POST http://localhost:5000/api/swi/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"you@example.com\",\"password\":\"your-password\"}"

# Use returned token
curl http://localhost:5000/api/swi/swi_job_area \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Database:** run `swi_backend/sql/swi.sql` on `hse` before using SWI document endpoints.

**Environment:** JWT and DB settings load from `Risk_assessment_backend/.env` and `SAT_backend/.env` (see [Configuration](#configuration)).

---

## Authentication

The **SWI UI** (`swi_frontend`) does not provide a login screen. Users authenticate once on the **HSE portal (SAT)** and open SWI from **Master Dashboard** with `?token=...` (same JWT family as below).

For API testing (Postman), you may still use:

### Login (API testing / direct clients)

```http
POST /auth/login
Content-Type: application/json
```

**Body:** `{ "email", "password" }` — HSE `users` table credentials.

Or use a token from `POST /api/sat/login` or `POST /api/auth/login` if signed with the same `JWT_*_SECRET` values.

### Bearer token on protected routes

```http
Authorization: Bearer <token>
```

**Accepted tokens**

- SWI: `POST /api/swi/auth/login`
- ORAC: `POST /api/auth/login`
- SAT: `POST /api/sat/login` (when signed with shared secrets)

Verification tries, in order: `JWT_SECRET`, `JWT_USER_SECRET`, `JWT_ADMIN_SECRET`, `JWT_EXECUTOR_SECRET`.

### Access levels

| Level | Middleware | Routes |
|-------|------------|--------|
| Public | — | `GET /health`, `POST /auth/login` |
| Authenticated | `requireAuth` | SWI tables, locations, departments, tickets, … |
| Admin only | `requireAdmin` | `users`, `roles`, `country`, `account_status`, `/admin/icons`, … |

Admin role name defaults to `admin` (`JWT_ADMIN_ROLE`).

---

## Health

```http
GET /health
```

**Response `200`**

```json
{
  "status": "ok",
  "service": "swi"
}
```

---

## CRUD conventions

Every resource table uses the same pattern:

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/{table}` | List rows |
| `GET` | `/{table}/:pk` | Get one row |
| `GET` | `/{table}/:pk1/:pk2` | Composite primary key |
| `POST` | `/{table}` | Create row |
| `PUT` | `/{table}/:pk...` | Update row |
| `DELETE` | `/{table}/:pk...` | Delete row |

### Query filters (list)

| Param | Example | SQL |
|-------|---------|-----|
| Exact match | `?document_status_id=1` | `column = ?` |
| LIKE search | `?document_status_name_like=draft` | `column LIKE %draft%` |

### Request body (create / update)

JSON object whose keys are **column names**:

```json
{
  "job_area_id": 1,
  "job_area_name": "Assembly Line A"
}
```

### Responses

**Create `201`**

```json
{
  "insertedId": 5,
  "affectedRows": 1
}
```

`insertedId` is set when the table has `AUTO_INCREMENT` (e.g. `page_id`, `text_block_id`).

**Update / delete `200`**

```json
{
  "affectedRows": 1
}
```

**Get one `200`** — single object.

**List `200`** — JSON array.

### Error format

```json
{
  "message": "Route /api/swi/unknown not found",
  "stack": "..."
}
```

`stack` only when `NODE_ENV` ≠ `production`.

| Status | Typical cause |
|--------|----------------|
| 400 | Missing PK param, empty body |
| 401 | Missing / invalid Bearer token |
| 403 | Admin route with non-admin token |
| 404 | Row not found |
| 500 | DB / server error (e.g. FK violation) |

---

## Endpoints reference

### Public

| Method | Path |
|--------|------|
| GET | `/health` |
| POST | `/auth/login` |

### SWI document module (authenticated)

| Table | Primary key(s) | Methods |
|-------|----------------|---------|
| `swi_job_area` | `job_area_id` | GET, POST, GET/:id, PUT/:id, DELETE/:id |
| `swi_document_status` | `document_status_id` | same |
| `swi_document` | `document_id`, `document_number` | GET, POST, GET/:document_id/:document_number, PUT/…, DELETE/… |
| `swi_document_page_map` | `page_id` | same |
| `swi_text_blocks` | `text_block_id` | same |
| `swi_text_block_icons` | `block_icon_id` | same |
| `swi_images` | `image_id` | same |

**Logo library (`swi_icons`)** — managed via admin endpoints below (not generic CRUD).

### Admin — logo library (admin JWT only)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/admin/icons` | List logos with `icon_data_url`, `icon_name`, `usage_count` |
| POST | `/admin/icons` | Upload logo (`icon_data_url` base64 data URL, optional `icon_name`) |
| PUT | `/admin/icons/:icon_id` | Replace image and/or update `icon_name` |
| DELETE | `/admin/icons/:icon_id` | Delete if unused (`409` when referenced by documents) |

**POST / PUT body example**

```json
{
  "icon_data_url": "data:image/png;base64,iVBORw0KGgoAAA...",
  "icon_name": "Safety hazard"
}
```

Allowed image types: PNG, JPEG, GIF, WebP. Max size: 2 MB decoded.

### Shared HSE — authenticated (any valid JWT)

| Table | Primary key(s) |
|-------|----------------|
| `locations` | `location_id` |
| `departments` | `department_id` |
| `location_departments` | `department_id`, `location_id` |
| `job_title` | `job_title_id` |
| `tickets_type` | `type_id` |
| `tickets_status` | `status_id` |
| `tickets` | `id` |
| `tickets_logs` | `log_id` |

### Shared HSE — admin JWT only

| Table | Primary key(s) |
|-------|----------------|
| `country` | `country_id` |
| `roles` | `role_id` |
| `user_role` | `user_role_id` |
| `account_status` | `status_id` |
| `users` | `user_id` |
| `users_logs` | `log_id` |

---

## Data model (SWI tables)

Schema source: `swi_backend/sql/swi.sql`.

### `swi_job_area`

| Column | Type | Notes |
|--------|------|-------|
| `job_area_id` | integer | PK |
| `job_area_name` | varchar(255) | |

### `swi_document_status`

| Column | Type | Notes |
|--------|------|-------|
| `document_status_id` | integer | PK |
| `document_status_name` | varchar(255) | |

### `swi_document`

| Column | Type | Notes |
|--------|------|-------|
| `document_id` | integer | PK (composite) |
| `document_number` | integer | PK (composite) |
| `document_name` | varchar(255) | |
| `document_status_id` | integer | FK → `swi_document_status` |
| `assigned_to_user_id` | integer | FK → `users` |
| `previous_document_id` | integer | Version chain |
| `location_id` | integer | FK → `locations` |
| `department_id` | integer | FK → `departments` |
| `job_area_id` | integer | FK → `swi_job_area` |
| `process_name` | varchar(255) | |
| `page_count` | integer | |
| `issued_at` | timestamp | |
| `assigned_at` | timestamp | |
| `completed_at` | timestamp | |

### `swi_document_page_map`

| Column | Type | Notes |
|--------|------|-------|
| `page_id` | integer | PK, AUTO_INCREMENT |
| `document_id` | integer | FK → `swi_document` |
| `page_header` | varchar(255) | |
| `page_num` | integer | |

### `swi_text_blocks`

| Column | Type | Notes |
|--------|------|-------|
| `text_block_id` | integer | PK, AUTO_INCREMENT |
| `page_id` | integer | FK → `swi_document_page_map` |
| `text_block` | varchar(255) | |

### `swi_text_block_icons`

| Column | Type | Notes |
|--------|------|-------|
| `block_icon_id` | integer | PK, AUTO_INCREMENT |
| `text_block_id` | integer | FK → `swi_text_blocks` |
| `icon_id` | integer | FK → `swi_icons` |

### `swi_images`

| Column | Type | Notes |
|--------|------|-------|
| `image_id` | integer | PK, AUTO_INCREMENT |
| `page_id` | integer | FK → `swi_document_page_map` |
| `image` | longblob | Binary; send as base64 in JSON if needed |

### `swi_icons`

| Column | Type | Notes |
|--------|------|-------|
| `icon_id` | integer | PK, AUTO_INCREMENT |
| `icon` | blob | Binary image data |
| `icon_name` | varchar(255) | Optional display name (admin) |
| `mime_type` | varchar(64) | e.g. `image/png` |
| `created_at` | timestamp | Upload time |
| `created_by_user_id` | integer | FK → `users` (optional) |

Managed via **`/admin/icons`** (admin JWT). Document forms load icons through assignment endpoints (`listIconsForSelect`).

---

## Example workflows

### 1. Seed lookups

```http
POST /swi_job_area
Authorization: Bearer <token>
Content-Type: application/json

{ "job_area_id": 1, "job_area_name": "Line A" }
```

```http
POST /swi_document_status

{ "document_status_id": 1, "document_status_name": "Draft" }
```

### 2. Create a document

Requires existing `users`, `locations`, `departments`, and lookup rows above.

```http
POST /swi_document

{
  "document_id": 1,
  "document_number": 1,
  "document_name": "SWI-001 Welding",
  "document_status_id": 1,
  "assigned_to_user_id": 1,
  "location_id": 1,
  "department_id": 1,
  "job_area_id": 1,
  "process_name": "Welding",
  "page_count": 1
}
```

### 3. Add a page and text

```http
POST /swi_document_page_map

{
  "document_id": 1,
  "page_header": "Safety steps",
  "page_num": 1
}
```

Use `insertedId` from response as `page_id`:

```http
POST /swi_text_blocks

{
  "page_id": 1,
  "text_block": "Wear full PPE before operation."
}
```

### 4. Read document with composite key

```http
GET /swi_document/1/1
Authorization: Bearer <token>
```

### 5. List with filter

```http
GET /swi_document?document_status_id=1
Authorization: Bearer <token>
```

---

## Configuration

Env load order (`src/config/loadEnv.js`):

1. Repo root `.env`
2. `SAT_backend/.env`
3. `Risk_assessment_backend/.env`
4. `swi_backend/.env` (overrides)

| Variable | Purpose |
|----------|---------|
| `DB_HOST` / `MYSQL_HOST` | MySQL host |
| `DB_USER` / `MYSQL_USER` | MySQL user |
| `DB_PASSWORD` / `MYSQL_PASSWORD` | MySQL password |
| `JWT_USER_SECRET` | User / support tokens |
| `JWT_ADMIN_SECRET` | Admin tokens |
| `JWT_EXECUTOR_SECRET` | Executor/verifier tokens |
| `JWT_SECRET` | SAT / shared SSO tokens |
| `JWT_EXPIRES_IN` | Token TTL (default `1d`) |
| `JWT_ADMIN_ROLE` | Normalized admin role name (default `admin`) |
| `APP_PORT` | Standalone SWI port (default `5002`) |
| `ALLOWED_ORIGINS` | CORS (comma-separated) |

Copy `swi_backend/.env.example` for local overrides.

---

## Running the server

```bash
# Unified API (recommended)
cd EHS
npm start

# SWI only with reload
npm run dev-swi-api

# SWI only, no reload
cd swi_backend && npm start
```

---

## Postman import checklist

1. Variable `baseUrl` = `http://localhost:5000/api/swi`
2. Request **Login** → Tests tab: `pm.collectionVariables.set("token", pm.response.json().token)`
3. Collection auth: Bearer `{{token}}`
4. Folder **Public**: Health, Login
5. Folder **SWI**: CRUD for each `swi_*` table
6. Folder **Shared**: locations, departments, tickets
7. Folder **Admin**: users, roles (admin account only)

---

## Related docs

- `API_ROUTES.md` — compact route index
- `docs/APPS-AND-API-BOUNDARIES.md` — EHS apps and `/api/swi` vs `/api/sat` vs `/api/orac`
- `sql/swi.sql` — DDL for SWI tables
