⚙ Engineer view
Implementation detail, patterns, dependencies, gotchas · Software Engineer, Tech Lead
⚡ Agent trace · 13 steps · $0.0504 · click to expand
search_claims(query='module overview architecture purpose', category='informational') →
search_claims(query='main entry point application', category='structural') →
search_claims(category='informational', max_results=30) →
get_file_claims(file_path='backend/app/main.py') →
search_claims(category='structural', max_results=30) →
get_file_claims(file_path='backend/app/api/main.py') →
get_file_claims(file_path='backend/app/api/deps.py') →
search_claims(category='structural', max_results=30) →
search_claims(category='structural', max_results=20) →
get_file_claims(file_path='backend/app/models.py') →
search_claims(category='structural', max_results=20) →
search_claims(category='structural', max_results=20) →
get_file_claims(file_path='backend/app/core/config.py') →
search_claims(category='structural', max_results=20) →
get_file_claims(file_path='backend/app/core/security.py') →
get_file_claims(file_path='backend/app/api/routes/users.py') →
get_file_claims(file_path='backend/app/api/routes/items.py') →
get_file_claims(file_path='backend/app/crud.py') →
get_file_claims(file_path='backend/app/api/routes/login.py') →
get_file_claims(file_path='backend/app/utils.py') →
get_file_claims(file_path='backend/app/core/db.py') →
get_file_claims(file_path='frontend/src/main.tsx') →
get_file_claims(file_path='frontend/src/routes/_layout.tsx') →
get_file_claims(file_path='frontend/src/client/core/OpenAPI.ts') →
get_dependencies(file_path='backend/app/api/routes/users.py') →
get_file_claims(file_path='frontend/src/hooks/useAuth.ts') →
get_file_claims(file_path='frontend/src/components/Sidebar/AppSidebar.tsx') →
get_file_claims(file_path='frontend/src/routes/_layout/index.tsx') →
get_file_claims(file_path='frontend/src/routes/_layout/items.tsx') →
get_file_claims(file_path='frontend/src/components/theme-provider.tsx') →
get_file_claims(file_path='frontend/src/routes/login.tsx') →
get_file_claims(file_path='backend/app/api/routes/private.py') →
get_file_claims(file_path='backend/app/initial_data.py') →
get_file_claims(file_path='frontend/src/routes/_layout/settings.tsx') →
get_file_claims(file_path='frontend/src/components/Items/columns.tsx') →
get_file_claims(file_path='frontend/src/components/Items/AddItem.tsx') →
get_file_claims(file_path='frontend/src/components/Items/EditItem.tsx') →
get_file_claims(file_path='frontend/src/components/DataTable.tsx') →
get_file_claims(file_path='frontend/src/components/Items/ItemActionsMenu.tsx') →
search_claims(category='structural', max_results=20) →
search_claims(category='structural', max_results=20) →
get_file_claims(file_path='backend/tests/conftest.py') →
get_file_claims(file_path='backend/tests/crud/test_user.py') →
search_claims(category='structural', max_results=15) →
search_claims(category='structural', max_results=15)
Engineer's Guide
Module Map
| Module/File | Role | Key Responsibilities | In-Degree |
|---|---|---|---|
backend/app/main.py |
FastAPI app factory | Creates app, CORS, Sentry init, mounts API router | Root |
backend/app/api/main.py |
API router aggregator | Includes login/users/utils/items routers; conditionally private router | 1 (main) |
backend/app/api/deps.py |
Dependency injection | get_db, get_current_user, get_current_active_superuser, OAuth2 bearer |
High (all routes) |
backend/app/models.py |
SQLModel schema + ORM | User/Item tables, all Pydantic request/response models, Token, Message | High |
backend/app/crud.py |
Data access layer | create_user, update_user, get_user_by_email, authenticate, create_item |
High (routes, db init) |
backend/app/core/config.py |
Settings (pydantic-settings) | Env parsing, CORS origins, DB URI, secret enforcement, email config | Global |
backend/app/core/security.py |
Password + JWT | Argon2/Bcrypt hashing, create_access_token, verify_password |
High |
backend/app/core/db.py |
Engine + init | SQLAlchemy engine, init_db (creates first superuser) |
High |
backend/app/api/routes/users.py |
User endpoints | CRUD users, /me self-service, signup, superuser admin |
Medium |
backend/app/api/routes/items.py |
Item endpoints | CRUD items with owner scoping | Medium |
backend/app/api/routes/login.py |
Auth endpoints | Access token, test-token, password recovery/reset | Medium |
backend/app/api/routes/private.py |
Local-only user creation | POST /private/users/ — only mounted when ENVIRONMENT=local |
Low |
backend/app/api/routes/utils.py |
Health + email test | /health-check/, /test-email/ (superuser) |
Low |
backend/app/utils.py |
Email + token helpers | Jinja2 email templates, SMTP send, password-reset JWT | Medium |
frontend/src/main.tsx |
React bootstrap | Sets OpenAPI base/token, error handling, QueryClient, router | Root |
frontend/src/hooks/useAuth.ts |
Auth state | isLoggedIn, useAuth (login/signup/logout/user query) |
High |
frontend/src/client/ |
Auto-generated SDK | sdk.gen.ts, types.gen.ts, core/OpenAPI.ts (hey-api/openapi-ts) |
High |
frontend/src/routes/_layout.tsx |
Authenticated layout | Sidebar + header + Outlet; beforeLoad auth guard |
Medium |
frontend/src/components/Common/DataTable.tsx |
Generic table | TanStack Table wrapper | Medium |
frontend/src/components/Items/* |
Item UI | Add/Edit/Delete dialogs, columns, actions menu | Medium |
frontend/src/components/Admin/* |
Admin UI | User management table, delete user | Low |
frontend/src/components/UserSettings/* |
Profile UI | UserInformation, ChangePassword, DeleteAccount | Low |
backend/tests/ + frontend/tests/ |
Test suites | Pytest (backend), Playwright (frontend) | — |
Design Patterns in Use
1. FastAPI dependency injection with type aliases — backend/app/api/deps.py:21-57 defines generator/function dependencies and aliases them as SessionDep, TokenDep, CurrentUser, CurrentSuperuser. Routes declare these as annotated parameters (e.g., users.py:37). This is the canonical FastAPI pattern and is applied consistently across all route modules.
2. SQLModel single-model pattern — backend/app/models.py uses SQLModel's inheritance to define a base schema (UserBase), then derives both the ORM table (User, table=True) and request/response models (UserCreate, UserPublic, etc.) from it. This avoids duplicating field definitions. Applied consistently for both User and Item.
3. CRUD layer separation — backend/app/crud.py isolates all database mutations from route handlers. Routes call crud.create_user, crud.authenticate, etc. rather than touching the session directly. Not fully consistent: items.py routes perform their own session commit/refresh inline (items.py:69-71), and private.py:35-36 also commits directly, bypassing the CRUD layer.
4. Frontend: TanStack Query + auto-generated SDK — All data fetching goes through useQuery/useMutation calling static methods on ItemsService/UsersService/LoginService from the auto-generated sdk.gen.ts. Query invalidation is done via ['items'] / ['users'] keys after mutations (e.g., AddItem.tsx:61-63).
5. File-based routing (TanStack Router) — Routes are defined per-file under frontend/src/routes/ with createFileRoute. The _layout prefix creates a layout route group; beforeLoad guards redirect unauthenticated users (_layout.tsx:10-15).
6. shadcn/ui component pattern — frontend/src/components/ui/* are shadcn-style components using cva (class-variance-authority) for variants and cn for class merging. Consistent across button, badge, alert, card, table, etc.
Critical Implementation Details
1. Password hashing is dual-algorithm with automatic upgrade. backend/app/core/security.py:12-16 creates a PasswordHash with both Argon2Hasher and BcryptHasher. verify_password returns (bool, updated_hash) — when a legacy bcrypt hash is verified, crud.authenticate (crud.py:45-60) transparently re-hashes with Argon2 and persists it. This is verified by backend/tests/crud/test_user.py:20-27.
2. Timing-attack protection on login. crud.py:40-41 defines DUMMY_HASH; authenticate runs password verification against it even when the user is not found (crud.py:45-60), so response time doesn't reveal whether an email exists.
3. Email enumeration is deliberately prevented. login.py:54-74 — recover_password always returns the same success message whether or not the user exists. But recover_password_html_content (login.py:99-123) raises 404 if the user is missing — this endpoint is superuser-only, so it's an intentional admin tool, but it does leak existence to superusers.
4. The private router is only mounted in local environments. api/main.py:12-13 conditionally includes private.router when settings.ENVIRONMENT == 'local'. This is the template's mechanism for creating users during local dev without exposing a public signup.
5. is_verified in PrivateUserCreate is silently ignored. private.py:30-33 accepts is_verified in the request model but never passes it to the User constructor — the field is dead input. Also, private.py has no error handling for DB integrity violations (duplicate email → unhandled 500).
6. Superuser cannot delete themselves. users.py:133-143 — DELETE /me raises 403 if the current user is a superuser. This is a deliberate guard against locking yourself out of admin.
7. Item authorization is owner-based, with superuser bypass. items.py:14-45 — superusers see all items; regular users only see their own. The GET/PUT/DELETE by ID endpoints (items.py:49-113) return 403 for non-owners, 404 for missing items.
8. model_dump(exclude_unset=True) for partial updates. items.py:89 — PUT only updates fields the client actually sent, so omitted fields aren't nulled out. Same pattern in crud.update_user (crud.py:20-31).
9. Frontend auth is localStorage-token-based, not cookie-based. main.tsx:18-20 reads access_token from localStorage for every request; handleApiError (main.tsx:22-26) clears the token and redirects to /login on 401/403. useAuth.ts:14-16 isLoggedIn() just checks for the presence of the key — it does not validate the token.
10. DB schema is created by Alembic migrations, not SQLModel. db.py:19-20 has the create_all call commented out. The init_db function only creates the first superuser — table creation is delegated to Alembic (migrations directory not shown in claims but implied by the commented-out code).
11. Settings is instantiated at module import time. config.py:26-116 — settings is a module-level singleton. This means env vars are read once at process start; changing .env requires a restart.
Entry Points — Start Here
-
backend/app/main.py— The FastAPI app factory. Read this first to understand the app's composition: Sentry, CORS, router mounting, and the custom OpenAPI ID function (main.py:10-11). -
backend/app/models.py— The data model is the heart of the domain. Understanding the SQLModel inheritance pattern (base → table → request/response) is essential before touching any route. -
backend/app/api/deps.py— The auth/dependency backbone. Every protected route flows throughget_current_user/get_current_active_superuser. Understanding the JWT decode + DB lookup flow here explains all authorization behavior. -
backend/app/crud.py— The data-access layer with the subtle timing-attack and hash-upgrade logic. Read this before modifying any user/item persistence. -
frontend/src/main.tsx+frontend/src/hooks/useAuth.ts— The frontend bootstrap and auth state. Together they explain how the token flows from login → localStorage → API calls → error handling → logout.
Known Gotchas
private.pyhas no error handling (private.py:24-38): duplicate email or DB constraint violations produce unhandled 500s. Alsois_verifiedis accepted but ignored — a silent contract violation.recover_password_html_contentleaks user existence (login.py:99-123): while the public recovery endpoint is enumeration-safe, this superuser-only endpoint raises 404 vs 200, which is inconsistent with the anti-enumeration stance.isLoggedIn()is a false sense of security (useAuth.ts:14-16): it only checks localStorage key presence. A stale/expired token passes the guard, then the API returns 401 andhandleApiErrorkicks the user out. Route guards are UX, not security.- Settings are frozen at import time (
config.py): changing env vars requires a process restart; there's no dynamic re-read. - The
dbtest fixture deletes all Items then all Users (conftest.py:16-24): tests share a single session-scoped DB. Tests that create users/items must be aware they're operating on a shared, cleaned-up-per-session database — parallel test execution would break this. - Frontend SDK is auto-generated (
frontend/src/client/index.ts:1): any backend API change requires regenerating the client (hey-api/openapi-ts). Hand-editingsdk.gen.tsortypes.gen.tswill be overwritten. settings.tsxhas a dead conditional (settings.tsx:27-29): the code checkscurrentUser.is_superuserto decide whether to filter tabs, but both branches assign the fulltabsConfig— the "danger-zone" tab is shown to everyone regardless. Either the filter logic is incomplete or the intent was to hide the delete-account tab from superusers.items.tsxfetches with hardcodedlimit=100(items.tsx:12-17): pagination is effectively capped at 100 items with no UI to page further. The backend supports skip/limit, but the frontend never exposes it.crud.authenticatemutates the user's hash on login (crud.py:45-60): every successful bcrypt login triggers a DB write to upgrade to Argon2. This is intentional but means login isn't read-only — worth knowing when profiling or debugging session behavior.