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>
65 lines
1.7 KiB
Python
65 lines
1.7 KiB
Python
"""
|
||
Database engine and session factory.
|
||
|
||
We use SQLAlchemy 2.x with the "declarative" ORM style. All models should
|
||
inherit from ``Base`` defined here.
|
||
|
||
Session lifecycle:
|
||
- ``get_db()`` is a FastAPI dependency that opens a session per request and
|
||
guarantees it is closed (and rolled back on error) when the request ends.
|
||
"""
|
||
from sqlalchemy import create_engine
|
||
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
||
|
||
from app.config import settings
|
||
|
||
|
||
# connect_args is SQLite-specific: it allows the same connection to be used
|
||
# from multiple threads, which is required because FastAPI runs in a thread
|
||
# pool. This flag is harmless / unused for PostgreSQL.
|
||
_connect_args = (
|
||
{"check_same_thread": False}
|
||
if settings.database_url.startswith("sqlite")
|
||
else {}
|
||
)
|
||
|
||
engine = create_engine(settings.database_url, connect_args=_connect_args)
|
||
|
||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||
|
||
|
||
class Base(DeclarativeBase):
|
||
"""All ORM models inherit from this class."""
|
||
pass
|
||
|
||
|
||
def get_db():
|
||
"""
|
||
FastAPI dependency that yields a database session.
|
||
|
||
Usage::
|
||
|
||
@router.get("/things")
|
||
def list_things(db: Session = Depends(get_db)):
|
||
...
|
||
"""
|
||
db = SessionLocal()
|
||
try:
|
||
yield db
|
||
finally:
|
||
db.close()
|
||
|
||
|
||
def create_tables():
|
||
"""Create all tables that are registered on Base.metadata.
|
||
|
||
Called once at application startup (see main.py). Safe to run multiple
|
||
times – SQLAlchemy will not recreate existing tables.
|
||
"""
|
||
# Importing models here ensures they are registered on Base.metadata
|
||
# before create_all() is called.
|
||
import app.models.user # noqa: F401
|
||
import app.models.item # noqa: F401
|
||
|
||
Base.metadata.create_all(bind=engine)
|