Phase 3: Graph view, backlinks, quick switcher, export

- Add outgoing_links (JSON) and backlinks_count to Document model
- POST /documents/{id}/detect-links — detect [[uuid]] patterns in content
- GET /documents/{id}/backlinks — documents referencing this doc
- GET /documents/{id}/outgoing-links — documents this doc references
- GET /documents/{id}/links — combined incoming + outgoing
- GET /projects/{id}/graph — full project relationship graph
- GET /search/quick — fuzzy search (Quick Switcher Cmd+K)
- GET /projects/{id}/documents/search — project-scoped search
- GET /documents/{id}/export — markdown|json export
- GET /projects/{id}/export — json|zip export
- 27 new tests
This commit is contained in:
Motoko
2026-03-30 23:46:45 +00:00
parent 202e70b4a8
commit 07f9ac91fc
9 changed files with 1887 additions and 6 deletions

View File

@@ -145,3 +145,122 @@ class DocumentBriefResponse(BaseModel):
class DocumentListResponse(BaseModel):
documents: list[DocumentBriefResponse]
# =============================================================================
# Phase 3: Link Detection & Graph Schemas
# =============================================================================
class DetectLinksRequest(BaseModel):
content: str = Field(..., max_length=5_000_000) # ~5MB limit
class BrokenLink(BaseModel):
reference: str
reason: str # "document_not_found" | "invalid_format"
class DetectLinksResponse(BaseModel):
document_id: str
outgoing_links: list[str]
links_detected: int
links_broken: int
broken_links: list[BrokenLink] = []
class BacklinkItem(BaseModel):
document_id: str
title: str
project_id: str
project_name: str
excerpt: str
updated_at: datetime
class BacklinksResponse(BaseModel):
document_id: str
backlinks_count: int
backlinks: list[BacklinkItem]
class OutgoingLinkItem(BaseModel):
document_id: str
title: str
project_id: str
project_name: str
exists: bool
updated_at: datetime | None
class OutgoingLinksResponse(BaseModel):
document_id: str
outgoing_links_count: int
outgoing_links: list[OutgoingLinkItem]
class LinkItem(BaseModel):
document_id: str
title: str
anchor_text: str | None = None
class LinksResponse(BaseModel):
document_id: str
outgoing_links: list[LinkItem]
backlinks: list[LinkItem]
class GraphNode(BaseModel):
id: str
title: str
type: str = "document"
class GraphEdge(BaseModel):
source: str
target: str
type: str = "reference"
class GraphStats(BaseModel):
total_documents: int
total_references: int
orphaned_documents: int
class GraphResponse(BaseModel):
project_id: str
nodes: list[GraphNode]
edges: list[GraphEdge]
stats: GraphStats
# =============================================================================
# Phase 3: Export Schemas
# =============================================================================
class DocumentExportResponse(BaseModel):
"""Used for JSON export format."""
id: str
title: str
content: str
tiptap_content: dict[str, Any] | None = None
created_at: datetime
updated_at: datetime
metadata: dict[str, Any] = Field(default_factory=dict)
class ProjectExportDocument(BaseModel):
id: str
title: str
content: str
tiptap_content: dict[str, Any] | None = None
outgoing_links: list[str] = []
metadata: dict[str, Any] = Field(default_factory=dict)
class ProjectExportResponse(BaseModel):
project: dict[str, Any]
documents: list[ProjectExportDocument]
exported_at: datetime
format_version: str = "3.0"