A24_Patriot_API/README.md

736 lines
15 KiB
Markdown

# Patriot API
The **Alarm24 Patriot API** is a REST API used to manage clients, zones, and users in the format required by Patriot.
---
## 🔐 Authentication
Every request must include a authorization header:
```http
Authorization: Bearer <API_KEY>
```
If the key is missing, invalid, disabled, or expired → **401 Unauthorized**.
---
## 📘 Common Status Codes
| Status | Meaning |
|--------|------------------------------------------------|
| **200 OK** | Request successful |
| **201 Created** | Resource created |
| **204 No Content** | Resource deleted |
| **401 Unauthorized** | Missing/invalid/disabled/expired API key |
| **404 Not Found** | Client/zone/user not found |
| **409 Conflict** | Duplicate record, or client temporarily locked |
| **422 Unprocessable Entity** | Validation failed, check request format |
---
## 🧩 Required Headers
Some GET endpoints require IDs in HTTP headers:
| Header | Purpose |
|--------|---------|
| **X-Client-Id** | Selects the client |
| **X-User-No** | Selects a specific user |
All endpoints **also require** the `Authorization: Bearer` header.
---
## 🗄 Data Isolation & XML Generation
### Per-key Data Isolation
Each API key has its **own completely separate dataset**:
- Clients, zones, and users are not shared between API keys.
- Two different keys can both have a client `1234`, but those are **separate clients** internally.
### XML Output Structure
For each API key, XML files for new clients are written to temporary storage before import:
```text
out/clients/<key_name>/<client_id>.xml
```
Example:
```text
out/clients/Alarm24Key/9998.xml
out/clients/InstallerXKey/1234.xml
```
### `<__id>` with Port Suffix
Each key in `keys.json` has a `port` field, e.g.:
```json
{
"key_name": "Alarm24",
"key": "secure-api-key",
"enabled": true,
"valid_to": "2030-01-01T00:00:00Z",
"port": "BASE11"
}
```
When generating XML, the `<__id>` element is:
```xml
<__id>9998BASE11</__id>
```
- `9998` = client ID
- `BASE11` = `port` value from `keys.json`
The **XML filename** remains `9998.xml` (client id only).
### Extra XML Fields Injected Per Key
Some XML fields are **not provided by the API**, but are automatically injected from `keys.json` for each key:
Examples (exact names depend on your `keys.json`):
```xml
<Installer>Installer Name</Installer>
<UseGlobCallOuts>True</UseGlobCallOuts>
<ShowOnCallOuts>False</ShowOnCallOuts>
<GlobCallOuts>TLF01BASE01</GlobCallOuts>
<UseGlobCallOuts2>True</UseGlobCallOuts2>
<ShowOnCallOuts2>False</ShowOnCallOuts2>
<GlobCallOuts2>TLF02BASE01</GlobCallOuts2>
<AltLookup>True</AltLookup>
<AltAlarmNo>CID4BASE01</AltAlarmNo>
<ConvertType>None</ConvertType>
<SIGINTERPRET>SIADecimal</SIGINTERPRET>
<ClientGroupings>
<ClientGrouping>
<Description>Alarm24</Description>
<GroupingTypeDescription>Alarm24 Tilgang</GroupingTypeDescription>
<GroupingAllowMultiple>True</GroupingAllowMultiple>
</ClientGrouping>
<ClientGrouping>
<Description>Import</Description>
<GroupingTypeDescription>Østfold Sikkerhetsservice AS</GroupingTypeDescription>
<GroupingAllowMultiple>True</GroupingAllowMultiple>
</ClientGrouping>
</ClientGroupings>
```
These are **configured per key** and not exposed as API fields, and can therefore not be changed by the client.
### Auto-added Installer User (UserNo 199)
For each client, the XML will also contain an additional user (not managed via API):
```xml
<User>
<User_Name>Installer Name</User_Name>
<Email>installer@email.com</Email>
<UserNo>199</UserNo>
<CallOrder>0</CallOrder>
<Type>N</Type>
</User>
```
The actual `User_Name` and `Email` come from the API key configuration (`keys.json`).
This is the clients installer company. Make sure the name and e-mail is identical to whats stored in Patriot, otherwise it will generate a new installer on first import.
---
# 📂 API Endpoints
Below are the available endpoints and request/response formats.
> **Note:** All examples assume you are also sending the correct `Authorization: Bearer <API_KEY>` header.
---
## 🔹 CLIENTS
### GET `/clients`
Get full client info (info + zones + users).
Also **regenerates XML** for this client if the XML file is missing (Only when the client is not present in DB, and is pending import).
**Headers:**
```http
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
```
**Response (example):**
```json
{
"client_id": 9998,
"info": { ... },
"zones": { ... },
"users": { ... }
}
```
---
### PUT `/clients`
Create or update a client (**UPSERT**).
- Returns **201 Created** on first creation.
- Returns **200 OK** on subsequent updates.
**Body (all supported fields):**
```json
{
"client_id": 9998,
"info": {
"Name": "Ola Nordmann",
"Alias": "",
"Location": "Produksjonsveien 13",
"area_code": "1604",
"area": "Fredrikstad",
"BusPhone": "69310000",
"Email": "post@ostsik.no",
"OKPassword": "franzjager",
"SpecRequest": "Dette skal gjøres ved alarm på denne kunden.",
"NoSigsMon": 1,
"SinceDays": 1,
"SinceHrs": 0,
"SinceMins": 30,
"ResetNosigsIgnored": true,
"ResetNosigsDays": 7,
"ResetNosigsHrs": 0,
"ResetNosigsMins": 0,
"InstallDateTime": "2023-02-20",
"PanelName": "Ajax",
"PanelSite": "Stue",
"KeypadLocation": "Inngang",
"SPPage": "Ekstra informasjon som kan være relevant."
}
}
```
Some fields may be auto-defaulted if omitted, depending on implementation.
---
### PATCH `/clients`
Partial update of client info. Only the fields inside `info` that you send will be updated; the rest are left unchanged.
**Example: update only the Location:**
```json
{
"client_id": 9998,
"info": {
"Location": "Ny gate 99"
}
}
```
You can also update any combination of supported fields, for example:
```json
{
"client_id": 9998,
"info": {
"BusPhone": "12345678",
"SPPage": "Oppdatert tekst"
}
}
```
---
### DELETE `/clients`
Delete an entire client and its XML file for the current API key.
This does **not** touch clients of other API keys, even if they use the same `client_id`.
**Body:**
```json
{
"client_id": 9998
}
```
Response: **204 No Content** on success.
---
## 🔹 ZONES
### GET `/clients/zones`
List all zones for a client.
**Headers:**
```http
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
```
**Response example:**
```json
{
"client_id": 9998,
"zones": [
{
"Zone_No": 1,
"Zone_area": "Inngang",
"ModuleNo": 0
},
{
"Zone_No": 2,
"Zone_area": "Stue",
"ModuleNo": 0
}
]
}
```
---
### POST `/clients/zones/list`
Alternative way to list zones using JSON body.
**Body:**
```json
{
"client_id": 9998
}
```
---
### PUT `/clients/zones`
Create or update (UPSERT) a zone.
**Body:**
```json
{
"client_id": 9998,
"zone_id": 1,
"Zone_area": "Inngang",
"ModuleNo": 0
}
```
- `zone_id` must be between **1** and **999**.
- Returns **201 Created** for both create and update (by design).
---
### DELETE `/clients/zones`
Delete a specific zone for a client.
**Body:**
```json
{
"client_id": 9998,
"zone_id": 1
}
```
Response: **204 No Content** on success.
---
## 🔹 USERS
### GET `/clients/users`
List all users for a client.
**Headers:**
```http
X-Client-Id: 9998
Authorization: Bearer <API_KEY>
```
**Response example:**
```json
{
"client_id": 9998,
"users": [
{
"User_Name": "User Number 1",
"MobileNo": "+4712345678",
"MobileNoOrder": 1,
"Email": "user@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "Optional",
"CallOrder": 0
}
]
}
```
> Note: The installer user (UserNo 199) is **in XML only** and not exposed via this API.
---
### GET `/clients/users/single`
Get a single user by number.
**Headers:**
```http
X-Client-Id: 9998
X-User-No: 1
Authorization: Bearer <API_KEY>
```
---
### POST `/clients/users/list`
List all users via JSON body.
**Body:**
```json
{
"client_id": 9998
}
```
---
### POST `/clients/users`
Create a new user.
**Body:**
```json
{
"client_id": 9998,
"user": {
"User_Name": "User Number 1",
"MobileNo": "+4712345678",
"MobileNoOrder": 1,
"Email": "user@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "Optional",
"CallOrder": 0
}
}
```
If `UserNo` already exists → **409 Conflict**.
---
### POST `/clients/users/get`
Get one specific user using JSON body.
**Body:**
```json
{
"client_id": 9998,
"user_no": 1
}
```
---
### PUT `/clients/users`
Update/replace a user completely.
**Body:**
```json
{
"client_id": 9998,
"user": {
"User_Name": "Changed Name",
"MobileNo": "+4798765432",
"MobileNoOrder": 1,
"Email": "new@email.com",
"Type": "U",
"UserNo": 1,
"Instructions": "New instructions",
"CallOrder": 0
}
}
```
User is identified by `client_id` + `UserNo`.
---
### DELETE `/clients/users`
Delete a specific user.
**Body:**
```json
{
"client_id": 9998,
"user_no": 1
}
```
Response: **204 No Content** on success.
---
You can run the API as a **systemd service** (recommended) or screen/tmux process.
Copy "main.py" and "run_server.py" to (`/opt/patriot_api/`)
Example systemd unit (`/etc/systemd/system/patriot-api.service`):
```ini
[Unit]
Description=Patriot Client Registry API
After=network.target
[Service]
# Run as this user
User=root
Group=root
WorkingDirectory=/opt/patriot_api
# Environment variables (optional overrides)
#Environment=DATA_FILE=/opt/patriot_api/data.json
Environment=KEY_FILE=/opt/patriot_api/keys.json
Environment=XML_DIR=/opt/patriot_api/out/clients
# Start uvicorn from the venv
ExecStart=/opt/patriot_api/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8081 --proxy-headers
# Restart behavior
Restart=always
RestartSec=3
# Logging (stdout/stderr go to journalctl)
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl daemon-reload
sudo systemctl enable patriot-api.service
sudo systemctl start patriot-api.service
```
Logs will be available both in:
- `/opt/patriot_api/api_requests.log`
- `journalctl -u patriot-api.service -f`
---
# 🔁 Companion Service: XML Combiner & SMB Uploader
In addition to the API, there is a separate Python script that:
1. Scans all per-client XML files from the API:
```text
/opt/patriot_api/out/clients/**/<client_id>.xml
```
2. Combines them into a single `clients.xml` file under:
```text
/opt/patriot_api/ready_for_import/clients.xml
```
3. Uploads this combined `clients.xml` to a Windows SMB share.
4. Tracks import state per client in `client_state.json`.
5. Logs missing or failed imports based on a results file.
The script is designed to run **as a daemon**, sleeping until ~1 minute before the next full hour, then performing a combine/upload run.
## Script Location & Paths
Example paths used in the script:
```python
XML_ROOT_PATH = Path("/opt/patriot_api/out/clients") # per-client XMLs from API
READY_DIR = Path("/opt/patriot_api/ready_for_import") # combined XML output
COMBINED_FILENAME = "clients.xml"
LOG_FILE = "/opt/patriot_api/xml_combine.log"
ERROR_LOG_FILE = "/opt/patriot_api/import_errors.log"
CLIENT_STATE_FILE = Path("/opt/patriot_api/client_state.json")
```
### Client State Tracking
The script maintains a JSON file:
```text
/opt/patriot_api/client_state.json
```
After a successful combine+upload, every client `<__id>` in that batch is marked:
```json
{
"9998BASE11": {
"status": "pending_import",
"last_batch": "2025-01-01T12:55:00"
},
"1234BASE05": {
"status": "pending_import",
"last_batch": "2025-01-01T12:55:00"
}
}
```
This is used for tracking what was included in which batch.
### SMB / Windows Share Upload
The script uploads `clients.xml` to an SMB share using the `pysmb` library.
Key config:
```python
SMB_ENABLED = True
SMB_SERVER_IP = "10.181.149.83"
SMB_SERVER_NAME = "PATRIOT"
SMB_SHARE_NAME = "api_import"
SMB_USERNAME = "administrator"
SMB_PASSWORD = "...."
SMB_DOMAIN = "WORKGROUP"
SMB_REMOTE_PATH = "clients.xml"
SMB_RESULTS_FILENAME = "clients_Import_Results.txt"
```
Upload behavior:
1. **Optional pre-upload check**:
- Attempts to download existing `clients.xml` and `clients_Import_Results.txt` from the share.
- Parses all `<__id>` values from the remote `clients.xml`.
- For each `__id`, checks whether `clients_Import_Results.txt` contains a line with that id and the phrase `"Completed processing client"`.
- Any ids missing a success line are logged into `import_errors.log` with timestamp.
2. **Rotation of existing clients.xml**:
- If `clients.xml` already exists on the share, it is renamed with a timestamp, e.g.:
`clients_20250101_120000.xml`
3. **Upload new clients.xml**:
- The new combined XML is uploaded as `clients.xml`.
4. **Local cleanup**:
- If upload succeeds, the per-client XMLs that were included in this batch are deleted from `out/clients/...`.
- If upload fails, per-client XMLs are **not** deleted, so the batch can be retried later.
### Combining Logic
- The script looks for all `*.xml` files under `XML_ROOT_PATH` (recursively).
- It parses each file and extracts `<Row>` elements with a `<__id>` child.
- Rows are keyed by `__id`; if the same `__id` appears multiple times, the **last one wins** in the combined XML.
- All rows are appended under a single root `<Clients>` element.
- A limit `MAX_CLIENTS_PER_RUN` can be set (default 300).
### Scheduling
The script is written to run in an infinite loop:
```python
if __name__ == "__main__":
main()
```
Where `main()` essentially:
1. Calls `sleep_until_next_run()` → sleeps until ~hh:59 before the next hour.
2. Runs `combine_xml_once()`.
3. Sleeps 1 second as a safety fallback.
4. Repeats.
You can run it as a **systemd service** (recommended) or screen/tmux process.
Example systemd unit (`/etc/systemd/system/patriot-xml-combine.service`):
```ini
[Unit]
Description=Patriot XML Combiner & SMB Uploader
After=network-online.target
[Service]
Type=simple
User=patriot
WorkingDirectory=/opt/patriot_api
ExecStart=/usr/bin/python3 /opt/patriot_api/combine_xml.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl daemon-reload
sudo systemctl enable patriot-xml-combine.service
sudo systemctl start patriot-xml-combine.service
```
Logs will be available both in:
- `/opt/patriot_api/xml_combine.log`
- `journalctl -u patriot-xml-combine.service -f`
---
# ✅ Summary
- The REST API manages **clients, zones, and users**, per API key.
- Each API key has **fully isolated data** and a dedicated XML output folder.
- `<__id>` in XML is generated as `client_id + port` from `keys.json`.
- Extra XML-only fields (installer info, callouts, client groupings, User 199) are injected per key, not via the API.
- A companion script periodically **combines per-client XMLs** into one `clients.xml`, uploads it to a Windows SMB share, and tracks client import state.
This README describes all the endpoints and the companion XML pipeline required for integrating with Patriot.