# 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-typescript` →
`frontend/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

```yaml
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` `contract-validate` job lints any committed OpenAPI file changed in the PR:

```bash
npx @stoplight/spectral-cli lint <your-contract>.yaml
```

The ruleset is `.spectral.yaml` at the repo root (extends `spectral:oas`; documentation-only
warnings relaxed for design-time stubs). Spectral auto-discovers it. It catches malformed
specs, undefined `$ref`s, and duplicate `operationId`s; tune `.spectral.yaml` to adjust.
