Replace team_id with account_id across all API endpoints (trees, categories, tags, steps, step_categories, admin, auth). Add new accounts and webhooks endpoints. Registration now atomically creates Account + Subscription, with account_invite_code bypassing the platform invite gate. Schemas updated for account_id/account_role. 82 tests passing including 18 new tests for accounts, subscriptions, and permissions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136 lines
3.7 KiB
Python
136 lines
3.7 KiB
Python
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Optional, Literal
|
|
from uuid import UUID
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class StepCommand(BaseModel):
|
|
"""A command that can be run as part of a step."""
|
|
label: str
|
|
command: str
|
|
command_type: Optional[str] = None # e.g., 'powershell', 'cmd', 'bash'
|
|
|
|
|
|
class StepContent(BaseModel):
|
|
"""Content structure for step library entries (stored as JSONB)."""
|
|
instructions: str = Field(..., min_length=1)
|
|
help_text: Optional[str] = None
|
|
commands: Optional[list[StepCommand]] = None
|
|
|
|
|
|
# Base schemas
|
|
class StepLibraryBase(BaseModel):
|
|
title: str = Field(..., min_length=1, max_length=255)
|
|
step_type: Literal['decision', 'action', 'solution']
|
|
content: StepContent
|
|
category_id: Optional[UUID] = None
|
|
tags: list[str] = Field(default_factory=list)
|
|
visibility: Literal['private', 'team', 'public'] = 'private'
|
|
|
|
|
|
class StepLibraryCreate(StepLibraryBase):
|
|
account_id: Optional[UUID] = None
|
|
|
|
|
|
class StepLibraryUpdate(BaseModel):
|
|
title: Optional[str] = Field(None, min_length=1, max_length=255)
|
|
step_type: Optional[Literal['decision', 'action', 'solution']] = None
|
|
content: Optional[StepContent] = None
|
|
category_id: Optional[UUID] = None
|
|
tags: Optional[list[str]] = None
|
|
visibility: Optional[Literal['private', 'team', 'public']] = None
|
|
|
|
|
|
class StepLibraryResponse(StepLibraryBase):
|
|
id: UUID
|
|
created_by: UUID
|
|
account_id: Optional[UUID] = None
|
|
usage_count: int
|
|
rating_average: Decimal
|
|
rating_count: int
|
|
helpful_yes: int
|
|
helpful_no: int
|
|
is_featured: bool
|
|
is_verified: bool
|
|
is_active: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
# Computed fields (populated by API)
|
|
category_name: Optional[str] = None
|
|
author_name: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class StepLibraryListResponse(BaseModel):
|
|
id: UUID
|
|
title: str
|
|
step_type: str
|
|
visibility: str
|
|
category_id: Optional[UUID] = None
|
|
category_name: Optional[str] = None
|
|
tags: list[str]
|
|
usage_count: int
|
|
rating_average: Decimal
|
|
rating_count: int
|
|
is_featured: bool
|
|
created_by: UUID
|
|
author_name: Optional[str] = None
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Rating schemas
|
|
class StepRatingBase(BaseModel):
|
|
rating: int = Field(..., ge=1, le=5)
|
|
was_helpful: Optional[bool] = None
|
|
review_text: Optional[str] = Field(None, max_length=500)
|
|
|
|
|
|
class StepRatingCreate(StepRatingBase):
|
|
session_id: Optional[UUID] = None # For verified use tracking
|
|
|
|
|
|
class StepRatingUpdate(BaseModel):
|
|
rating: Optional[int] = Field(None, ge=1, le=5)
|
|
was_helpful: Optional[bool] = None
|
|
review_text: Optional[str] = Field(None, max_length=500)
|
|
|
|
|
|
class StepRatingResponse(StepRatingBase):
|
|
id: UUID
|
|
step_id: UUID
|
|
user_id: UUID
|
|
is_verified_use: bool
|
|
session_id: Optional[UUID] = None
|
|
is_visible: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
# Computed
|
|
user_name: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Search and filter schemas
|
|
class StepSearchParams(BaseModel):
|
|
q: Optional[str] = None # Full-text search query
|
|
category_id: Optional[UUID] = None
|
|
tags: Optional[list[str]] = None
|
|
min_rating: Optional[float] = Field(None, ge=0, le=5)
|
|
step_type: Optional[Literal['decision', 'action', 'solution']] = None
|
|
visibility: Optional[Literal['private', 'team', 'public']] = None
|
|
sort_by: Literal['recent', 'popular', 'highest_rated', 'most_used'] = 'recent'
|
|
limit: int = Field(20, ge=1, le=100)
|
|
offset: int = Field(0, ge=0)
|
|
|
|
|
|
class PopularTagResponse(BaseModel):
|
|
tag: str
|
|
count: int
|