Add multi-user auth, admin panel, and timezone support; rename to Yolkbook

- Rename app from Eggtracker to Yolkbook throughout
- Add JWT-based authentication (python-jose, passlib/bcrypt)
- Add users table; all data tables gain user_id FK for full data isolation
- Super admin credentials sourced from ADMIN_USERNAME/ADMIN_PASSWORD env vars,
  synced on every startup; orphaned rows auto-assigned to admin post-migration
- Login page with self-registration; JWT stored in localStorage (30-day expiry)
- Admin panel (/admin): list users, reset passwords, disable/enable, delete,
  and impersonate (Login As) with Return to Admin banner
- Settings modal (gear icon in nav): timezone selector and change password
- Timezone stored per-user; stats date windows computed in user's timezone;
  date input setToday() respects user timezone via Intl API
- migrate_v2.sql for existing single-user installs
- Auto-migration adds timezone column to users on startup
- Updated README with full setup, auth, admin, and migration docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 23:19:29 -07:00
parent 7d50af0054
commit aa12648228
31 changed files with 1572 additions and 140 deletions

View File

@@ -4,6 +4,44 @@ from typing import Optional
from pydantic import BaseModel, Field
# ── Auth ──────────────────────────────────────────────────────────────────────
class LoginRequest(BaseModel):
username: str
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class ChangePasswordRequest(BaseModel):
current_password: str
new_password: str = Field(min_length=6)
class ResetPasswordRequest(BaseModel):
new_password: str = Field(min_length=6)
class TimezoneUpdate(BaseModel):
timezone: str = Field(min_length=1, max_length=64)
# ── Users ─────────────────────────────────────────────────────────────────────
class UserCreate(BaseModel):
username: str = Field(min_length=2, max_length=64)
password: str = Field(min_length=6)
class UserOut(BaseModel):
id: int
username: str
is_admin: bool
is_disabled: bool
timezone: str
created_at: datetime
model_config = {"from_attributes": True}
# ── Egg Collections ───────────────────────────────────────────────────────────
class EggCollectionCreate(BaseModel):