From 152d5b82cd68b98bd880c679ce697ba20f62f7c8 Mon Sep 17 00:00:00 2001 From: Kristjan Komlosi Date: Wed, 14 Jan 2026 14:49:57 +0100 Subject: [PATCH] feat: update DiscoveryMessage struct and add API documentation --- API.md | 1024 +++++++++++++++++++++++++++++++++ internal/handler/discovery.go | 6 +- 2 files changed, 1027 insertions(+), 3 deletions(-) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..dfb682f --- /dev/null +++ b/API.md @@ -0,0 +1,1024 @@ +# Lambda IoT Core API Documentation + +## Overview + +The Lambda IoT Core API provides a RESTful interface for managing IoT devices, sensors, actors, and sensor readings. The API uses JWT authentication for protected endpoints and integrates with MQTT for real-time device communication. + +**Base URL**: `http://localhost:8080` (default) + +## Authentication + +Most endpoints require JWT authentication. Include the token in the `Authorization` header: + +``` +Authorization: Bearer +``` + +Tokens are obtained through the `/login` endpoint and are valid for 24 hours. + +--- + +## Public Endpoints + +### Health Check + +Check the service health status. + +**Endpoint**: `GET /health` + +**Authentication**: None required + +**Response**: +```json +{ + "status": "ok" +} +``` + +--- + +### Hello + +Simple greeting endpoint for testing connectivity. + +**Endpoint**: `GET /hello` + +**Authentication**: None required + +**Response**: +```json +{ + "message": "hello from lambdaiot" +} +``` + +--- + +### Register + +Create a new user account. + +**Endpoint**: `POST /register` + +**Authentication**: None required + +**Request Body**: +```json +{ + "username": "string (required, min: 3, max: 50)", + "password": "string (required, min: 6)", + "email": "string (optional)" +} +``` + +**Response** (201 Created): +```json +{ + "id": "uuid", + "username": "string" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid request format +- `409 Conflict`: Username already exists +- `500 Internal Server Error`: Database or server error + +--- + +### Login + +Authenticate and receive a JWT token. + +**Endpoint**: `POST /login` + +**Authentication**: None required + +**Request Body**: +```json +{ + "username": "string", + "password": "string" +} +``` + +**Response** (200 OK): +```json +{ + "token": "jwt-token-string" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid JSON +- `401 Unauthorized`: Invalid credentials +- `500 Internal Server Error`: Database or token generation error + +--- + +### List All Devices + +Retrieve all devices in the system (public read access). + +**Endpoint**: `GET /devices` + +**Authentication**: None required + +**Response** (200 OK): +```json +[ + { + "id": "uuid", + "name": "string", + "description": "string", + "location": "string", + "status_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" + } +] +``` + +**Status IDs**: +- `1`: OK/Online +- `2`: Warning +- `3`: Error +- `4`: Offline + +--- + +## Protected Endpoints + +All endpoints below require JWT authentication. + +### Protected Test + +Test authentication and view token claims. + +**Endpoint**: `GET /protected` + +**Authentication**: Required + +**Response** (200 OK): +```json +{ + "claims": { + "sub": "username", + "user_id": "uuid", + "exp": 1705324800 + } +} +``` + +--- + +## Device Endpoints + +### Create Device + +Create a new device. + +**Endpoint**: `POST /devices` + +**Authentication**: Required + +**Request Body**: +```json +{ + "name": "string (required)", + "description": "string (required)", + "location": "string (required)", + "status_id": 1 (required, range: 1-4) +} +``` + +**Response** (201 Created): +```json +{ + "id": "uuid" +} +``` + +--- + +### Get Device + +Retrieve a specific device by ID. + +**Endpoint**: `GET /devices/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Device UUID + +**Response** (200 OK): +```json +{ + "id": "uuid", + "name": "string", + "description": "string", + "location": "string", + "status_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid device ID +- `404 Not Found`: Device not found + +--- + +### Update Device + +Update an existing device. + +**Endpoint**: `PUT /devices/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Device UUID + +**Request Body** (all fields optional): +```json +{ + "name": "string", + "description": "string", + "location": "string", + "status_id": 1 +} +``` + +**Response** (200 OK): +```json +{ + "message": "device updated" +} +``` + +--- + +### Delete Device + +Delete a device. + +**Endpoint**: `DELETE /devices/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Device UUID + +**Response** (200 OK): +```json +{ + "message": "device deleted" +} +``` + +--- + +## Sensor Endpoints + +### Create Sensor + +Create a new sensor for a device. + +**Endpoint**: `POST /sensors` + +**Authentication**: Required + +**Request Body**: +```json +{ + "device_id": "uuid (required)", + "name": "string (required)", + "type": "string (required)", + "data_type_id": 1 (required, min: 1) +} +``` + +**Response** (201 Created): +```json +{ + "id": "uuid" +} +``` + +--- + +### List All Sensors + +Retrieve all sensors. + +**Endpoint**: `GET /sensors` + +**Authentication**: Required + +**Response** (200 OK): +```json +[ + { + "id": "uuid", + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" + } +] +``` + +--- + +### Get Sensor + +Retrieve a specific sensor by ID. + +**Endpoint**: `GET /sensors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Sensor UUID + +**Response** (200 OK): +```json +{ + "id": "uuid", + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid sensor ID +- `404 Not Found`: Sensor not found + +--- + +### Update Sensor + +Update an existing sensor. + +**Endpoint**: `PUT /sensors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Sensor UUID + +**Request Body** (all fields optional): +```json +{ + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1 +} +``` + +**Response** (200 OK): +```json +{ + "message": "sensor updated" +} +``` + +--- + +### Delete Sensor + +Delete a sensor. + +**Endpoint**: `DELETE /sensors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Sensor UUID + +**Response** (200 OK): +```json +{ + "message": "sensor deleted" +} +``` + +--- + +### Trigger Sensor + +Request a new reading from a sensor via MQTT. + +**Endpoint**: `POST /sensors/:id/trigger` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Sensor UUID + +**Request Body** (optional): +```json +{ + "action": "string (default: 'read')" +} +``` + +**Response** (202 Accepted): +```json +{ + "status": "published", + "topic": "lambdaiot" +} +``` + +**MQTT Message Published**: +```json +{ + "type": "sensor_trigger", + "sensor_id": "uuid", + "action": "read", + "requested_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid sensor ID +- `404 Not Found`: Sensor not found +- `500 Internal Server Error`: Failed to publish to MQTT + +--- + +## Actor Endpoints + +### Create Actor + +Create a new actor for a device. + +**Endpoint**: `POST /actors` + +**Authentication**: Required + +**Request Body**: +```json +{ + "device_id": "uuid (required)", + "name": "string (required)", + "type": "string (required)", + "data_type_id": 1 (required, min: 1) +} +``` + +**Response** (201 Created): +```json +{ + "id": "uuid" +} +``` + +--- + +### List All Actors + +Retrieve all actors. + +**Endpoint**: `GET /actors` + +**Authentication**: Required + +**Response** (200 OK): +```json +[ + { + "id": "uuid", + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" + } +] +``` + +--- + +### Get Actor + +Retrieve a specific actor by ID. + +**Endpoint**: `GET /actors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Actor UUID + +**Response** (200 OK): +```json +{ + "id": "uuid", + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1, + "created_at": "2026-01-14T10:00:00Z", + "updated_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid actor ID +- `404 Not Found`: Actor not found + +--- + +### Update Actor + +Update an existing actor. + +**Endpoint**: `PUT /actors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Actor UUID + +**Request Body** (all fields optional): +```json +{ + "device_id": "uuid", + "name": "string", + "type": "string", + "data_type_id": 1 +} +``` + +**Response** (200 OK): +```json +{ + "message": "actor updated" +} +``` + +--- + +### Delete Actor + +Delete an actor. + +**Endpoint**: `DELETE /actors/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Actor UUID + +**Response** (200 OK): +```json +{ + "message": "actor deleted" +} +``` + +--- + +### Write to Actor + +Send a command to an actor via MQTT. + +**Endpoint**: `POST /actors/:id/write` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Actor UUID + +**Request Body**: +```json +{ + "action": "string (required)", + "value": "any JSON value (optional)" +} +``` + +**Response** (202 Accepted): +```json +{ + "status": "published", + "topic": "lambdaiot" +} +``` + +**MQTT Message Published**: +```json +{ + "type": "actor_command", + "actor_id": "uuid", + "action": "string", + "value": "any", + "requested_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid actor ID or missing action +- `404 Not Found`: Actor not found +- `500 Internal Server Error`: Failed to publish to MQTT + +--- + +## Sensor Reading Endpoints + +### Create Sensor Reading + +Create a new sensor reading. + +**Endpoint**: `POST /sensor-readings` + +**Authentication**: Required + +**Request Body**: +```json +{ + "sensor_id": "uuid (required)", + "value": 123.45 (required), + "value_at": "2026-01-14T10:00:00Z (optional, defaults to now)" +} +``` + +**Response** (201 Created): +```json +{ + "id": 123 +} +``` + +--- + +### List Sensor Readings + +Retrieve sensor readings with optional filtering and pagination. + +**Endpoint**: `GET /sensor-readings` + +**Authentication**: Required + +**Query Parameters**: +- `sensor_id`: Filter by sensor UUID (optional) +- `limit`: Number of results (default: 100, max: 1000) +- `page`: Page number for pagination (default: 0) + +**Response** (200 OK): +```json +[ + { + "id": 123, + "sensor_id": "uuid", + "value": 123.45, + "value_at": "2026-01-14T10:00:00Z" + } +] +``` + +--- + +### Get Sensor Reading + +Retrieve a specific sensor reading by ID. + +**Endpoint**: `GET /sensor-readings/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Reading ID (integer) + +**Response** (200 OK): +```json +{ + "id": 123, + "sensor_id": "uuid", + "value": 123.45, + "value_at": "2026-01-14T10:00:00Z" +} +``` + +**Error Responses**: +- `400 Bad Request`: Invalid reading ID +- `404 Not Found`: Reading not found + +--- + +### Update Sensor Reading + +Update an existing sensor reading. + +**Endpoint**: `PUT /sensor-readings/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Reading ID (integer) + +**Request Body** (all fields optional): +```json +{ + "value": 123.45, + "value_at": "2026-01-14T10:00:00Z" +} +``` + +**Response** (200 OK): +```json +{ + "message": "sensor reading updated" +} +``` + +--- + +### Delete Sensor Reading + +Delete a sensor reading. + +**Endpoint**: `DELETE /sensor-readings/:id` + +**Authentication**: Required + +**Path Parameters**: +- `id`: Reading ID (integer) + +**Response** (200 OK): +```json +{ + "message": "sensor reading deleted" +} +``` + +--- + +## MQTT & System Endpoints + +### MQTT Ping + +Publish a ping message to the MQTT broker for testing. + +**Endpoint**: `GET /mqttping` + +**Authentication**: Required + +**Response** (200 OK): +```json +{ + "timestamp": "2026-01-14T10:00:00Z", + "topic": "lambdaiot" +} +``` + +**MQTT Message Published**: +```json +{ + "type": "ping", + "timestamp": "2026-01-14T10:00:00Z" +} +``` + +--- + +### Get State Messages + +Retrieve stored state messages from SQLite storage. + +**Endpoint**: `GET /messages` + +**Authentication**: Required + +**Query Parameters**: +- `page`: Page number (default: 0) + +**Response** (200 OK): +```json +{ + "messages": [ + { + "id": 1, + "payload": "message content", + "timestamp": "2026-01-14T10:00:00Z" + } + ] +} +``` + +--- + +## MQTT Integration + +### Device Discovery + +Devices can announce themselves by publishing to the discovery topic: + +**Topic**: `{configured_mqtt_topic}/discovery` + +**Message Format**: +```json +{ + "mac_address": "00:11:22:33:44:55", + "device": { + "id": "uuid", + "name": "Device Name", + "description": "Device Description", + "location": "Device Location", + "status_id": 1 + }, + "sensors": [ + { + "id": "uuid", + "name": "Temperature Sensor", + "type": "temperature", + "data_type_id": 1 + } + ], + "actors": [ + { + "id": "uuid", + "name": "LED Actuator", + "type": "led", + "data_type_id": 2 + } + ] +} +``` + +**Behavior**: +- New devices are automatically created with all sensors and actors +- Existing devices are updated, and sensors/actors are synced +- Device status is set to OK (1) upon discovery + +--- + +## Error Responses + +All endpoints may return the following standard error responses: + +### 400 Bad Request +```json +{ + "error": "descriptive error message" +} +``` + +### 401 Unauthorized +```json +{ + "error": "invalid credentials" // or "no claims" +} +``` + +### 404 Not Found +```json +{ + "error": "resource not found" +} +``` + +### 409 Conflict +```json +{ + "error": "resource already exists" +} +``` + +### 500 Internal Server Error +```json +{ + "error": "descriptive error message" +} +``` + +--- + +## Data Types + +### Device Status IDs + +| ID | Status | Description | +|----|---------|--------------------------------| +| 1 | OK | Device is online and functioning | +| 2 | Warning | Device has warnings | +| 3 | Error | Device has errors | +| 4 | Offline | Device is offline | + +### Date/Time Format + +All timestamps use RFC3339 format: `2026-01-14T10:00:00Z` + +### UUIDs + +UUIDs follow the standard UUID v4 format: `550e8400-e29b-41d4-a716-446655440000` + +--- + +## Configuration + +The server uses a configuration file (typically `config.toml`) with the following structure: + +```toml +[server] +address = ":8080" +port = 8080 +jwt_secret = "your-secret-key" + +[database] +host = "localhost" +port = 3306 +user = "root" +password = "password" +name = "lambdaiot" + +[mqtt] +broker = "tcp://localhost:1883" +topic = "lambdaiot" +client_id = "lambdaiot-core" +username = "" +password = "" +``` + +--- + +## Rate Limiting & Best Practices + +1. **Pagination**: Use `limit` and `page` parameters for large result sets +2. **MQTT Topics**: All MQTT messages are published to the configured topic +3. **Token Expiry**: JWT tokens expire after 24 hours - refresh as needed +4. **UUID Format**: Always use valid UUID v4 format for device, sensor, and actor IDs +5. **Date Format**: Use RFC3339 format for all timestamp fields +6. **Device Discovery**: Devices can self-register via MQTT discovery messages +7. **State Messages**: Messages on topics starting with `state:` are automatically stored + +--- + +## Example Workflows + +### Complete Device Setup Workflow + +1. Register a user: + ```bash + POST /register + {"username": "admin", "password": "secret123"} + ``` + +2. Login and get token: + ```bash + POST /login + {"username": "admin", "password": "secret123"} + ``` + +3. Create a device: + ```bash + POST /devices (with JWT) + {"name": "Room Sensor", "description": "Living room", "location": "Floor 1", "status_id": 1} + ``` + +4. Create a sensor: + ```bash + POST /sensors (with JWT) + {"device_id": "device-uuid", "name": "Temperature", "type": "temp", "data_type_id": 1} + ``` + +5. Trigger a reading: + ```bash + POST /sensors/{sensor-id}/trigger (with JWT) + {"action": "read"} + ``` + +6. Create a reading: + ```bash + POST /sensor-readings (with JWT) + {"sensor_id": "sensor-uuid", "value": 22.5} + ``` + +### Query Readings for a Specific Sensor + +```bash +GET /sensor-readings?sensor_id={uuid}&limit=50&page=0 (with JWT) +``` + +--- + +## Support & Documentation + +For more information about device discovery specifications, see [DEVICE_DISCOVERY_SPEC.md](DEVICE_DISCOVERY_SPEC.md). + +--- + +*Generated: January 14, 2026* diff --git a/internal/handler/discovery.go b/internal/handler/discovery.go index a2da935..38d566c 100644 --- a/internal/handler/discovery.go +++ b/internal/handler/discovery.go @@ -9,8 +9,8 @@ import ( // DiscoveryMessage represents the JSON payload from a device discovery message type DiscoveryMessage struct { - MACAddress string `json:"mac_address"` - Device DiscoveryDevicePayload `json:"device"` + MACAddress string `json:"mac_address"` + Device DiscoveryDevicePayload `json:"device"` Sensors []DiscoverySensorPayload `json:"sensors"` Actors []DiscoveryActorPayload `json:"actors"` } @@ -162,7 +162,7 @@ func (h *Handler) updateExistingDevice(msg DiscoveryMessage, previousStatusID in return nil } -// getDeviceSensors retrieves existing sensors for a device +// getDeviceSensors retrieves existing sensors for a device< func getDeviceSensors(tx *sql.Tx, deviceID string) (map[string]DiscoverySensorPayload, error) { rows, err := tx.Query( "SELECT BIN_TO_UUID(id), name, type, data_type_id FROM sensors WHERE device_id = UUID_TO_BIN(?) ORDER BY id",