--- category: reference tags: - phase-2 - management-api last_updated: 2026-03-13 --- # P2-4: Management API **Branch:** `feat/P2-4-management-api` (from `phase-2`) **Commit:** `1e80a07` **Tests:** 30 passing ## What was built WSGI middleware (`ManagementMiddleware`) that intercepts `/admin/*` requests before they reach the otterwiki Flask app. All routes require platform JWT authentication. ## Files | File | Purpose | |------|---------| | `app/management/routes.py` | WSGI middleware with 8 API endpoints | | `app/management/wiki_init.py` | Git repo init + bootstrap template writer | | `app/management/token.py` | MCP bearer token generation (secrets + bcrypt) | | `app/models/user.py` | Added `get_by_email()` scan method | | `tests/test_management_api.py` | 30 unit tests (moto + tmpdir) | ## API Endpoints | Method | Path | Description | |--------|------|-------------| | POST | `/admin/wikis` | Create wiki (repo init, bootstrap, ACL, token) | | GET | `/admin/wikis` | List user's wikis | | GET | `/admin/wikis/{slug}` | Wiki details + MCP endpoint | | DELETE | `/admin/wikis/{slug}` | Delete wiki (repo, DB records, ACLs) | | POST | `/admin/wikis/{slug}/token` | Regenerate MCP bearer token | | POST | `/admin/wikis/{slug}/acl` | Grant access (body: `{email, role}`) | | DELETE | `/admin/wikis/{slug}/acl/{grantee_id}` | Revoke access | | GET | `/admin/wikis/{slug}/acl` | List ACL entries | ## Design decisions - **WSGI middleware pattern** — wraps the otterwiki app; if `PATH_INFO` starts with `/admin`, handle it; otherwise pass through. - **Plain JSON responses** — `json.dumps()` with `Content-Type: application/json`, not Flask. - **Ownership enforcement** — delete, token regen, and ACL grant/revoke require `role=owner` in ACL table. - **Tier limits** — `wiki_count >= wiki_limit` check on create; count incremented/decremented atomically. - **Token shown once** — plaintext returned only on create and regenerate; only bcrypt hash stored. - **Repo cleanup on create failure** — if `init_wiki_repo` fails, DB records are rolled back. - **wiki_id format** — `{owner_id}:{wiki_slug}` (consistent with ACL model). - **MCP endpoint** — returns `/{slug}/mcp` for now (no username mapping yet). ## Integration notes To wire into the Lambda handler, wrap the otterwiki WSGI app: ```python from app.management.routes import ManagementMiddleware wrapped = ManagementMiddleware( otterwiki_wsgi_app, auth_middleware=auth_middleware, user_model=users, wiki_model=wikis, acl_model=acls, ) handler = make_lambda_handler(wrapped) ``` ## Not included (future work) - Lambda handler wiring (will be done when all Phase 2 components integrate) - Username-to-subdomain mapping for MCP endpoint URLs - Rate limiting on wiki creation