Address QA review: model registry, NOT NULL constraint, variable naming, toggle defaults, lockout UX

- C3: Register User, UserSession, NtfySent, TOTPUsage, BackupCode in models/__init__.py
- C4: Enforce settings.user_id NOT NULL after backfill in migration 023, update model
- W4: Rename misleading current_user → current_settings in dashboard.py
- W5: Match NtfySettingsSection initial state defaults to backend (true/1/2)
- W8: Clear lockout banner on username/password input change in LockScreen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-25 04:34:21 +08:00
parent f136a0820d
commit 15c99152d3
6 changed files with 30 additions and 15 deletions

View File

@ -119,7 +119,12 @@ def upgrade() -> None:
) )
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# 6. Drop pin_hash from settings — data now lives in users.password_hash # 6. Enforce NOT NULL on user_id now that backfill is complete
# ------------------------------------------------------------------
op.alter_column('settings', 'user_id', nullable=False)
# ------------------------------------------------------------------
# 7. Drop pin_hash from settings — data now lives in users.password_hash
# ------------------------------------------------------------------ # ------------------------------------------------------------------
op.drop_column('settings', 'pin_hash') op.drop_column('settings', 'pin_hash')

View File

@ -8,6 +8,11 @@ from app.models.project_task import ProjectTask
from app.models.person import Person from app.models.person import Person
from app.models.location import Location from app.models.location import Location
from app.models.task_comment import TaskComment from app.models.task_comment import TaskComment
from app.models.user import User
from app.models.session import UserSession
from app.models.ntfy_sent import NtfySent
from app.models.totp_usage import TOTPUsage
from app.models.backup_code import BackupCode
__all__ = [ __all__ = [
"Settings", "Settings",
@ -20,4 +25,9 @@ __all__ = [
"Person", "Person",
"Location", "Location",
"TaskComment", "TaskComment",
"User",
"UserSession",
"NtfySent",
"TOTPUsage",
"BackupCode",
] ]

View File

@ -10,10 +10,10 @@ class Settings(Base):
id: Mapped[int] = mapped_column(primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True, index=True)
# FK to users table — nullable during migration, will be NOT NULL after data migration # FK to users table — NOT NULL enforced by migration 023 after data backfill
user_id: Mapped[Optional[int]] = mapped_column( user_id: Mapped[int] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), ForeignKey("users.id", ondelete="CASCADE"),
nullable=True, nullable=False,
index=True, index=True,
) )

View File

@ -26,11 +26,11 @@ _not_parent_template = or_(
async def get_dashboard( async def get_dashboard(
client_date: Optional[date] = Query(None), client_date: Optional[date] = Query(None),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user: Settings = Depends(get_current_settings) current_settings: Settings = Depends(get_current_settings)
): ):
"""Get aggregated dashboard data.""" """Get aggregated dashboard data."""
today = client_date or date.today() today = client_date or date.today()
upcoming_cutoff = today + timedelta(days=current_user.upcoming_days) upcoming_cutoff = today + timedelta(days=current_settings.upcoming_days)
# Today's events (exclude parent templates — they are hidden, children are shown) # Today's events (exclude parent templates — they are hidden, children are shown)
today_start = datetime.combine(today, datetime.min.time()) today_start = datetime.combine(today, datetime.min.time())
@ -143,7 +143,7 @@ async def get_upcoming(
days: int = Query(default=7, ge=1, le=90), days: int = Query(default=7, ge=1, le=90),
client_date: Optional[date] = Query(None), client_date: Optional[date] = Query(None),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user: Settings = Depends(get_current_settings) current_settings: Settings = Depends(get_current_settings)
): ):
"""Get unified list of upcoming items (todos, events, reminders) sorted by date.""" """Get unified list of upcoming items (todos, events, reminders) sorted by date."""
today = client_date or date.today() today = client_date or date.today()

View File

@ -218,7 +218,7 @@ export default function LockScreen() {
id="username" id="username"
type="text" type="text"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => { setUsername(e.target.value); setLockoutMessage(null); }}
placeholder="Enter username" placeholder="Enter username"
required required
autoFocus autoFocus
@ -232,7 +232,7 @@ export default function LockScreen() {
id="password" id="password"
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => { setPassword(e.target.value); setLockoutMessage(null); }}
placeholder={isSetup ? 'Create a password' : 'Enter password'} placeholder={isSetup ? 'Create a password' : 'Enter password'}
required required
autoComplete={isSetup ? 'new-password' : 'current-password'} autoComplete={isSetup ? 'new-password' : 'current-password'}

View File

@ -36,13 +36,13 @@ export default function NtfySettingsSection({ settings, updateSettings }: NtfySe
const [showToken, setShowToken] = useState(false); const [showToken, setShowToken] = useState(false);
// Per-type toggles // Per-type toggles
const [eventsEnabled, setEventsEnabled] = useState(false); const [eventsEnabled, setEventsEnabled] = useState(true);
const [eventLeadMinutes, setEventLeadMinutes] = useState(15); const [eventLeadMinutes, setEventLeadMinutes] = useState(15);
const [remindersEnabled, setRemindersEnabled] = useState(false); const [remindersEnabled, setRemindersEnabled] = useState(true);
const [todosEnabled, setTodosEnabled] = useState(false); const [todosEnabled, setTodosEnabled] = useState(true);
const [todoLeadDays, setTodoLeadDays] = useState(0); const [todoLeadDays, setTodoLeadDays] = useState(1);
const [projectsEnabled, setProjectsEnabled] = useState(false); const [projectsEnabled, setProjectsEnabled] = useState(true);
const [projectLeadDays, setProjectLeadDays] = useState(0); const [projectLeadDays, setProjectLeadDays] = useState(2);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [isTestingNtfy, setIsTestingNtfy] = useState(false); const [isTestingNtfy, setIsTestingNtfy] = useState(false);