Introduces the SDD root: a v1.0.0 constitution and machine-readable AGENTS.md grounded in the project's real conventions; six EARS-aware persona spec-review checklists that cross-reference .claude/personas/; feature-spec/ADR/threat-model/ api-contract templates; a fully worked _example feature; a living RTM; and an adrs/ pointer that reuses the existing docs/adr/ archive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
4.5 KiB
YAML
137 lines
4.5 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: Familienarchiv API — Profile picture upload
|
|
version: 0.0.1-SNAPSHOT
|
|
description: >
|
|
Design-time contract for the avatar feature (.specify/features/_example).
|
|
Source of truth once shipped 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:
|
|
type: apiKey
|
|
in: cookie
|
|
name: SESSION
|
|
schemas:
|
|
ErrorResponse:
|
|
type: object
|
|
required: [code, message]
|
|
properties:
|
|
code:
|
|
type: string
|
|
example: AVATAR_TOO_LARGE
|
|
message:
|
|
type: string
|
|
UserProfileView:
|
|
type: object
|
|
required: [id, displayName]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
displayName:
|
|
type: string
|
|
avatarUrl:
|
|
type: [string, "null"]
|
|
description: Authenticated proxy path (/api/users/{id}/avatar) when an avatar exists, else null.
|
|
example: /api/users/3f1c.../avatar
|
|
security:
|
|
- cookieAuth: []
|
|
paths:
|
|
/api/users/me/avatar:
|
|
post:
|
|
summary: Upload or replace the current user's avatar
|
|
operationId: uploadMyAvatar
|
|
security:
|
|
- cookieAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
description: PNG or JPEG, max 2 MB.
|
|
responses:
|
|
'200':
|
|
description: Avatar stored; updated profile returned.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/UserProfileView' }
|
|
'400':
|
|
description: Unsupported type (UNSUPPORTED_FILE_TYPE) or too large (AVATAR_TOO_LARGE).
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
'401':
|
|
description: Unauthenticated (UNAUTHORIZED).
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
delete:
|
|
summary: Remove the current user's avatar
|
|
operationId: deleteMyAvatar
|
|
security:
|
|
- cookieAuth: []
|
|
responses:
|
|
'200':
|
|
description: Avatar removed; profile returned with avatarUrl null.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/UserProfileView' }
|
|
'401':
|
|
description: Unauthenticated (UNAUTHORIZED).
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
/api/users/{id}/avatar:
|
|
get:
|
|
summary: Stream a user's avatar image (authenticated proxy)
|
|
operationId: getUserAvatar
|
|
security:
|
|
- cookieAuth: []
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
'200':
|
|
description: Image bytes.
|
|
content:
|
|
image/png: { schema: { type: string, format: binary } }
|
|
image/jpeg: { schema: { type: string, format: binary } }
|
|
'401': { description: Unauthenticated, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
'404': { description: User has no avatar, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
delete:
|
|
summary: Remove another user's avatar (admin only)
|
|
operationId: deleteUserAvatar
|
|
description: Requires Permission.ADMIN_USER (enforced by @RequirePermission on the controller).
|
|
security:
|
|
- cookieAuth: []
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
responses:
|
|
'200':
|
|
description: Avatar removed.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/UserProfileView' }
|
|
'401': { description: Unauthenticated, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }
|
|
'403':
|
|
description: Caller lacks ADMIN_USER (FORBIDDEN).
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|