Initial commit: Backend API Phase 1a complete

- FastAPI backend with JWT auth
- PostgreSQL database schema
- Trees and Sessions CRUD APIs
- Export functionality (Markdown, Text, HTML)
- Docker setup for local development
- Alembic migrations
This commit is contained in:
Michael Chihlas
2026-01-22 14:38:53 -05:00
commit 52e8190211
42 changed files with 5385 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
from .user import UserCreate, UserUpdate, UserResponse, UserLogin
from .token import Token, TokenPayload
from .tree import TreeCreate, TreeUpdate, TreeResponse, TreeListResponse
from .session import SessionCreate, SessionUpdate, SessionResponse, SessionExport, DecisionRecord
__all__ = [
"UserCreate", "UserUpdate", "UserResponse", "UserLogin",
"Token", "TokenPayload",
"TreeCreate", "TreeUpdate", "TreeResponse", "TreeListResponse",
"SessionCreate", "SessionUpdate", "SessionResponse", "SessionExport", "DecisionRecord"
]

View File

@@ -0,0 +1,51 @@
from datetime import datetime
from typing import Optional, Any
from uuid import UUID
from pydantic import BaseModel, Field
class DecisionRecord(BaseModel):
node_id: str
question: Optional[str] = None
answer: Optional[str] = None
action_performed: Optional[str] = None
notes: Optional[str] = None
automation_used: Optional[bool] = False
timestamp: datetime
attachments: list[str] = Field(default_factory=list)
class SessionCreate(BaseModel):
tree_id: UUID
ticket_number: Optional[str] = Field(None, max_length=100)
client_name: Optional[str] = Field(None, max_length=255)
class SessionUpdate(BaseModel):
path_taken: Optional[list[str]] = None
decisions: Optional[list[DecisionRecord]] = None
ticket_number: Optional[str] = Field(None, max_length=100)
client_name: Optional[str] = Field(None, max_length=255)
class SessionResponse(BaseModel):
id: UUID
tree_id: UUID
user_id: UUID
tree_snapshot: dict[str, Any]
path_taken: list[str]
decisions: list[dict[str, Any]]
started_at: datetime
completed_at: Optional[datetime] = None
ticket_number: Optional[str] = None
client_name: Optional[str] = None
exported: bool
class Config:
from_attributes = True
class SessionExport(BaseModel):
format: str = Field(default="markdown", pattern="^(text|markdown|html)$")
include_timestamps: bool = True
include_tree_info: bool = True

View File

@@ -0,0 +1,14 @@
from typing import Optional
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
class TokenPayload(BaseModel):
sub: Optional[str] = None
exp: Optional[int] = None
type: Optional[str] = None

View File

@@ -0,0 +1,52 @@
from datetime import datetime
from typing import Optional, Any
from uuid import UUID
from pydantic import BaseModel, Field
class TreeBase(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
description: Optional[str] = None
category: Optional[str] = Field(None, max_length=100)
class TreeCreate(TreeBase):
tree_structure: dict[str, Any] = Field(..., description="The decision tree structure in JSON format")
class TreeUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=255)
description: Optional[str] = None
category: Optional[str] = Field(None, max_length=100)
tree_structure: Optional[dict[str, Any]] = None
is_active: Optional[bool] = None
class TreeResponse(TreeBase):
id: UUID
tree_structure: dict[str, Any]
author_id: Optional[UUID] = None
team_id: Optional[UUID] = None
is_active: bool
version: int
created_at: datetime
updated_at: datetime
usage_count: int
class Config:
from_attributes = True
class TreeListResponse(BaseModel):
id: UUID
name: str
description: Optional[str] = None
category: Optional[str] = None
is_active: bool
version: int
usage_count: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True

View File

@@ -0,0 +1,34 @@
from datetime import datetime
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field
class UserBase(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=255)
class UserCreate(UserBase):
password: str = Field(..., min_length=10, description="Password must be at least 10 characters")
class UserUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=255)
email: Optional[EmailStr] = None
class UserLogin(BaseModel):
email: EmailStr
password: str
class UserResponse(UserBase):
id: UUID
role: str
team_id: Optional[UUID] = None
created_at: datetime
last_login: Optional[datetime] = None
class Config:
from_attributes = True