Ctrl K
ComunidadGuías…

openapi.yaml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
openapi: 3.0.3
info:
  title: Meistertracker API
  version: 1.0.0
  description: REST API for the Meistertracker mushroom lab management system.
servers:
  - url: https://localhost:3000
    description: Local development (HTTPS)
  - url: http://localhost:3000
    description: Local development (HTTP fallback)

components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: session
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
    Ok:
      type: object
      properties:
        ok:
          type: boolean
    MushroomStrain:
      type: object
      required: [id, name, kuerzel, created]
      properties:
        id:
          type: integer
          description: Primary key
        name:
          type: string
          description: Full strain name (unique)
        kuerzel:
          type: string
          description: Short code (unique)
        description:
          type: string
        created:
          type: string
          format: date-time
        updated:
          type: string
          format: date-time
          nullable: true
    Stamm:
      type: object
      required: [id, strainId, name, created]
      properties:
        id:
          type: integer
        strainId:
          type: integer
          description: The Pilzsorte it belongs to
        name:
          type: string
          description: Unique per Pilzsorte, case-insensitive
        notes:
          type: string
        created:
          type: string
          format: date-time
        updated:
          type: string
          format: date-time
          nullable: true

security:
  - cookieAuth: []

paths:
  /api/auth/setup-required:
    get:
      summary: Check if initial setup is needed
      security: []
      responses:
        '200':
          description: Setup status
          content:
            application/json:
              schema:
                type: object
                properties:
                  setupRequired:
                    type: boolean

  /api/auth/setup:
    post:
      summary: Initial admin account setup
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
                  minLength: 8
      responses:
        '200':
          description: Setup complete
        '400':
          description: Validation error

  /api/auth/login:
    post:
      summary: User login
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
      responses:
        '200':
          description: Login successful, session cookie set
        '401':
          description: Invalid credentials

  /api/auth/logout:
    post:
      summary: User logout
      responses:
        '200':
          description: Session invalidated

  /api/auth/me:
    get:
      summary: Get current user info
      responses:
        '200':
          description: Current user
          content:
            application/json:
              schema:
                type: object
                properties:
                  username:
                    type: string
                  role:
                    type: string

  /api/health:
    get:
      summary: Server health check
      security: []
      responses:
        '200':
          description: Healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [ok, degraded]
                  db:
                    type: string
                  uptime:
                    type: integer
                  version:
                    type: string
                  sseClients:
                    type: integer
                  memory:
                    type: object
                    properties:
                      rss:
                        type: integer
                      heapUsed:
                        type: integer
                      heapTotal:
                        type: integer
        '503':
          description: Database unreachable

  /api/data:
    get:
      summary: Fetch all application data
      responses:
        '200':
          description: Full data snapshot
    post:
      summary: Full-state save (backup/restore only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Data saved

  /api/events:
    get:
      summary: Server-Sent Events stream for real-time sync
      responses:
        '200':
          description: SSE stream (text/event-stream)

  /api/mushroom-strains:
    get:
      summary: List all Pilzsorten (mushroom strains)
      responses:
        '200':
          description: Array of strains
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MushroomStrain'
    post:
      summary: Create a new Pilzsorte
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, kuerzel]
              properties:
                name:
                  type: string
                  description: Must be unique
                kuerzel:
                  type: string
                  description: Short code, must be unique
                description:
                  type: string
      responses:
        '200':
          description: Strain created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
        '400':
          description: Validation error (missing fields or unique violation)

  /api/mushroom-strains/{id}:
    patch:
      summary: Update a Pilzsorte
      description: >-
        Changes to `name` or `kuerzel` cascade into all batches and cultures
        that reference this strain (their `species`/`strain` columns are
        updated in a single transaction).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                kuerzel:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: Strain updated
        '400':
          description: Unique violation
    delete:
      summary: Delete a Pilzsorte (admin only)
      description: >-
        Fails with 400 when the strain is still referenced by any batch or
        culture. Delete or reassign dependent rows first.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Strain deleted
        '400':
          description: Strain still in use
        '403':
          description: Admin role required

  /api/staemme:
    get:
      summary: List all Stämme (strains / genetic lines) of every Pilzsorte
      description: >-
        The vocabulary for the strain field on batches and cultures. A batch or
        culture stores the chosen strain by NAME in `strainText`, not by id, so
        rows from before this list existed keep their free text. Also part of
        `/api/data` as `staemme`.
      responses:
        '200':
          description: Array of strains
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Stamm'
    post:
      summary: Add a Stamm to a Pilzsorte
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [strainId, name]
              properties:
                strainId:
                  type: integer
                  description: The Pilzsorte (mushroom_strains.id) the strain belongs to
                name:
                  type: string
                  maxLength: 60
                  description: >-
                    Unique per Pilzsorte, case-insensitive. Surrounding and
                    repeated whitespace is removed.
                notes:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: Strain created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
        '400':
          description: >-
            Validation error — missing fields, unknown Pilzsorte, or
            "Stamm: already exists"

  /api/staemme/{id}:
    patch:
      summary: Rename a Stamm or change its notes
      description: >-
        A new `name` is carried into the `strainText` of every batch and
        culture of the same Pilzsorte that carried the old name
        (case-insensitive), in one transaction.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 60
                notes:
                  type: string
                  maxLength: 500
      responses:
        '200':
          description: Strain updated
        '400':
          description: 'Empty name, unknown id, or "Stamm: already exists"'
    delete:
      summary: Remove a Stamm from the list (admin only)
      description: >-
        Only removes it from the selection. Batches and cultures that carry the
        name keep it.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Strain removed
        '403':
          description: Admin role required

  /api/batches:
    post:
      summary: Create a new batch
      description: >-
        Either `strainId` or `species` must be supplied. When `strainId` is set,
        `species` and `strain` are auto-filled from the referenced Pilzsorte
        and any values passed in the request body are overwritten.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [batchId, qty, days, created, due]
              properties:
                batchId:
                  type: string
                strainId:
                  type: integer
                  description: Pilzsorte id — auto-fills species/strain from mushroom_strains
                species:
                  type: string
                  description: Required when strainId is omitted
                strain:
                  type: string
                  description: Kuerzel (free-text fallback when strainId is omitted)
                qty:
                  type: integer
                days:
                  type: integer
                bags:
                  type: array
                  items:
                    type: string
                created:
                  type: string
                  format: date-time
                due:
                  type: string
                  format: date-time
      responses:
        '200':
          description: Batch created

  /api/batches/{batchId}:
    patch:
      summary: Update batch fields
      description: >-
        Passing `strainId` switches the batch to a different Pilzsorte and
        cascades `species`/`strain` from the mushroom_strains row.
      parameters:
        - name: batchId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                notes:
                  type: string
                strainId:
                  type: integer
                species:
                  type: string
                strain:
                  type: string
                days:
                  type: integer
                due:
                  type: string
      responses:
        '200':
          description: Batch updated
    delete:
      summary: Delete a batch (cascades bags)
      parameters:
        - name: batchId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Batch deleted

  /api/batches/{batchId}/bags:
    patch:
      summary: Add bags to an existing batch
      parameters:
        - name: batchId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                add:
                  type: array
                  items:
                    type: string
                newQty:
                  type: integer
                bagKg:
                  type: number
                  description: >-
                    Weight of each added bag in kg. Omit to reuse the batch's own
                    weight. Grain spawn is weighed and one batch may hold more
                    than one size, so the added bags need not match the rest.
                  minimum: 0
                  exclusiveMinimum: true
                  maximum: 1000
      responses:
        '200':
          description: Bags added

  /api/scan-log:
    post:
      summary: Append scan log entries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                entries:
                  type: array
                  items:
                    type: object
                    properties:
                      time:
                        type: string
                      action:
                        type: string
                        enum: [ADD, MOVE, REMOVE, HARVEST]
                      batch:
                        type: string
                      bag:
                        type: string
                      from:
                        type: string
                      to:
                        type: string
      responses:
        '200':
          description: Entries added
          content:
            application/json:
              schema:
                type: object
                properties:
                  ids:
                    type: array
                    items:
                      type: integer
    delete:
      summary: Clear all scan log entries
      responses:
        '200':
          description: Scan log cleared

  /api/scan-log/{id}:
    delete:
      summary: Delete a single scan log entry
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Entry deleted

  /api/harvests:
    post:
      summary: Log a harvest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [time, grams]
              properties:
                time:
                  type: string
                  format: date-time
                batch:
                  type: string
                bag:
                  type: string
                species:
                  type: string
                grams:
                  type: number
                flush:
                  type: integer
      responses:
        '200':
          description: Harvest logged

  /api/block-book:
    get:
      summary: Blocks marked good or commented, each with its lineage
      description: >-
        Lineage (substrate mix with target and measured moisture, grain spawn
        with its hydration) is read live while the batch exists, otherwise the
        snapshot taken when the block was first marked is returned (`live: false`).
      responses:
        '200':
          description: '{ entries: [...] }'

  /api/block-notes/good:
    post:
      summary: Mark a block good, or clear the mark
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bag]
              properties:
                bag:
                  type: string
                on:
                  type: boolean
                  default: true
      responses:
        '200':
          description: Saved
        '404':
          description: Unknown bag

  /api/block-notes/comments:
    post:
      summary: Add a comment to a block
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [bag, text]
              properties:
                bag:
                  type: string
                text:
                  type: string
                  maxLength: 2000
                flush:
                  type: integer
                  description: Set when the comment was written while harvesting
      responses:
        '200':
          description: '{ ok, id }'
        '404':
          description: Unknown bag

  /api/block-notes/comments/{id}:
    delete:
      summary: Delete a comment (its author or an admin)
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Deleted
        '403':
          description: Not the author and not an admin

  /api/cultures:
    post:
      summary: Create cultures
      description: >-
        Pass `strainId` on each culture to auto-fill `species`/`strain` from
        the referenced Pilzsorte. Free-text `species`/`strain` are only used
        when `strainId` is omitted.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                cultures:
                  type: array
                  items:
                    type: object
                    required: [id, type]
                    properties:
                      id:
                        type: string
                      type:
                        type: string
                        enum: [MC, PD, LC, G2G]
                      strainId:
                        type: integer
                        description: Pilzsorte id (auto-fills species/strain)
                      species:
                        type: string
                      strain:
                        type: string
                      parentId:
                        type: string
                        nullable: true
                      source:
                        type: string
                        nullable: true
                      status:
                        type: string
                        enum: [active, stored, used, contam]
                      notes:
                        type: string
                      created:
                        type: string
                        format: date-time
      responses:
        '200':
          description: Cultures created

  /api/cultures/{id}:
    patch:
      summary: Update a culture
      description: >-
        Passing `strainId` switches the culture to a different Pilzsorte and
        cascades `species`/`strain` from the mushroom_strains row.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [active, stored, used, contam]
                notes:
                  type: string
                strainId:
                  type: integer
                species:
                  type: string
                strain:
                  type: string
                source:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Culture updated

  /api/tasks:
    post:
      summary: Create a task
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text, created]
              properties:
                text:
                  type: string
                priority:
                  type: string
                  enum: [low, med, high]
                created:
                  type: string
                  format: date-time
                assignee:
                  type: string
                dueDate:
                  type: string
                  format: date
      responses:
        '200':
          description: Task created

  /api/tasks/{id}:
    patch:
      summary: Update a task
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                done:
                  type: boolean
                text:
                  type: string
                priority:
                  type: string
                assignee:
                  type: string
                dueDate:
                  type: string
      responses:
        '200':
          description: Task updated
    delete:
      summary: Delete a task
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Task deleted

  /api/team:
    post:
      summary: Add a team member
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, added]
              properties:
                name:
                  type: string
                role:
                  type: string
                added:
                  type: string
                  format: date-time
      responses:
        '200':
          description: Member added

  /api/team/{id}:
    delete:
      summary: Remove a team member
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Member removed

  /api/inventory/delta:
    post:
      summary: Adjust inventory stock by delta
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mat, deltaKg]
              properties:
                mat:
                  type: string
                  enum: [hardwood, wheatbran, gypsum, grain]
                deltaKg:
                  type: number
                type:
                  type: string
                ref:
                  type: string
      responses:
        '200':
          description: Stock updated

  /api/inventory/set:
    post:
      summary: Set absolute inventory stock level
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mat, value]
              properties:
                mat:
                  type: string
                  enum: [hardwood, wheatbran, gypsum, grain]
                value:
                  type: number
      responses:
        '200':
          description: Stock set

  /api/inventory/config:
    post:
      summary: Update inventory thresholds and avg composition
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                thresholds:
                  type: object
                avgComposition:
                  type: object
      responses:
        '200':
          description: Config updated

  /api/calendar-events:
    post:
      summary: Create a calendar event
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id, title, startDate]
              properties:
                id:
                  type: string
                title:
                  type: string
                startDate:
                  type: string
                  format: date
                endDate:
                  type: string
                  format: date
                allDay:
                  type: boolean
                category:
                  type: string
                assignees:
                  type: array
                  items:
                    type: integer
      responses:
        '200':
          description: Event created

  /api/calendar-events/{id}:
    patch:
      summary: Update a calendar event
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Event updated
    delete:
      summary: Delete a calendar event
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Event deleted

  /api/backup/download:
    post:
      summary: Download encrypted database backup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password:
                  type: string
                  minLength: 8
      responses:
        '200':
          description: Encrypted backup file (AES-256-GCM)
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  /api/backup/restore:
    post:
      summary: Restore database from encrypted backup
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Restore successful
        '401':
          description: Wrong password

  /api/usb-backup:
    get:
      summary: USB stick backup status (admin)
      description: >
        Registered sticks with their last result (with `skipped`,
        `unreadable` and `missing` when photos on the PC could not be read),
        sticks plugged in now (`sameDisk` marks a stick folder on the app's
        own disk, which is never backed up to), the run in progress, and
        `reminder` — one of running, state, setup, first, error, gaps,
        remove, due or null — which the start screen shows. Beside it,
        `keyConfirmed` (false shows a line asking to put the recovery key
        away), `firstRunPending` (a connected stick waits for "Back up now"),
        `stateError` (data/usb-backup.json cannot be read) and `enabled`
        (false with USB_BACKUP=off). Changes are announced as the SSE event
        `usb-backup`.
      responses:
        '200':
          description: Status
        '403':
          description: Not an admin

  /api/usb-backup/drives:
    get:
      summary: Drives a stick could be set up on (admin)
      responses:
        '200':
          description: Drives with free space, whether they are the app's own disk, and any stick on them

  /api/usb-backup/setup:
    post:
      summary: Set up the stick in a drive and back up onto it (admin)
      description: >
        The first stick creates the recovery key; later sticks use the same
        key. The key is returned with every setup until it is confirmed as
        put away (POST /api/usb-backup/key/confirm), so a first setup that
        failed half-way cannot lose it (Cache-Control no-store).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [root]
              properties:
                root:
                  type: string
                  description: A drive from /api/usb-backup/drives
                label:
                  type: string
                  maxLength: 40
      responses:
        '200':
          description: >
            Stick set up and a backup started. recoveryKey is set until the key
            is confirmed; newKey says whether this setup created it.
        '400':
          description: Not one of the drives (code NOT_A_DRIVE)
        '409':
          description: >
            Refused, with a code (SAME_DISK, NEEDS_KEY, WRONG_KEY, BUSY, FULL,
            READ_ONLY, STATE when this PC's record cannot be read or saved …)

  /api/usb-backup/connect:
    post:
      summary: Take back a stick from an earlier installation with its recovery key (admin)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [root, recoveryKey]
              properties:
                root:
                  type: string
                recoveryKey:
                  type: string
      responses:
        '200':
          description: >
            Connected. Nothing is written to the stick until someone starts the
            first backup with POST /api/usb-backup/run (restore first on a new
            PC); after that it is backed up every day by itself.
        '409':
          description: Refused (HAS_KEY, WRONG_KEY, STATE …)

  /api/usb-backup/run:
    post:
      summary: Back up onto a plugged-in stick now (admin)
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: string
                  description: The stick to back up onto; without it, the first registered stick plugged in
                force:
                  type: boolean
                  description: >
                    true only for "Back up anyway" after a SHRUNK stop: the run
                    goes ahead although it finds far less than last time
      responses:
        '200':
          description: The run that started
        '409':
          description: >
            No registered stick plugged in (NOT_PRESENT), only a stick folder
            on the app's own disk (SAME_DISK), or a run in progress (BUSY)

  /api/usb-backup/key:
    post:
      summary: Show the recovery key (admin, Cache-Control no-store)
      responses:
        '200':
          description: The key

  /api/usb-backup/key/confirm:
    post:
      summary: Confirm the recovery key is put away (admin)
      description: >
        Stops the key being returned with every setup and the start-screen
        line asking for it. Saved, or not made.
      responses:
        '200':
          description: Confirmed
        '409':
          description: No recovery key yet, or the confirmation could not be saved (code STATE)

  /api/usb-backup/sticks/{id}:
    delete:
      summary: Forget a lost or retired stick (admin); nothing on the stick is touched
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            pattern: '^[0-9a-f]{16}$'
      responses:
        '200':
          description: Forgotten
        '404':
          description: Unknown stick
        '409':
          description: Not forgotten, because this PC's record could not be saved (code STATE)

  /api/users:
    get:
      summary: List all users
      responses:
        '200':
          description: User list (without password hashes)
    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                password:
                  type: string
                  minLength: 8
                role:
                  type: string
                  enum: [admin, user]
      responses:
        '200':
          description: User created

  /api/users/{id}:
    delete:
      summary: Delete a user
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: User deleted

  /api/usernames:
    get:
      summary: Get all usernames (lightweight)
      responses:
        '200':
          description: Username list

  /api/print:
    post:
      summary: Send ZPL to label printer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [zpl]
              properties:
                zpl:
                  type: string
      responses:
        '200':
          description: Print job sent

  /api/caldav/config:
    post:
      summary: Update CalDAV configuration
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
      responses:
        '200':
          description: Config updated