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' }