docs(memoro/server): add OpenAPI 3.1 spec and update ManaScore to 79

- Add openapi.yaml with all 50+ endpoints, schemas, and auth methods
- Update ManaScore: 76→79 (testing 45→55, documentation 78→82)
- 210 tests total (main-server 185 + audio-server 25)
- API conformity: documentation now true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-01 16:31:44 +02:00
parent c582f164ba
commit 9aedc89ce5
2 changed files with 943 additions and 19 deletions

View file

@ -6,14 +6,14 @@ app: 'memoro'
author: 'Till Schneider'
tags: ['audit', 'memoro', 'production-readiness', 'voice-memos', 'ai']
score: 76
score: 79
scores:
backend: 82
frontend: 78
database: 65
testing: 45
testing: 55
deployment: 70
documentation: 78
documentation: 82
security: 75
ux: 65
@ -25,8 +25,8 @@ stats:
webRoutes: 16
components: 79
dbTables: 8
testFiles: 11
testCount: 183
testFiles: 13
testCount: 210
languages: 5
linesOfCode: 140971
sourceFiles: 801
@ -59,7 +59,7 @@ apiConformity:
errorCodes: true
pagination: true
versioning: true
documentation: false
documentation: true
healthEndpoint: true
validation: true
@ -133,21 +133,19 @@ Memoro ist eine KI-gestützte Sprachnotiz-App mit Web (SvelteKit), Mobile (Expo)
- Kein Schema-Versionierung im Repo
- Local-First Migration noch nicht vollständig (Hybrid-Ansatz)
## Testing (45/100)
## Testing (55/100)
**Stärken:**
- Vitest konfiguriert mit 11 Test-Dateien
- 183 ausführbare Tests (alle bestehend)
- 59 Zod-Schema-Validierungstests für alle API-Schemas
- 124 API-Route-Tests mit gemocktem Auth, Supabase, AI und Credits
- Abdeckung aller Server-Endpoints: Memos, Spaces, Credits, Settings, Meetings, Internal, Cleanup
- Utility-Tests (calcTranscriptionCost, COSTS)
- Vitest konfiguriert mit 13 Test-Dateien, 210 ausführbare Tests
- **Main-Server (185 Tests):** 59 Schema + 126 API-Route-Tests, alle Endpoints abgedeckt
- **Audio-Server (25 Tests):** Health, Auth, Transcribe/Append-Validation, Azure-Config
- Abdeckung: Memos, Spaces, Credits, Settings, Meetings, Internal, Cleanup
- Utility-Tests (calcTranscriptionCost, COSTS, Azure pickRandomService)
- Test-Setup mit Environment-Isolation
**Lücken:**
- Keine Tests für Audio-Server (Port 3016)
- Keine Integration-Tests für Transkriptions-Pipeline (End-to-End)
- Keine E2E-Tests für Web-App
- Kein Coverage-Reporting
@ -169,7 +167,7 @@ Memoro ist eine KI-gestützte Sprachnotiz-App mit Web (SvelteKit), Mobile (Expo)
- Kein Staging-Environment
- ~~App als archived markiert~~ ✅ Status: published, requiredTier: 'founder'
## Documentation (78/100)
## Documentation (82/100)
**Stärken:**
@ -177,10 +175,10 @@ Memoro ist eine KI-gestützte Sprachnotiz-App mit Web (SvelteKit), Mobile (Expo)
- README.md mit 374 Zeilen: Installation, Setup, Stack-Übersicht
- Web-App README mit Features und Deployment-Optionen
- Gut dokumentierte Umgebungsvariablen mit .env.example
- ~~Keine API-Dokumentation~~ ✅ OpenAPI 3.1 Spec mit allen 50+ Endpoints, Schemas, Auth-Methoden
**Lücken:**
- Keine API-Dokumentation (OpenAPI/Swagger)
- Keine Architektur-Diagramme
- Audio-Server-Dokumentation fehlt
@ -231,8 +229,8 @@ Memoro ist eine KI-gestützte Sprachnotiz-App mit Web (SvelteKit), Mobile (Expo)
## Nächste Empfehlungen
1. ~~**Tests schreiben (Testing 10→45)**~~ ✅ 183 Tests: 59 Schema + 124 API-Route-Tests
2. **Audio-Server Tests** — Transkriptions-Pipeline, Whisper-Integration testen
3. **OpenAPI-Dokumentation** — Swagger/OpenAPI-Spec für alle Endpoints generieren
1. ~~**Tests schreiben (Testing 10→55)**~~ ✅ 210 Tests: Server (185) + Audio-Server (25)
2. ~~**Audio-Server Tests**~~ ✅ 25 Tests: Health, Auth, Transcribe, Azure-Config
3. ~~**OpenAPI-Dokumentation**~~ ✅ OpenAPI 3.1 Spec (openapi.yaml) mit allen Endpoints
4. **i18n erweitern** — Web-App von 5 auf mindestens 10 Sprachen erweitern
5. **Lighthouse-Audit** — Performance, Accessibility, SEO Baseline messen

View file

@ -0,0 +1,926 @@
openapi: 3.1.0
info:
title: Memoro Server API
description: AI-powered voice memo management — memo processing, spaces, credits, meetings, settings.
version: 1.0.0
contact:
name: ManaCore
url: https://mana.how
servers:
- url: http://localhost:3015
description: Local development
- url: https://memoro.mana.how
description: Production
tags:
- name: Health
description: Health check and public endpoints
- name: Memos
description: Memo creation, transcription, and AI operations
- name: Spaces
description: Collaborative workspaces and invitations
- name: Invites
description: Pending invite management
- name: Credits
description: Mana credit balance, validation, and consumption
- name: Settings
description: User profile and app settings
- name: Meetings
description: Meeting bot and recording management
- name: Internal
description: Service-to-service callbacks (X-Service-Key auth)
- name: Cleanup
description: Audio file cleanup (X-Internal-API-Key auth)
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
serviceKey:
type: apiKey
in: header
name: X-Service-Key
internalKey:
type: apiKey
in: header
name: X-Internal-API-Key
schemas:
ApiResult:
type: object
properties:
success:
type: boolean
error:
type: string
required: [success]
PaginationQuery:
type: object
properties:
limit:
type: integer
minimum: 1
maximum: 100
default: 50
offset:
type: integer
minimum: 0
default: 0
CreateMemoBody:
type: object
required: [filePath, duration]
properties:
filePath:
type: string
minLength: 1
duration:
type: number
minimum: 0
spaceId:
type: string
format: uuid
blueprintId:
type: string
format: uuid
memoId:
type: string
format: uuid
recordingStartedAt:
type: string
mediaType:
type: string
AppendMemoBody:
type: object
required: [filePath, duration]
properties:
filePath:
type: string
minLength: 1
duration:
type: number
minimum: 0
recordingIndex:
type: integer
minimum: 0
recordingLanguages:
type: array
items:
type: string
enableDiarization:
type: boolean
CombineMemoBody:
type: object
required: [memoIds]
properties:
memoIds:
type: array
items:
type: string
format: uuid
minItems: 2
QuestionMemoBody:
type: object
required: [question]
properties:
question:
type: string
minLength: 1
CreateSpaceBody:
type: object
required: [name]
properties:
name:
type: string
minLength: 1
description:
type: string
LinkMemoBody:
type: object
required: [memoId]
properties:
memoId:
type: string
format: uuid
InviteBody:
type: object
required: [email]
properties:
email:
type: string
format: email
InviteActionBody:
type: object
required: [inviteId]
properties:
inviteId:
type: string
format: uuid
CreateBotBody:
type: object
required: [meeting_url]
properties:
meeting_url:
type: string
pattern: '^https://(teams\.microsoft\.com|meet\.google\.com|[\w-]+\.zoom\.us)/'
space_id:
type: string
format: uuid
CheckCreditsBody:
type: object
required: [operation, amount]
properties:
operation:
type: string
minLength: 1
amount:
type: number
minimum: 0
ConsumeCreditsBody:
type: object
required: [operation, amount, description]
properties:
operation:
type: string
minLength: 1
amount:
type: number
minimum: 0
description:
type: string
minLength: 1
metadata:
type: object
UpdateProfileBody:
type: object
properties:
display_name:
type: string
avatar_url:
type: string
format: uri
bio:
type: string
maxLength: 500
UpdateDataUsageBody:
type: object
required: [accepted]
properties:
accepted:
type: boolean
TranscriptionCompletedBody:
type: object
required: [memoId, userId, success]
properties:
memoId:
type: string
userId:
type: string
success:
type: boolean
transcriptionResult:
type: object
properties:
transcript:
type: string
utterances:
type: array
items:
type: object
properties:
offset: { type: number }
duration: { type: number }
text: { type: string }
speaker: { type: string }
languages:
type: array
items: { type: string }
primary_language:
type: string
duration:
type: number
route:
type: string
error:
type: string
fallbackStage:
type: string
ManualCleanupBody:
type: object
properties:
userIds:
type: array
items:
type: string
format: uuid
CreditCosts:
type: object
properties:
TRANSCRIPTION_PER_MINUTE: { type: number, example: 2 }
HEADLINE_GENERATION: { type: number, example: 10 }
MEMORY_CREATION: { type: number, example: 10 }
BLUEPRINT_PROCESSING: { type: number, example: 5 }
QUESTION_MEMO: { type: number, example: 5 }
MEMO_COMBINE: { type: number, example: 5 }
MEETING_RECORDING_PER_MINUTE: { type: number, example: 2 }
paths:
/health:
get:
tags: [Health]
summary: Health check with dependency status
responses:
'200':
description: All systems operational
content:
application/json:
schema:
type: object
properties:
status: { type: string, enum: [ok, degraded] }
service: { type: string }
runtime: { type: string }
timestamp: { type: string, format: date-time }
checks:
type: object
properties:
supabase: { type: string, enum: [ok, error] }
'503':
description: One or more dependencies degraded
/api/v1/credits/pricing:
get:
tags: [Credits]
summary: Get credit cost constants (public, no auth)
responses:
'200':
description: Cost constants
content:
application/json:
schema:
type: object
properties:
costs:
$ref: '#/components/schemas/CreditCosts'
# ── Memos ────────────────────────────────────────────────────────
/api/v1/memos:
post:
tags: [Memos]
summary: Create memo from uploaded file
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateMemoBody'
responses:
'201': { description: Memo created, transcription started }
'400': { description: Validation error }
'402': { description: Insufficient credits }
/api/v1/memos/{id}/append:
post:
tags: [Memos]
summary: Append transcription to existing memo
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AppendMemoBody'
responses:
'200': { description: Append transcription started }
'400': { description: Validation error }
'402': { description: Insufficient credits }
'404': { description: Memo not found }
/api/v1/memos/{id}/retry-transcription:
post:
tags: [Memos]
summary: Retry failed transcription
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Transcription retry started }
'400': { description: No audio file }
'404': { description: Memo not found }
/api/v1/memos/{id}/retry-headline:
post:
tags: [Memos]
summary: Retry headline generation
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Headline regenerated }
'404': { description: Memo not found }
'500': { description: Generation failed }
/api/v1/memos/combine:
post:
tags: [Memos]
summary: Combine multiple memos with AI
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CombineMemoBody'
responses:
'200': { description: Memos combined }
'400': { description: Validation error }
'402': { description: Insufficient credits }
'404': { description: One or more memos not found }
/api/v1/memos/{id}/question:
post:
tags: [Memos]
summary: Ask a question about memo transcript
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/QuestionMemoBody'
responses:
'200': { description: Answer generated }
'400': { description: Validation error or no transcript }
'402': { description: Insufficient credits }
'404': { description: Memo not found }
# ── Spaces ───────────────────────────────────────────────────────
/api/v1/spaces:
get:
tags: [Spaces]
summary: List user's spaces
security: [{ bearerAuth: [] }]
parameters:
- name: limit
in: query
schema: { type: integer, default: 50, maximum: 100 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of spaces with pagination }
post:
tags: [Spaces]
summary: Create a space
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSpaceBody'
responses:
'201': { description: Space created }
'400': { description: Validation error }
/api/v1/spaces/{id}:
get:
tags: [Spaces]
summary: Get space details
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Space details }
'403': { description: Access denied }
'404': { description: Space not found }
delete:
tags: [Spaces]
summary: Delete space (owner only)
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Space deleted }
'403': { description: Not the owner }
'404': { description: Space not found }
/api/v1/spaces/{id}/leave:
post:
tags: [Spaces]
summary: Leave a space (non-owner)
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Left space }
'400': { description: Owner cannot leave }
'403': { description: Not a member }
/api/v1/spaces/{id}/memos/link:
post:
tags: [Spaces]
summary: Link memo to space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LinkMemoBody'
responses:
'200': { description: Memo linked }
'403': { description: Not a member }
/api/v1/spaces/{id}/memos/unlink:
post:
tags: [Spaces]
summary: Unlink memo from space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LinkMemoBody'
responses:
'200': { description: Memo unlinked }
/api/v1/spaces/{id}/memos:
get:
tags: [Spaces]
summary: List memos in space
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: Paginated memo list }
'403': { description: Not a member }
/api/v1/spaces/{id}/invites:
get:
tags: [Spaces]
summary: List space invites
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: List of invites }
/api/v1/spaces/{id}/invite:
post:
tags: [Spaces]
summary: Send invitation to email
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteBody'
responses:
'201': { description: Invite created and email sent }
'400': { description: Invalid email }
'403': { description: Not a member }
# ── Invites ──────────────────────────────────────────────────────
/api/v1/invites/pending:
get:
tags: [Invites]
summary: List pending invites for current user
security: [{ bearerAuth: [] }]
responses:
'200': { description: List of pending invites }
/api/v1/invites/accept:
post:
tags: [Invites]
summary: Accept an invite
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteActionBody'
responses:
'200': { description: Invite accepted }
'404': { description: Invite not found }
/api/v1/invites/decline:
post:
tags: [Invites]
summary: Decline an invite
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/InviteActionBody'
responses:
'200': { description: Invite declined }
'404': { description: Invite not found }
# ── Credits ──────────────────────────────────────────────────────
/api/v1/credits/balance:
get:
tags: [Credits]
summary: Get credit balance
security: [{ bearerAuth: [] }]
responses:
'200':
description: Credit balance
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
credits: { type: number }
totalEarned: { type: number }
totalSpent: { type: number }
/api/v1/credits/check:
post:
tags: [Credits]
summary: Check if user has enough credits
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CheckCreditsBody'
responses:
'200': { description: Credit validation result }
/api/v1/credits/consume:
post:
tags: [Credits]
summary: Consume credits
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ConsumeCreditsBody'
responses:
'200': { description: Credits consumed }
# ── Settings ─────────────────────────────────────────────────────
/api/v1/settings:
get:
tags: [Settings]
summary: Get all user settings
security: [{ bearerAuth: [] }]
responses:
'200': { description: User settings }
/api/v1/settings/memoro:
get:
tags: [Settings]
summary: Get memoro-specific settings
security: [{ bearerAuth: [] }]
responses:
'200': { description: Memoro settings }
patch:
tags: [Settings]
summary: Update memoro settings
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
minProperties: 1
responses:
'200': { description: Settings updated }
'400': { description: Empty body }
/api/v1/settings/memoro/data-usage:
patch:
tags: [Settings]
summary: Update data usage acceptance
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDataUsageBody'
responses:
'200': { description: Data usage updated }
/api/v1/settings/profile:
patch:
tags: [Settings]
summary: Update user profile
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProfileBody'
responses:
'200': { description: Profile updated }
'400': { description: Validation error }
# ── Meetings ─────────────────────────────────────────────────────
/api/v1/meetings/bots:
post:
tags: [Meetings]
summary: Create meeting bot
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateBotBody'
responses:
'200': { description: Bot created }
'400': { description: Invalid meeting URL }
'402': { description: Insufficient credits }
get:
tags: [Meetings]
summary: List meeting bots
security: [{ bearerAuth: [] }]
parameters:
- name: space_id
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of bots }
/api/v1/meetings/bots/{id}:
get:
tags: [Meetings]
summary: Get bot by ID
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Bot details }
'404': { description: Bot not found }
/api/v1/meetings/bots/{id}/stop:
post:
tags: [Meetings]
summary: Stop a meeting bot
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Bot stop signal sent }
'404': { description: Bot not found }
/api/v1/meetings/recordings:
get:
tags: [Meetings]
summary: List recordings
security: [{ bearerAuth: [] }]
parameters:
- name: space_id
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, default: 50 }
- name: offset
in: query
schema: { type: integer, default: 0 }
responses:
'200': { description: List of recordings }
/api/v1/meetings/recordings/{id}:
get:
tags: [Meetings]
summary: Get recording by ID
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Recording details }
'404': { description: Recording not found }
/api/v1/meetings/recordings/{id}/to-memo:
post:
tags: [Meetings]
summary: Convert recording to memo
security: [{ bearerAuth: [] }]
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200': { description: Memo created from recording }
'400': { description: No audio/video file }
'404': { description: Recording not found }
# ── Internal ─────────────────────────────────────────────────────
/api/v1/internal/transcription-completed:
post:
tags: [Internal]
summary: Transcription completion callback
security: [{ serviceKey: [] }]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TranscriptionCompletedBody'
responses:
'200': { description: Callback processed }
/api/v1/internal/batch-metadata:
post:
tags: [Internal]
summary: Update batch job metadata
security: [{ serviceKey: [] }]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [memoId, jobId]
properties:
memoId: { type: string }
jobId: { type: string }
responses:
'200': { description: Metadata updated }
'404': { description: Memo not found }
# ── Cleanup ──────────────────────────────────────────────────────
/api/v1/cleanup/run:
post:
tags: [Cleanup]
summary: Trigger async audio cleanup
security: [{ internalKey: [] }]
responses:
'200': { description: Cleanup started }
/api/v1/cleanup/manual:
post:
tags: [Cleanup]
summary: Manual cleanup with optional user IDs
security: [{ internalKey: [] }]
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ManualCleanupBody'
responses:
'200': { description: Cleanup result }
# ── Webhooks ─────────────────────────────────────────────────────
/meetings/webhooks/bot-events:
post:
tags: [Meetings]
summary: Meeting bot webhook (HMAC-verified)
description: Receives recording.completed and recording.failed events from meeting bot service.
responses:
'200': { description: Event processed }
'400': { description: Invalid JSON }
'401': { description: Invalid HMAC signature }