Files
resolutionflow/backend/app/schemas/step_library.py
chihlasm c782d41eff Add step library foundation and user preferences (Issues #3, #5, #6, #7)
Issue #3: User Preferences - export format default
- Add userPreferencesStore with localStorage persistence
- Create Settings page with export format dropdown and theme toggle
- SessionDetailPage now uses default export format from preferences

Issue #5: Step Categories - database table and seed data
- Migration 007: step_categories table with team scoping
- Seed 10 default global categories (Citrix/VDI, AD, M365, etc.)
- Full CRUD API at /api/v1/step-categories

Issue #6: Step Library - database schema
- Migration 008: step_library, step_ratings, step_usage_log tables
- Support for decision/action/solution step types
- Visibility levels: private, team, public
- Rating aggregates and usage tracking

Issue #7: Step Library - CRUD API endpoints
- Full CRUD at /api/v1/steps with visibility filtering
- Full-text search endpoint
- Popular tags endpoint
- Rating/review system with verified use tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 01:25:31 -05:00

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):
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