Files
flipventory_v2/app/schemas/user.py
Martin Hohenberg 4e0d0d3962 feat: initial FastAPI stub with auth, user management, and inventory CRUD
Sets up the complete application skeleton for Flipventory v2, an inventory
management tool for small/medium resellers using chaotic stockpiling.

Architecture
------------
- app/config.py         pydantic-settings; reads .env; holds admin_email guard
- app/database.py       SQLAlchemy 2.x engine, SessionLocal, Base, get_db dep
- app/models/user.py    User (email, bcrypt hash, is_admin bool, is_active)
- app/models/item.py    StorageBin + Item with FOUND/LISTED/SOLD/ARCHIVED enum
- app/schemas/          Pydantic request/response shapes (separate from models)
- app/auth/security.py  bcrypt password hashing + HS256 JWT creation/validation
- app/routers/auth.py   POST /auth/register, POST /auth/login, GET /auth/me
- app/routers/users.py  Admin-only CRUD for user accounts
- app/routers/items.py  /bins and /items CRUD with role-based access control

Key design decisions
--------------------
- martin@hohenberg.jp is ALWAYS admin: enforced on every login, cannot be
  demoted, cannot be deactivated. Prevents accidental lockout.
- All other registered e-mails are customers (is_admin=False).
- Customers can manage their own bins and items; admins see everything.
- Item hard-delete is admin-only; customers archive instead (audit safety).
- SQLite default for zero-infra local dev; any SQLAlchemy URL works in prod.
- Tables are created on startup via create_tables(); swap for Alembic later.

Addresses v1 Gitea issues
--------------------------
- Issue #1 (storage bins): StorageBin model + /bins CRUD endpoints
- Issue #2 (item CRUD): Item model + /items CRUD endpoints
- Issue #3 (audit logging): created_by_id / updated_by_id on every item row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:40:53 +02:00

68 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Pydantic schemas for the User resource.
Schemas vs. models
-------------------
SQLAlchemy *models* define how data is stored in the database.
Pydantic *schemas* define what comes in over the API (request bodies) and
what goes out (response shapes). Keeping them separate means we can expose
only the fields we want and add validation logic without touching the DB layer.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, EmailStr, field_validator
class UserCreate(BaseModel):
"""Request body for POST /auth/register."""
email: EmailStr
display_name: str
password: str
@field_validator("password")
@classmethod
def password_min_length(cls, v: str) -> str:
if len(v) < 8:
raise ValueError("Password must be at least 8 characters long")
return v
@field_validator("display_name")
@classmethod
def display_name_not_empty(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("Display name must not be empty")
return v
class UserRead(BaseModel):
"""Response schema safe to expose publicly (no password hash)."""
model_config = ConfigDict(from_attributes=True)
id: int
email: EmailStr
display_name: str
is_admin: bool
is_active: bool
created_at: datetime
class UserUpdate(BaseModel):
"""Request body for PATCH /users/{id} all fields optional."""
display_name: str | None = None
is_active: bool | None = None
# Admins can promote/demote other users here; the business rule about
# settings.admin_email always being admin is enforced in the router.
is_admin: bool | None = None
class TokenResponse(BaseModel):
"""Response body for POST /auth/login."""
access_token: str
token_type: str = "bearer"
user: UserRead