Backend features: - Tree sharing via secure tokens with expiration (Issue #16) - Draft tree status with conditional validation (Issue #25) - Save session as custom tree with fork tracking (Issue #17) - Tree validation system for publish requirements - Session-to-tree conversion preserving custom steps Database migrations: - 024: Tree sharing (tree_shares table, visibility field) - 025: Tree status field (draft/published) - 25b: Merge migration for indexes New endpoints: - POST /api/v1/trees/{id}/share - Generate share token - GET /api/v1/shared/{token} - Public tree access - POST /api/v1/trees/{id}/can-publish - Validate tree - POST /api/v1/sessions/{id}/save-as-tree - Convert session Test coverage: - 20 tests for draft trees functionality - 14 tests for session-to-tree conversion - 15 tests for tree sharing Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from typing import Optional, Any, Literal
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -25,6 +25,7 @@ class TreeCreate(TreeBase):
|
||||
tree_structure: dict[str, Any] = Field(..., description="The decision tree structure in JSON format")
|
||||
is_public: bool = Field(False, description="Make tree visible to all users")
|
||||
is_default: bool = Field(False, description="Mark as a default/system tree (admin only)")
|
||||
status: Literal['draft', 'published'] = Field('published', description="Status: draft or published")
|
||||
category_id: Optional[UUID] = Field(None, description="Category ID from tree_categories table")
|
||||
tags: Optional[list[str]] = Field(None, max_length=10, description="List of tag names to assign")
|
||||
|
||||
@@ -37,6 +38,7 @@ class TreeUpdate(BaseModel):
|
||||
tree_structure: Optional[dict[str, Any]] = None
|
||||
is_public: Optional[bool] = None
|
||||
is_active: Optional[bool] = None
|
||||
status: Optional[Literal['draft', 'published']] = None
|
||||
tags: Optional[list[str]] = Field(None, max_length=10, description="List of tag names to assign (replaces existing)")
|
||||
|
||||
|
||||
@@ -70,6 +72,7 @@ class TreeResponse(TreeBase):
|
||||
is_active: bool
|
||||
is_public: bool
|
||||
is_default: bool
|
||||
status: str # draft or published
|
||||
version: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
@@ -92,6 +95,7 @@ class TreeListResponse(BaseModel):
|
||||
is_active: bool
|
||||
is_public: bool
|
||||
is_default: bool
|
||||
status: str # draft or published
|
||||
version: int
|
||||
usage_count: int
|
||||
created_at: datetime
|
||||
@@ -99,3 +103,60 @@ class TreeListResponse(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Tree Sharing Schemas ---
|
||||
|
||||
class TreeShareCreate(BaseModel):
|
||||
"""Request to create a share token for a tree."""
|
||||
allow_forking: bool = Field(True, description="Whether recipients can fork this tree")
|
||||
expires_at: Optional[datetime] = Field(None, description="Optional expiration time for the share")
|
||||
|
||||
|
||||
class TreeShareResponse(BaseModel):
|
||||
"""Response containing share token and URL."""
|
||||
id: UUID
|
||||
tree_id: UUID
|
||||
share_token: str
|
||||
share_url: str
|
||||
allow_forking: bool
|
||||
created_by: UUID
|
||||
created_at: datetime
|
||||
expires_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TreeVisibilityUpdate(BaseModel):
|
||||
"""Request to update tree visibility."""
|
||||
visibility: Literal['private', 'team', 'link', 'public'] = Field(..., description="Visibility level")
|
||||
|
||||
|
||||
class SharedTreeResponse(TreeBase):
|
||||
"""Public response for shared trees (minimal info)."""
|
||||
id: UUID
|
||||
tree_structure: dict[str, Any]
|
||||
category: Optional[str] = None
|
||||
tags: list[str] = []
|
||||
version: int
|
||||
allow_forking: bool # From share token
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Tree Validation Schemas ---
|
||||
|
||||
class ValidationError(BaseModel):
|
||||
"""Individual validation error."""
|
||||
field: str
|
||||
message: str
|
||||
|
||||
|
||||
class TreeValidationResponse(BaseModel):
|
||||
"""Response for tree validation endpoint."""
|
||||
can_publish: bool
|
||||
errors: list[ValidationError] = []
|
||||
|
||||
Reference in New Issue
Block a user