Phase 5.14: API documentation - OpenAPI tags, response models, examples, docs
This commit is contained in:
+57
-1
@@ -212,7 +212,63 @@ async def lifespan(app: FastAPI):
|
||||
def create_app() -> FastAPI:
|
||||
"""Create and configure the FastAPI application."""
|
||||
settings = get_settings()
|
||||
app = FastAPI(title="LeoCRM", version="1.0.0", lifespan=lifespan)
|
||||
app = FastAPI(
|
||||
title="LeoCRM",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
description=(
|
||||
"LeoCRM — Self-hosted CRM system for small sales teams.\n\n"
|
||||
"## Authentication\n"
|
||||
"All endpoints (except `/api/v1/auth/login` and `/api/v1/health`) require "
|
||||
"a valid session cookie. Obtain a session by calling `POST /api/v1/auth/login`.\n\n"
|
||||
"## Multi-Tenancy\n"
|
||||
"All data is tenant-scoped. The tenant context is set automatically from the "
|
||||
"authenticated session.\n\n"
|
||||
"## Plugins\n"
|
||||
"LeoCRM uses a plugin architecture. Core routes are always available. "
|
||||
"Plugin routes (DMS, Mail, Calendar, Automation, etc.) are registered when "
|
||||
"the corresponding plugin is active."
|
||||
),
|
||||
openapi_tags=[
|
||||
{"name": "health", "description": "Health check endpoints — no auth required."},
|
||||
{"name": "metrics", "description": "Prometheus metrics endpoint."},
|
||||
{"name": "auth", "description": "Authentication: login, logout, session, password reset."},
|
||||
{"name": "users", "description": "User management: CRUD, user-tenant assignments."},
|
||||
{"name": "roles", "description": "Role management and permission definitions."},
|
||||
{"name": "groups", "description": "User groups for contact assignment and filtering."},
|
||||
{"name": "tenants", "description": "Tenant management and tenant switching."},
|
||||
{"name": "notifications", "description": "User notifications and notification preferences."},
|
||||
{"name": "contacts", "description": "Contact CRUD, contact persons, FTS search, export, soft-delete."},
|
||||
{"name": "contact-folders", "description": "Contact folder management for organization."},
|
||||
{"name": "entity-history", "description": "Audit trail and entity change history."},
|
||||
{"name": "import-export", "description": "Bulk import and export of contacts and data."},
|
||||
{"name": "plugins", "description": "Plugin management: list, install, activate, deactivate."},
|
||||
{"name": "ai-copilot", "description": "AI copilot: chat, suggestions, conversation history."},
|
||||
{"name": "workflows", "description": "Workflow definitions, instances, and execution."},
|
||||
{"name": "user-preferences", "description": "Per-user preference settings."},
|
||||
{"name": "currencies", "description": "Currency management for multi-currency support."},
|
||||
{"name": "taxes", "description": "Tax rate management (VAT, sales tax)."},
|
||||
{"name": "sequences", "description": "Number sequence management for invoices, quotes, etc."},
|
||||
{"name": "system-settings", "description": "Tenant-level system settings (company info, theme)."},
|
||||
{"name": "attachments", "description": "File attachments for contacts, companies, and entities."},
|
||||
{"name": "addresses", "description": "Address management for contacts and companies."},
|
||||
{"name": "audit", "description": "Audit log queries and compliance reporting."},
|
||||
{"name": "automation", "description": "Automation engine: agents, automations, cron jobs, execution logs."},
|
||||
{"name": "agents", "description": "AI agent definitions and agent runner endpoints."},
|
||||
{"name": "dms", "description": "Document Management System: files, folders, sources, sharing."},
|
||||
{"name": "mail", "description": "Email integration: IMAP accounts, folders, messages, send."},
|
||||
{"name": "calendar", "description": "Calendar management: appointments, resources, recurrence."},
|
||||
{"name": "search", "description": "Unified search across contacts, documents, emails, etc."},
|
||||
{"name": "reports", "description": "Report generator: templates, rendering, scheduled reports."},
|
||||
{"name": "entity-links", "description": "Entity linking: connect contacts to DMS documents and other entities."},
|
||||
{"name": "kommunikation", "description": "Unified messaging: conversations, participants, messages."},
|
||||
{"name": "ai-proactive", "description": "Proactive AI: insights, alerts, and recommendations."},
|
||||
{"name": "ai-assistant", "description": "AI assistant: chat completions, tool calling, context awareness."},
|
||||
{"name": "tags", "description": "Tag management: create, assign, search tags across entities."},
|
||||
{"name": "permissions", "description": "Permission management: roles, field-level permissions, sharing."},
|
||||
{"name": "public-share", "description": "Public sharing endpoints — no auth required, token-based access."},
|
||||
],
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
||||
@@ -26,7 +26,7 @@ from app.plugins.builtins.ai_ui_control.schemas import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
router = APIRouter(tags=["ai-ui-control"])
|
||||
|
||||
|
||||
# ─── REST endpoints (for AI agents) ───
|
||||
|
||||
+5
-3
@@ -12,7 +12,9 @@ from app.core.auth import get_redis
|
||||
from app.core.db import get_db
|
||||
from app.core.rate_limit import check_rate_limit, get_client_ip, reset_rate_limit
|
||||
from app.schemas.auth import (
|
||||
AuthResponse,
|
||||
LoginRequest,
|
||||
MessageResponse,
|
||||
PasswordResetConfirm,
|
||||
PasswordResetRequest,
|
||||
SwitchTenantRequest,
|
||||
@@ -24,7 +26,7 @@ router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
@router.post("/login", response_model=AuthResponse)
|
||||
async def login(
|
||||
request: Request,
|
||||
body: LoginRequest,
|
||||
@@ -91,7 +93,7 @@ async def login(
|
||||
return resp
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
@router.post("/logout", response_model=MessageResponse)
|
||||
async def logout(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -112,7 +114,7 @@ async def logout(
|
||||
return resp
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
@router.get("/me", response_model=AuthResponse)
|
||||
async def me(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
@@ -5,11 +5,12 @@ from __future__ import annotations
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.core.monitoring import get_health_status
|
||||
from app.schemas.common import HealthResponse
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("/api/v1/health")
|
||||
@router.get("/api/v1/health", response_model=HealthResponse)
|
||||
async def health():
|
||||
"""Health check — no auth required.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from app.models.notification import (
|
||||
NotificationPreference,
|
||||
NotificationType,
|
||||
)
|
||||
from app.schemas.common import NotificationPreferenceUpdate
|
||||
from app.schemas.common import NotificationPreferenceUpdate, UnreadCountResponse
|
||||
|
||||
router = APIRouter(prefix="/api/v1/notifications", tags=["notifications"])
|
||||
|
||||
@@ -67,7 +67,7 @@ async def mark_notification_read_endpoint(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/unread-count")
|
||||
@router.get("/unread-count", response_model=UnreadCountResponse)
|
||||
async def unread_count_endpoint(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(require_permission("notifications:read")),
|
||||
|
||||
@@ -9,13 +9,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db
|
||||
from app.deps import require_permission
|
||||
from app.schemas.system_settings import SystemSettingsUpsert
|
||||
from app.schemas.system_settings import SystemSettingsUpsert, SystemSettingsResponse
|
||||
from app.services import system_settings_service
|
||||
|
||||
router = APIRouter(prefix="/api/v1/system-settings", tags=["system-settings"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
@router.get("", response_model=SystemSettingsResponse)
|
||||
async def get_system_settings(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(require_permission("settings:read")),
|
||||
@@ -28,7 +28,7 @@ async def get_system_settings(
|
||||
return result
|
||||
|
||||
|
||||
@router.put("")
|
||||
@router.put("", response_model=SystemSettingsResponse)
|
||||
async def upsert_system_settings(
|
||||
body: SystemSettingsUpsert,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ from app.core.db import get_db
|
||||
from app.core.notifications import create_notification
|
||||
from app.core.permissions import invalidate_permission_cache
|
||||
from app.deps import require_permission
|
||||
from app.schemas.user import UserCreate, UserUpdate
|
||||
from app.schemas.user import UserCreate, UserUpdate, UserResponse, PaginatedUsers
|
||||
from app.services.user_service import user_service, _UNSET
|
||||
|
||||
router = APIRouter(prefix="/api/v1/users", tags=["users"])
|
||||
@@ -36,7 +36,7 @@ def _parse_role_id(raw: str | None) -> uuid.UUID | None:
|
||||
) from None
|
||||
|
||||
|
||||
@router.get("")
|
||||
@router.get("", response_model=PaginatedUsers)
|
||||
async def list_users(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(25, ge=1, le=100),
|
||||
@@ -49,7 +49,7 @@ async def list_users(
|
||||
return await user_service.list_users(db, tenant_id, page, page_size, search)
|
||||
|
||||
|
||||
@router.post("", status_code=status.HTTP_201_CREATED)
|
||||
@router.post("", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
|
||||
async def create_user(
|
||||
body: UserCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@@ -103,7 +103,7 @@ async def create_user(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{user_id}")
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
async def get_user(
|
||||
user_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
+8
-8
@@ -6,8 +6,8 @@ from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(..., min_length=1)
|
||||
email: EmailStr = Field(..., examples=["admin@leocrm.local"])
|
||||
password: str = Field(..., min_length=1, examples=["secure-password"])
|
||||
|
||||
|
||||
class PasswordResetRequest(BaseModel):
|
||||
@@ -24,12 +24,12 @@ class SwitchTenantRequest(BaseModel):
|
||||
|
||||
|
||||
class AuthResponse(BaseModel):
|
||||
user_id: str
|
||||
email: str
|
||||
name: str
|
||||
role: str
|
||||
tenant_id: str
|
||||
tenant_name: str | None = None
|
||||
user_id: str = Field(..., examples=["550e8400-e29b-41d4-a716-446655440000"])
|
||||
email: str = Field(..., examples=["admin@leocrm.local"])
|
||||
name: str = Field(..., examples=["Admin User"])
|
||||
role: str = Field(..., examples=["admin"])
|
||||
tenant_id: str = Field(..., examples=["550e8400-e29b-41d4-a716-446655440001"])
|
||||
tenant_name: str | None = Field(None, examples=["Acme GmbH"])
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
|
||||
+5
-5
@@ -6,11 +6,11 @@ from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
email: EmailStr
|
||||
name: str = Field(..., min_length=1, max_length=200)
|
||||
password: str = Field(..., min_length=8)
|
||||
role: str = Field(default="viewer")
|
||||
role_id: str | None = Field(default=None, description="UUID of a custom Role")
|
||||
email: EmailStr = Field(..., examples=["user@leocrm.local"])
|
||||
name: str = Field(..., min_length=1, max_length=200, examples=["John Doe"])
|
||||
password: str = Field(..., min_length=8, examples=["secure-password"])
|
||||
role: str = Field(default="viewer", examples=["viewer"])
|
||||
role_id: str | None = Field(default=None, description="UUID of a custom Role", examples=["550e8400-e29b-41d4-a716-446655440000"])
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user