Add step library foundation and user preferences (#24)
## Summary Implements Phase 2.5 Step Library Foundation: ### Issues Completed - #3 User Preferences - export format default setting - #5 Step Categories - database table and seed data - #6 Step Library - database schema and migrations - #7 Step Library - CRUD API endpoints - #8 Step Library - rating and review system ### Changes **Backend:** - Migration 007: step_categories table with 10 seeded global categories - Migration 008: step_library, step_ratings, step_usage_log tables - Full CRUD API for step categories (/api/v1/step-categories) - Full CRUD API for step library (/api/v1/steps) with search, filters, ratings - CORS support for Railway PR environments (ALLOW_RAILWAY_ORIGINS) **Frontend:** - User preferences store (Zustand + localStorage) - Settings page at /settings with export format dropdown - Default export format applied in SessionDetailPage ### Testing - Tested in Railway PR environment - Database seeded with 7 MSP troubleshooting trees - All API endpoints verified working
This commit was merged in pull request #24.
This commit is contained in:
135
backend/app/schemas/step_library.py
Normal file
135
backend/app/schemas/step_library.py
Normal file
@@ -0,0 +1,135 @@
|
||||
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):
|
||||
team_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
|
||||
team_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
|
||||
Reference in New Issue
Block a user