Files
familienarchiv/.specify/templates/api-contract-stub.md
Marcel 40c1bba113 refactor(sdd): make the feature spec issue-only (no committed spec.md)
The Gitea issue body is the single source of truth for a spec; the only
per-feature artifact in git is the RTM row (REQ-ID -> issue # -> test). Drops
per-feature spec.md/tasks.md/checklist files from the workflow (the _example
stays as a template/reference). Updates the guide, ADR-041, AGENTS.md, CLAUDE.md,
templates, the RTM (adds an Issue column), the implement/review-pr skills, and
replaces the file-spec CI jobs with an rtm-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:14:32 +02:00

4.0 KiB

API Contract Stub

This project is REST + OpenAPI. The backend serves the live spec via springdoc at http://localhost:8080/v3/api-docs (dev profile only), and the frontend generates its TypeScript client from it with npm run generate:api (openapi-typescriptfrontend/src/lib/generated/api.ts). There is no GraphQL in this stack.

The live spec is generated from the Java controllers — it is the source of truth. A hand-written stub is a design artifact: it pins the intended shape during spec review. Issue-only: paste the stub inline into the issue's ## API / Contract Stub section. Keep it OpenAPI 3.1, and keep @Schema(requiredMode = REQUIRED) on the Java side as the real driver of required.

How to use this stub

  1. Fill in the skeleton below with the paths/methods/schemas your feature adds, and paste it into the issue's ## API / Contract Stub section.
  2. Every mutating path documents the 403/401 responses and the cookieAuth security requirement (matching the real @RequirePermission gate).
  3. If you prefer a standalone, lintable file (e.g. for a large contract), commit it on the feature branch as <feature>.openapi.yaml — the sdd-gate.yml CI job lints any committed OpenAPI contract with Spectral (npx @stoplight/spectral-cli lint). It never needs to predate the issue.
  4. After the endpoint ships, run npm run generate:api and diff the generated types against this contract; reconcile any drift (the generated spec wins — update the contract).

OpenAPI 3.1 skeleton

openapi: 3.1.0
info:
  title: Familienarchiv API — <feature name>
  version: 0.0.1-SNAPSHOT
  description: Design-time contract for <feature>. Source of truth is the generated /v3/api-docs.
servers:
  - url: http://localhost:8080
    description: Local backend (dev profile)
  - url: https://archiv.raddatz.cloud
    description: Production (behind Caddy)
components:
  securitySchemes:
    cookieAuth:               # Spring Session JDBC — opaque session id in the SESSION cookie
      type: apiKey
      in: cookie
      name: SESSION
  schemas:
    ErrorResponse:            # shape produced by GlobalExceptionHandler
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          description: Machine-readable ErrorCode (see ErrorCode.java / errors.ts).
          example: FORBIDDEN
        message:
          type: string
    # <YourResponseView>:     # always a view, never a lazy-collection entity (ADR-036)
    #   type: object
    #   required: [id]
    #   properties:
    #     id: { type: string, format: uuid }
security:
  - cookieAuth: []            # default: every path requires a session unless overridden to []
paths:
  /api/<resource>:
    post:
      summary: <create …>
      operationId: <createResource>
      security:
        - cookieAuth: []      # plus @RequirePermission(Permission.X) on the controller
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/<CreateDTO>' }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/<YourResponseView>' }
        '400': { description: Validation failed, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
        '401': { description: Unauthenticated, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
        '403': { description: Missing permission, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }

Validating the contract in CI

The sdd-gate.yml workflow runs, on PRs that touch a api-contract.yaml:

npx @stoplight/spectral-cli lint .specify/features/**/api-contract.yaml

Spectral's default OpenAPI ruleset catches malformed specs, missing operationIds, and undefined $refs. Add a .spectral.yaml at the repo root to tune rules if needed.