feat(08-05): decompose documents API monolith into documents/ package — CODE-02 CODE-08
- upload.py: request_upload_url, upload_document, confirm_upload (3 routes) - crud.py: list_documents, get_document, patch_document, delete_document, classify_document (5 routes) - content.py: stream_document_content + _parse_range (1 route) - shared.py: _CLOUD_PROVIDERS, UploadUrlRequest, DocumentPatch (single definitions) - __init__.py: router aggregator prefix=/api/documents, 9 routes total - documents.py monolith deleted; all URL paths unchanged - list_documents registered on parent router to avoid FastAPI empty-path/prefix check - get_storage_backend_for_document re-exported for test monkeypatching compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
f01fb0e6d5
commit
81337bd9e3
@@ -0,0 +1,45 @@
|
||||
"""Shared constants and Pydantic request models for the documents API package.
|
||||
|
||||
CODE-08: Single definition of _CLOUD_PROVIDERS, UploadUrlRequest, and DocumentPatch.
|
||||
These are imported by upload.py and crud.py — never duplicated.
|
||||
|
||||
T-05-06-01: _CLOUD_PROVIDERS is an allowlist frozenset; target_backend validated
|
||||
against it (never against user-supplied strings).
|
||||
T-05-09-01: DocumentPatch fields declared explicitly — mass assignment prevented.
|
||||
T-05-09-02: filename_no_path_separators validator preserved verbatim (path traversal
|
||||
defense at the API boundary — D-11 analysis: stays in Pydantic model).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
# Valid cloud backend slugs (T-05-06-01: validated against allowlist, not user-supplied string)
|
||||
_CLOUD_PROVIDERS = frozenset({"google_drive", "onedrive", "nextcloud", "webdav"})
|
||||
|
||||
|
||||
class UploadUrlRequest(BaseModel):
|
||||
filename: str
|
||||
content_type: str
|
||||
|
||||
|
||||
class DocumentPatch(BaseModel):
|
||||
"""Pydantic model for PATCH /api/documents/{doc_id}.
|
||||
|
||||
Optional fields — model_fields_set distinguishes "not provided" from "set to null".
|
||||
At least one field must be present in model_fields_set (enforced in the handler).
|
||||
|
||||
T-05-09-01: explicit field declaration prevents mass assignment.
|
||||
T-05-09-02: only filename and folder_id are accepted — no other fields can be set.
|
||||
"""
|
||||
filename: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
folder_id: Optional[uuid.UUID] = None
|
||||
|
||||
@field_validator("filename")
|
||||
@classmethod
|
||||
def filename_no_path_separators(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v is not None and ("/" in v or "\\" in v):
|
||||
raise ValueError("filename must not contain path separators")
|
||||
return v
|
||||
Reference in New Issue
Block a user