feat: v1.0.0 — UX polish, security hardening, code quality
UI/UX: - Bill detail page tab UI (Analysis / Timeline / Votes / Notes) - Topic tag pills on bill detail and listing pages — filtered to known topics, clickable, properly labelled via shared lib/topics.ts - Notes panel always-open in Notes tab; sign-in prompt for guests - Collapsible sidebar with icon-only mode and localStorage persistence - Bills page defaults to has-text filter enabled - Follow mode dropdown transparency fix - Favicon (Landmark icon, blue background) Security: - Fernet encryption for ntfy passwords at rest (app/core/crypto.py) - Separate ENCRYPTION_SECRET_KEY env var; falls back to JWT derivation - ntfy_password no longer returned in GET response — replaced with ntfy_password_set: bool; NotificationSettingsUpdate type for writes - JWT_SECRET_KEY fail-fast on startup if using default placeholder - get_optional_user catches (JWTError, ValueError) only, not Exception Bug fixes & code quality: - Dashboard N+1 topic query replaced with single OR query - notification_utils.py topic follower N+1 replaced with batch query - Note query in bill detail page gated on token (enabled: !!token) - search.py max_length=500 guard against oversized queries - CollectionCreate.validate_name wired up with @field_validator - LLM_RATE_LIMIT_RPM default raised from 10 to 50 Authored by: Jack Levy
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from datetime import date, datetime
|
||||
from typing import Any, Generic, Optional, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
|
||||
# ── Notifications ──────────────────────────────────────────────────────────────
|
||||
@@ -31,7 +31,7 @@ class NotificationSettingsResponse(BaseModel):
|
||||
ntfy_auth_method: str = "none" # none | token | basic
|
||||
ntfy_token: str = ""
|
||||
ntfy_username: str = ""
|
||||
ntfy_password: str = ""
|
||||
ntfy_password_set: bool = False
|
||||
ntfy_enabled: bool = False
|
||||
rss_enabled: bool = False
|
||||
rss_token: Optional[str] = None
|
||||
@@ -315,11 +315,13 @@ class CollectionCreate(BaseModel):
|
||||
name: str
|
||||
is_public: bool = False
|
||||
|
||||
def validate_name(self) -> str:
|
||||
name = self.name.strip()
|
||||
if not 1 <= len(name) <= 100:
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def validate_name(cls, v: str) -> str:
|
||||
v = v.strip()
|
||||
if not 1 <= len(v) <= 100:
|
||||
raise ValueError("name must be 1–100 characters")
|
||||
return name
|
||||
return v
|
||||
|
||||
|
||||
class CollectionUpdate(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user