feat(sdd): add .specify scaffold — constitution, AGENTS, personas, templates, example, RTM
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>
This commit is contained in:
136
.specify/features/_example/api-contract.yaml
Normal file
136
.specify/features/_example/api-contract.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
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' }
|
||||
Reference in New Issue
Block a user