## 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
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
from pydantic_settings import BaseSettings
|
|
from pydantic import field_validator
|
|
from typing import Optional
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
# Application
|
|
APP_NAME: str = "Patherly"
|
|
DEBUG: bool = False
|
|
API_V1_PREFIX: str = "/api/v1"
|
|
|
|
# Database - Railway provides DATABASE_URL, we convert it for asyncpg
|
|
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/patherly"
|
|
|
|
@field_validator("DATABASE_URL", mode="before")
|
|
@classmethod
|
|
def convert_database_url(cls, v: str) -> str:
|
|
"""Convert standard postgres URL to asyncpg format."""
|
|
if v.startswith("postgresql://"):
|
|
return v.replace("postgresql://", "postgresql+asyncpg://", 1)
|
|
return v
|
|
|
|
@property
|
|
def DATABASE_URL_SYNC(self) -> str:
|
|
"""Get sync URL by removing asyncpg prefix from DATABASE_URL."""
|
|
return self.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://", 1)
|
|
|
|
# JWT Settings
|
|
SECRET_KEY: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
|
|
ALGORITHM: str = "HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
|
|
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
|
|
|
# Security
|
|
BCRYPT_ROUNDS: int = 12
|
|
|
|
# Registration
|
|
REQUIRE_INVITE_CODE: bool = True # Set to False to allow open registration
|
|
|
|
# CORS - set FRONTEND_URL in production (e.g., https://patherly.up.railway.app)
|
|
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:5173", "http://localhost:5174"]
|
|
FRONTEND_URL: Optional[str] = None
|
|
# Allow all Railway PR environments (set to True in Railway env vars)
|
|
ALLOW_RAILWAY_ORIGINS: bool = False
|
|
|
|
@property
|
|
def allowed_origins(self) -> list[str]:
|
|
"""Get all allowed CORS origins including FRONTEND_URL if set."""
|
|
origins = self.CORS_ORIGINS.copy()
|
|
if self.FRONTEND_URL and self.FRONTEND_URL not in origins:
|
|
origins.append(self.FRONTEND_URL)
|
|
return origins
|
|
|
|
def is_origin_allowed(self, origin: str) -> bool:
|
|
"""Check if an origin is allowed, including Railway wildcard pattern."""
|
|
if origin in self.allowed_origins:
|
|
return True
|
|
# Allow any *.up.railway.app origin for PR environments
|
|
if self.ALLOW_RAILWAY_ORIGINS and origin.endswith(".up.railway.app"):
|
|
return True
|
|
return False
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
case_sensitive = True
|
|
extra = "ignore" # Ignore extra env vars like DATABASE_URL_SYNC
|
|
|
|
|
|
settings = Settings()
|