177 lines
5.1 KiB
Markdown
177 lines
5.1 KiB
Markdown
# VOTIANLT Realtime & Photo Upload API
|
||
|
||
This document describes how mobile/Flutter apps can interact with the backend via STOMP (WebSocket) and how to upload photos via HTTP POST (new flow).
|
||
|
||
## 1) STOMP Overview
|
||
|
||
- Connect to one of the WebSocket endpoints:
|
||
- `/ws` (SockJS fallback supported)
|
||
- `/websocket` (native WebSocket)
|
||
- `/stomp` (native WebSocket, alternate path)
|
||
- Application destination prefix: `/app`
|
||
- Broker destinations:
|
||
- Broadcast: `/topic/...`
|
||
- User specific: `/user/queue/...`
|
||
|
||
Examples:
|
||
- Send a generic message: send to `/app/message`, receive on `/topic/messages`
|
||
- Job updates: send to `/app/job/status`, receive on `/topic/job-updates`
|
||
- Device locations: send to `/app/device/location`, receive on `/topic/device-locations`
|
||
- Task updates (broadcast): `/topic/task-updates`
|
||
- Task specific topic: `/topic/tasks/{taskId}`
|
||
|
||
## 2) Photo Task – New HTTP Upload Flow
|
||
|
||
In the future, photos for a PHOTO task must NOT be embedded in the STOMP message anymore. Instead, upload the photos via HTTP POST and only send the task-completion event via STOMP without large payloads.
|
||
|
||
### Endpoint
|
||
|
||
- URL: `POST /api/tasks/{taskId}/photos`
|
||
- Purpose: Upload one or many photos for a task of type `PHOTO`.
|
||
- Path params:
|
||
- `taskId` (required): The MongoDB ObjectId of the task.
|
||
- Auth: (same as your app uses today; if you have a session/cookie or token, include it accordingly)
|
||
- CORS: Enabled for `*` by default on this controller.
|
||
|
||
### Content Types (choose one)
|
||
|
||
1) Multipart form data (recommended for binary images)
|
||
|
||
- `Content-Type: multipart/form-data`
|
||
- Fields:
|
||
- `files`: one or multiple image files (repeat the field for multiple images)
|
||
- `completedBy` (optional): username/id of the completing user
|
||
- `note` (optional): any note (currently stored on the task when you mark it as completed via STOMP)
|
||
|
||
Example (curl):
|
||
|
||
```bash
|
||
curl -X POST "http://localhost:8080/api/tasks/66f5c2f1a3b6e27a1a234567/photos" \
|
||
-H "Content-Type: multipart/form-data" \
|
||
-F "files=@/path/to/photo1.jpg" \
|
||
-F "files=@/path/to/photo2.jpg" \
|
||
-F "completedBy=driver01" \
|
||
-F "note=Anlieferung dokumentiert"
|
||
```
|
||
|
||
2) JSON with base64 strings (kept for compatibility)
|
||
|
||
- `Content-Type: application/json`
|
||
- Body:
|
||
|
||
```json
|
||
{
|
||
"completedBy": "driver01",
|
||
"note": "Anlieferung dokumentiert",
|
||
"photos": [
|
||
"<base64-of-image-1>",
|
||
"<base64-of-image-2>"
|
||
]
|
||
}
|
||
```
|
||
|
||
Example (curl):
|
||
|
||
```bash
|
||
curl -X POST "http://localhost:8080/api/tasks/66f5c2f1a3b6e27a1a234567/photos" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"completedBy":"driver01",
|
||
"note":"Anlieferung dokumentiert",
|
||
"photos":["iVBORw0KGgo...","iVBORw0KGgo..."]
|
||
}'
|
||
```
|
||
|
||
### Simple JSON endpoint (single photo per call)
|
||
|
||
- URL: `POST /api/photos`
|
||
- Content-Type: `application/json`
|
||
- Body:
|
||
|
||
```json
|
||
{
|
||
"taskId": "66f5c2f1a3b6e27a1a234567",
|
||
"photo": "<base64-of-image>",
|
||
"completedBy": "driver01",
|
||
"note": "optional"
|
||
}
|
||
```
|
||
|
||
- Multiple photos: call this endpoint multiple times (one image per request).
|
||
|
||
Example (curl):
|
||
|
||
```bash
|
||
curl -X POST "http://localhost:8080/api/photos" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"taskId":"66f5c2f1a3b6e27a1a234567",
|
||
"photo":"iVBORw0KGgo...",
|
||
"completedBy":"driver01",
|
||
"note":"Anlieferung dokumentiert"
|
||
}'
|
||
```
|
||
|
||
### Response
|
||
|
||
Both content types return the same JSON structure on success:
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2025-09-13T21:27:00",
|
||
"type": "photoUploadAck",
|
||
"success": true,
|
||
"photoId": "66f5c400b1a1b05b1c123abc",
|
||
"photosCount": 2,
|
||
"taskId": "66f5c2f1a3b6e27a1a234567",
|
||
"jobId": "66f5c2f1a3b6e27a1a200000"
|
||
}
|
||
```
|
||
|
||
Error responses use HTTP status codes with:
|
||
|
||
```json
|
||
{ "timestamp": "...", "type": "photoUploadAck", "success": false, "message": "..." }
|
||
```
|
||
|
||
### Server-side side-effect (notification)
|
||
|
||
After a successful upload, a small event is broadcast to the specific task topic:
|
||
|
||
- Destination: `/topic/tasks/{taskId}`
|
||
- Payload:
|
||
|
||
```json
|
||
{ "event": "photoUploaded", "taskId": "...", "jobId": "...", "photosCount": 2, "timestamp": "..." }
|
||
```
|
||
|
||
Use this to update UI (e.g., show that photos have arrived) without transferring large images via STOMP.
|
||
|
||
### Limits
|
||
|
||
- Multipart limits (configurable in `application.properties`):
|
||
- `spring.servlet.multipart.max-file-size=32MB`
|
||
- `spring.servlet.multipart.max-request-size=64MB`
|
||
- Tomcat limits adjusted accordingly.
|
||
|
||
If you need higher limits, we can raise these values.
|
||
|
||
## 3) Marking Task as Completed (STOMP)
|
||
|
||
Continue to mark a task as completed via STOMP without embedding images:
|
||
|
||
- Send to: `/app/task/photo/completed`
|
||
- Payload (no photos inside):
|
||
|
||
```json
|
||
{ "taskId": "66f5c2f1a3b6e27a1a234567", "completedBy": "driver01", "note": "Anlieferung dokumentiert" }
|
||
```
|
||
|
||
- Broadcasts an update to `/topic/task-updates` and `/topic/tasks/{taskId}`.
|
||
|
||
## 4) Retrieval of Photos (optional)
|
||
|
||
Currently, the backend stores uploaded photos as base64 in the `photos` collection referencing the `taskId` and `jobId`. If you need a download/list endpoint, let us know—we can expose `/api/tasks/{taskId}/photos` (GET) to return metadata and/or images.
|
||
|
||
---
|
||
If anything is unclear or you need different formats (e.g., presigned URLs, chunked uploads), please reach out. |