Ver Fonte

updating openapi

Medowar há 1 mês atrás
pai
commit
b0dbd91239
5 ficheiros alterados com 11 adições e 754 exclusões
  1. 4 4
      README.md
  2. 1 1
      api-docs/index.html
  3. 2 2
      docs/API.md
  4. 4 4
      docs/DEPLOYMENT.md
  5. 0 743
      openapi.yaml

+ 4 - 4
README.md

@@ -41,7 +41,7 @@ Der Datenfluss ist einfach:
 
 Die Anwendung ist jetzt so aufgebaut, dass der gesamte Projektordner direkt als Apache-Unterordner deployt werden kann.
 
-- `index.php`, `admin/`, `api/`, `api-docs/`, `app.js`, `styles.css`, `openapi.yaml`
+- `index.php`, `admin/`, `api/`, `api-docs/`, `app.js`, `styles.css`, `api-docs/openapi.yaml`
   - öffentliche Runtime-Dateien
 - `src/`
   - interne PHP-Kernlogik
@@ -61,7 +61,7 @@ Wichtige Dateien:
 - `api/v1/readings.php`: API für eingehende Sensorwerte
 - `api/v1/status.php`: Status-API für Dashboard und Adminpanel
 - `api-docs/index.html`: Swagger UI
-- `openapi.yaml`: OpenAPI-Spec
+- `api-docs/openapi.yaml`: OpenAPI-Spec
 - `src/MonitorService.php`: zentrale Orchestrierung für Lesen, Berechnen und Status
 - `src/InventoryService.php`: Berechnung von Füllgrad und Bestand
 - `src/AlertService.php`: Webhook- und Email-Alarmierung
@@ -96,7 +96,7 @@ Danach ist die Anwendung erreichbar unter:
 - Adminpanel: `http://localhost:8000/admin/`
 - Status-API: `http://localhost:8000/api/v1/status.php`
 - Swagger UI: `http://localhost:8000/api-docs/`
-- OpenAPI-Spec: `http://localhost:8000/openapi.yaml`
+- OpenAPI-Spec: `http://localhost:8000/api-docs/openapi.yaml`
 
 Hinweis: Der PHP Built-in Server wertet `.htaccess` nicht aus. Die Verzeichnisse `src/`, `data/` und `docs/` werden auf Shared Hosting erst durch Apache geschützt.
 
@@ -186,7 +186,7 @@ Damit repräsentiert `distance_per_unit` die Änderung des Messwerts pro Flasche
 Die API ist jetzt auf drei Ebenen dokumentiert:
 
 - Interaktive Swagger UI unter `http://localhost:8000/api-docs/`
-- Maschinenlesbare OpenAPI-Spec unter `http://localhost:8000/openapi.yaml`
+- Maschinenlesbare OpenAPI-Spec unter `http://localhost:8000/api-docs/openapi.yaml`
 - Erläuternde Referenz in [docs/API.md](docs/API.md)
 
 ### `POST /api/v1/readings.php`

+ 1 - 1
api-docs/index.html

@@ -34,7 +34,7 @@
   <script>
     window.addEventListener('load', function () {
       SwaggerUIBundle({
-        url: '../openapi.yaml',
+        url: 'openapi.yaml',
         dom_id: '#swagger-ui',
         deepLinking: true,
         docExpansion: 'list',

+ 2 - 2
docs/API.md

@@ -7,12 +7,12 @@ Diese Datei beschreibt die HTTP-Schnittstellen der Anwendung.
 Zusätzlich zur textuellen Referenz gibt es jetzt zwei API-Dokumentationsziele in der laufenden Anwendung:
 
 - Swagger UI: `/api-docs/`
-- OpenAPI-Spec: `/openapi.yaml`
+- OpenAPI-Spec: `/api-docs/openapi.yaml`
 
 Beim lokalen Start mit `php -S localhost:8000` sind die URLs:
 
 - `http://localhost:8000/api-docs/`
-- `http://localhost:8000/openapi.yaml`
+- `http://localhost:8000/api-docs/openapi.yaml`
 
 Die OpenAPI-Datei ist handgeschrieben und dient als maschinenlesbare Quelle für die Swagger UI.
 

+ 4 - 4
docs/DEPLOYMENT.md

@@ -38,7 +38,7 @@ Beispiel für eine Installation in einem Unterordner:
     ├── index.php
     ├── app.js
     ├── styles.css
-    ├── openapi.yaml
+    ├── api-docs/openapi.yaml
     ├── admin/
     ├── api/
     ├── api-docs/
@@ -59,7 +59,7 @@ Beispiel für eine Installation in einem Unterordner:
 
 Wichtig:
 
-- `index.php`, `admin/`, `api/`, `api-docs/`, `app.js`, `styles.css` und `openapi.yaml` sind öffentlich
+- `index.php`, `admin/`, `api/`, `api-docs/`, `app.js`, `styles.css` und `api-docs/openapi.yaml` sind öffentlich
 - `src/`, `data/` und `docs/` liegen im selben Ordner, werden aber durch mitgelieferte `.htaccess`-Dateien blockiert
 - `README.md` und Dotfiles werden ebenfalls über die Root-`.htaccess` geblockt
 
@@ -82,7 +82,7 @@ Dann sind die wichtigsten URLs:
 - Status-API: `https://example.com/monitor/api/v1/status.php`
 - Readings-API: `https://example.com/monitor/api/v1/readings.php`
 - Swagger UI: `https://example.com/monitor/api-docs/`
-- OpenAPI-Spec: `https://example.com/monitor/openapi.yaml`
+- OpenAPI-Spec: `https://example.com/monitor/api-docs/openapi.yaml`
 
 Wenn du den Inhalt direkt in den Domain-Root legst, bleibt `base_path` leer.
 
@@ -192,7 +192,7 @@ Diese Checks sollten direkt funktionieren:
 2. `https://example.com/monitor/admin/` zeigt den Login.
 3. `https://example.com/monitor/api/v1/status.php` liefert JSON.
 4. `https://example.com/monitor/api-docs/` zeigt Swagger UI.
-5. `https://example.com/monitor/openapi.yaml` ist abrufbar.
+5. `https://example.com/monitor/api-docs/openapi.yaml` ist abrufbar.
 
 Diese Pfade dürfen nicht öffentlich lesbar sein:
 

+ 0 - 743
openapi.yaml

@@ -1,743 +0,0 @@
-openapi: 3.1.0
-info:
-  title: Getränkeautomat Monitor API
-  version: 1.0.0
-  summary: HTTP API for ingesting sensor readings and retrieving vending machine status.
-  description: |
-    Handwritten OpenAPI specification for the Getränkeautomat Monitor application.
-
-    The API currently exposes two endpoints:
-    - `POST /api/v1/readings.php` for ingesting one sensor measurement
-    - `GET /api/v1/status.php` for retrieving the aggregated application state
-
-    The readings endpoint uses Bearer token authentication. The status endpoint is
-    intentionally public so the dashboard can poll it without a login.
-servers:
-  - url: ./
-    description: Same-origin deployment
-tags:
-  - name: Readings
-    description: Receive one sensor reading and update the persisted slot state.
-  - name: Status
-    description: Retrieve the current aggregated machine, slot, and alert status.
-paths:
-  /api/v1/readings.php:
-    post:
-      tags:
-        - Readings
-      summary: Submit one reading
-      description: |
-        Accepts exactly one sensor measurement for one configured machine slot.
-
-        Processing behavior:
-        - Requires a Bearer token in the `Authorization` header
-        - Accepts JSON request bodies
-        - Updates `data/state.json` on success
-        - Treats `distance_mm: -1` as a client-side measurement error, keeps
-          the last good slot value, and stores input-error metadata for the UI
-        - Triggers alerts only when the slot state changes from `ok` to `critical`
-          or from `critical` to `ok`
-      operationId: submitReading
-      security:
-        - bearerAuth: []
-      requestBody:
-        required: true
-        content:
-          application/json:
-            schema:
-              $ref: '#/components/schemas/ReadingRequest'
-            examples:
-              lobbySlot:
-                summary: Reading for the A1 slot in the lobby machine
-                value:
-                  machine_id: automat-lobby
-                  sensor_id: fach-a1
-                  distance_mm: 184
-                  measured_at: '2026-04-15T19:20:00Z'
-              clientError:
-                summary: Client-side measurement error for the A1 slot
-                value:
-                  machine_id: automat-lobby
-                  sensor_id: fach-a1
-                  distance_mm: -1
-                  measured_at: '2026-04-15T19:22:09Z'
-      responses:
-        '200':
-          description: Reading processed successfully.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ReadingSuccessResponse'
-              examples:
-                ok:
-                  value:
-                    ok: true
-                    machine_id: automat-lobby
-                    sensor_id: fach-a1
-                    slot_label: A1
-                    units_estimated: 4
-                    fill_percent: 63
-                    state: ok
-                clientError:
-                  value:
-                    ok: true
-                    machine_id: automat-lobby
-                    sensor_id: fach-a1
-                    slot_label: A1
-                    units_estimated: 4
-                    fill_percent: 63
-                    state: ok
-                    input_error:
-                      received_at: '2026-04-15T19:22:10+00:00'
-                      measured_at: '2026-04-15T19:22:09+00:00'
-                      reported_value: -1
-                      message: Client meldete einen Messfehler.
-                      last_good:
-                        distance_mm: 184
-                        fill_percent: 63
-                        units_estimated: 4
-                        max_units: 7
-                        state: ok
-                        measured_at: '2026-04-15T19:20:00+00:00'
-        '400':
-          description: Invalid JSON body or malformed request payload.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                invalidJson:
-                  value:
-                    ok: false
-                    error: Ungültiger JSON-Body.
-        '401':
-          description: Missing or invalid Bearer token.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                unauthorized:
-                  value:
-                    ok: false
-                    error: Nicht autorisiert.
-        '404':
-          description: The referenced machine or sensor is not configured.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                unknownMachineOrSensor:
-                  value:
-                    ok: false
-                    error: Unbekannter Automat oder Sensor.
-        '405':
-          description: Only POST is supported for this endpoint.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                wrongMethod:
-                  value:
-                    ok: false
-                    error: Nur POST ist erlaubt.
-        '422':
-          description: Semantic validation failed.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                missingIdentifiers:
-                  value:
-                    ok: false
-                    error: machine_id und sensor_id sind erforderlich.
-                nonNumericDistance:
-                  value:
-                    ok: false
-                    error: distance_mm muss numerisch sein.
-                invalidTimestamp:
-                  value:
-                    ok: false
-                    error: measured_at ist kein gültiger ISO-Zeitstempel.
-        '500':
-          description: Unexpected internal server error.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                internalError:
-                  value:
-                    ok: false
-                    error: Interner Fehler.
-    options:
-      tags:
-        - Readings
-      summary: CORS preflight for readings
-      description: |
-        Preflight handler for browser-based clients. The endpoint responds with
-        `204 No Content` and emits `Access-Control-Allow-Methods` and
-        `Access-Control-Allow-Headers`.
-      operationId: readingsPreflight
-      responses:
-        '204':
-          description: Preflight accepted without a response body.
-          headers:
-            Access-Control-Allow-Methods:
-              description: Allowed methods for this endpoint.
-              schema:
-                type: string
-                example: POST, OPTIONS
-            Access-Control-Allow-Headers:
-              description: Allowed request headers for this endpoint.
-              schema:
-                type: string
-                example: Authorization, Content-Type
-  /api/v1/status.php:
-    get:
-      tags:
-        - Status
-      summary: Retrieve the current application status
-      description: |
-        Returns the aggregated state for the dashboard and admin panel.
-
-        The response contains:
-        - app metadata
-        - machine and slot status
-        - a summary section
-        - the most recent alert log entries
-      operationId: getStatus
-      responses:
-        '200':
-          description: Aggregated status generated successfully.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/StatusResponse'
-              examples:
-                dashboard:
-                  value:
-                    ok: true
-                    generated_at: '2026-04-15T20:10:00+00:00'
-                    app:
-                      name: Getränkeautomat Monitor
-                      dashboard_refresh_seconds: 15
-                    summary:
-                      machine_count: 2
-                      slot_count: 3
-                      critical_count: 1
-                    machines:
-                      - id: automat-lobby
-                        name: Lobby Automat
-                        location: Erdgeschoss
-                        slots:
-                          - machine_id: automat-lobby
-                            machine_name: Lobby Automat
-                            sensor_id: fach-a1
-                            slot_label: A1
-                            product_name: Cola 0,5l
-                            fill_percent: 63
-                            units_estimated: 4
-                            max_units: 7
-                            distance_mm: 184
-                            state: ok
-                            measured_at: '2026-04-15T19:20:00+00:00'
-                            updated_at: '2026-04-15T19:20:02+00:00'
-                            alert_below_units: 2
-                    alerts:
-                      - id: alert_680004979d8512.07480974
-                        created_at: '2026-04-15T19:20:02+00:00'
-                        payload:
-                          event: critical
-                          machine_id: automat-lobby
-                          machine_name: Lobby Automat
-                          sensor_id: fach-a1
-                          slot_label: A1
-                          product_name: Cola 0,5l
-                          distance_mm: 320
-                          units_estimated: 1
-                          max_units: 7
-                          fill_percent: 14
-                          state: critical
-                          previous_state: ok
-                          measured_at: '2026-04-15T19:19:59+00:00'
-                        deliveries:
-                          webhooks:
-                            - label: lager-webhook
-                              success: false
-                              message: Webhook nicht gefunden oder deaktiviert.
-                          emails:
-                            - label: lager-team
-                              success: true
-                              message: Email versendet.
-        '405':
-          description: Only GET is supported for this endpoint.
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorResponse'
-              examples:
-                wrongMethod:
-                  value:
-                    ok: false
-                    error: Nur GET ist erlaubt.
-components:
-  securitySchemes:
-    bearerAuth:
-      type: http
-      scheme: bearer
-      bearerFormat: opaque token
-      description: Bearer token stored in config.json under api.bearer_token.
-  schemas:
-    ReadingRequest:
-      type: object
-      additionalProperties: false
-      required:
-        - machine_id
-        - sensor_id
-        - distance_mm
-      properties:
-        machine_id:
-          type: string
-          description: Configured machine identifier from config.json.
-          minLength: 1
-          example: automat-lobby
-        sensor_id:
-          type: string
-          description: Slot or sensor identifier within the machine.
-          minLength: 1
-          example: fach-a1
-        distance_mm:
-          type: number
-          description: Measured distance in millimeters. The sentinel value `-1` reports a client-side measurement error and does not replace the last good slot value.
-          example: 184
-        measured_at:
-          type:
-            - string
-            - 'null'
-          description: |
-            Optional measurement timestamp. When omitted or empty, the server uses
-            the current time. Values are parsed with PHP `strtotime()` and returned
-            as an ISO-8601 timestamp.
-          format: date-time
-          example: '2026-04-15T19:20:00Z'
-    ReadingSuccessResponse:
-      type: object
-      additionalProperties: false
-      required:
-        - ok
-        - machine_id
-        - sensor_id
-        - slot_label
-        - units_estimated
-        - fill_percent
-        - state
-      properties:
-        ok:
-          type: boolean
-          const: true
-        machine_id:
-          type: string
-          example: automat-lobby
-        sensor_id:
-          type: string
-          example: fach-a1
-        slot_label:
-          type: string
-          example: A1
-        units_estimated:
-          type:
-            - integer
-            - 'null'
-          example: 4
-        fill_percent:
-          type:
-            - integer
-            - 'null'
-          example: 63
-        state:
-          $ref: '#/components/schemas/SlotState'
-        input_error:
-          oneOf:
-            - $ref: '#/components/schemas/InputError'
-            - type: 'null'
-    ErrorResponse:
-      type: object
-      additionalProperties: false
-      required:
-        - ok
-        - error
-      properties:
-        ok:
-          type: boolean
-          const: false
-        error:
-          type: string
-          example: Nicht autorisiert.
-    StatusResponse:
-      type: object
-      additionalProperties: false
-      required:
-        - ok
-        - generated_at
-        - app
-        - summary
-        - machines
-        - alerts
-      properties:
-        ok:
-          type: boolean
-          const: true
-        generated_at:
-          type: string
-          format: date-time
-          description: Timestamp when the response was generated.
-        app:
-          $ref: '#/components/schemas/AppStatus'
-        summary:
-          $ref: '#/components/schemas/StatusSummary'
-        machines:
-          type: array
-          items:
-            $ref: '#/components/schemas/MachineStatus'
-        alerts:
-          type: array
-          items:
-            $ref: '#/components/schemas/AlertEvent'
-    AppStatus:
-      type: object
-      additionalProperties: false
-      required:
-        - name
-        - dashboard_refresh_seconds
-      properties:
-        name:
-          type: string
-          example: Getränkeautomat Monitor
-        dashboard_refresh_seconds:
-          type: integer
-          minimum: 1
-          example: 15
-    StatusSummary:
-      type: object
-      additionalProperties: false
-      required:
-        - machine_count
-        - slot_count
-        - critical_count
-      properties:
-        machine_count:
-          type: integer
-          minimum: 0
-          example: 2
-        slot_count:
-          type: integer
-          minimum: 0
-          example: 3
-        critical_count:
-          type: integer
-          minimum: 0
-          example: 1
-    MachineStatus:
-      type: object
-      additionalProperties: false
-      required:
-        - id
-        - name
-        - location
-        - slots
-      properties:
-        id:
-          type: string
-          example: automat-lobby
-        name:
-          type: string
-          example: Lobby Automat
-        location:
-          type: string
-          example: Erdgeschoss
-        slots:
-          type: array
-          items:
-            $ref: '#/components/schemas/SlotStatus'
-    SlotStatus:
-      type: object
-      additionalProperties: false
-      required:
-        - machine_id
-        - machine_name
-        - sensor_id
-        - slot_label
-        - product_name
-        - fill_percent
-        - units_estimated
-        - max_units
-        - distance_mm
-        - state
-        - measured_at
-        - updated_at
-        - alert_below_units
-      properties:
-        machine_id:
-          type: string
-          example: automat-lobby
-        machine_name:
-          type: string
-          example: Lobby Automat
-        sensor_id:
-          type: string
-          example: fach-a1
-        slot_label:
-          type: string
-          example: A1
-        product_name:
-          type: string
-          example: Cola 0,5l
-        fill_percent:
-          type:
-            - integer
-            - 'null'
-          minimum: 0
-          maximum: 100
-          description: Null until a first reading has been received.
-          example: 63
-        units_estimated:
-          type:
-            - integer
-            - 'null'
-          minimum: 0
-          description: Null until a first reading has been received.
-          example: 4
-        max_units:
-          type: integer
-          minimum: 0
-          example: 7
-        distance_mm:
-          type:
-            - number
-            - 'null'
-          description: Last measured distance in millimeters.
-          example: 184
-        state:
-          $ref: '#/components/schemas/SlotState'
-        measured_at:
-          type:
-            - string
-            - 'null'
-          format: date-time
-          example: '2026-04-15T19:20:00+00:00'
-        updated_at:
-          type:
-            - string
-            - 'null'
-          format: date-time
-          example: '2026-04-15T19:20:02+00:00'
-        alert_below_units:
-          type: integer
-          minimum: 0
-          example: 2
-        input_error:
-          oneOf:
-            - $ref: '#/components/schemas/InputError'
-            - type: 'null'
-          description: Present when the most recent submitted value for this slot was `-1`.
-    InputError:
-      type: object
-      additionalProperties: false
-      required:
-        - received_at
-        - measured_at
-        - reported_value
-        - message
-        - last_good
-      properties:
-        received_at:
-          type: string
-          format: date-time
-          description: Server time when the `-1` value was received.
-          example: '2026-04-15T19:22:10+00:00'
-        measured_at:
-          type: string
-          format: date-time
-          description: Timestamp supplied by the client for the failed reading, or the server time when omitted.
-          example: '2026-04-15T19:22:09+00:00'
-        reported_value:
-          type: integer
-          const: -1
-        message:
-          type: string
-          example: Client meldete einen Messfehler.
-        last_good:
-          oneOf:
-            - $ref: '#/components/schemas/LastGoodReading'
-            - type: 'null'
-          description: Last successfully calculated slot data, if any exists.
-    LastGoodReading:
-      type: object
-      additionalProperties: false
-      required:
-        - distance_mm
-        - fill_percent
-        - units_estimated
-        - max_units
-        - state
-        - measured_at
-      properties:
-        distance_mm:
-          type: number
-          example: 184
-        fill_percent:
-          type: integer
-          minimum: 0
-          maximum: 100
-          example: 63
-        units_estimated:
-          type: integer
-          minimum: 0
-          example: 4
-        max_units:
-          type: integer
-          minimum: 0
-          example: 7
-        state:
-          $ref: '#/components/schemas/SlotState'
-        measured_at:
-          type: string
-          format: date-time
-          example: '2026-04-15T19:20:00+00:00'
-    SlotState:
-      type: string
-      enum:
-        - ok
-        - critical
-        - unknown
-      example: ok
-    AlertEvent:
-      type: object
-      additionalProperties: false
-      required:
-        - id
-        - created_at
-        - payload
-        - deliveries
-      properties:
-        id:
-          type: string
-          description: Unique alert log entry ID generated with PHP uniqid().
-          example: alert_680004979d8512.07480974
-        created_at:
-          type: string
-          format: date-time
-          example: '2026-04-15T19:20:02+00:00'
-        payload:
-          $ref: '#/components/schemas/AlertPayload'
-        deliveries:
-          $ref: '#/components/schemas/AlertDeliveries'
-    AlertPayload:
-      type: object
-      additionalProperties: false
-      required:
-        - event
-        - machine_id
-        - machine_name
-        - sensor_id
-        - slot_label
-        - product_name
-        - distance_mm
-        - units_estimated
-        - max_units
-        - fill_percent
-        - state
-        - previous_state
-        - measured_at
-      properties:
-        event:
-          type: string
-          enum:
-            - critical
-            - recovered
-          example: critical
-        machine_id:
-          type: string
-          example: automat-lobby
-        machine_name:
-          type: string
-          example: Lobby Automat
-        sensor_id:
-          type: string
-          example: fach-a1
-        slot_label:
-          type: string
-          example: A1
-        product_name:
-          type: string
-          example: Cola 0,5l
-        distance_mm:
-          type:
-            - number
-            - 'null'
-          example: 320
-        units_estimated:
-          type:
-            - integer
-            - 'null'
-          example: 1
-        max_units:
-          type:
-            - integer
-            - 'null'
-          example: 7
-        fill_percent:
-          type:
-            - integer
-            - 'null'
-          example: 14
-        state:
-          $ref: '#/components/schemas/SlotState'
-        previous_state:
-          type:
-            - string
-            - 'null'
-          description: Previous slot state before the transition.
-          example: ok
-        measured_at:
-          type:
-            - string
-            - 'null'
-          format: date-time
-          example: '2026-04-15T19:19:59+00:00'
-    AlertDeliveries:
-      type: object
-      additionalProperties: false
-      required:
-        - webhooks
-        - emails
-      properties:
-        webhooks:
-          type: array
-          items:
-            $ref: '#/components/schemas/DeliveryResult'
-        emails:
-          type: array
-          items:
-            $ref: '#/components/schemas/DeliveryResult'
-    DeliveryResult:
-      type: object
-      additionalProperties: false
-      required:
-        - id
-        - success
-        - message
-      properties:
-        id:
-          type: string
-          example: lager-team
-        success:
-          type: boolean
-          example: true
-        message:
-          type: string
-          example: Email versendet.