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>
This commit is contained in:
Martin Hohenberg
2026-06-16 10:40:53 +02:00
parent fe774c924f
commit 4e0d0d3962
19 changed files with 1332 additions and 0 deletions

82
app/main.py Normal file
View File

@@ -0,0 +1,82 @@
"""
Flipventory v2 FastAPI application entry point.
What is this?
--------------
Inventory management system for small-to-medium resellers who use a
"chaotic stockpiling" approach: buy first, sort later. Items are added
quickly, placed in physical storage bins, and gradually processed through
a FOUND → LISTED → SOLD lifecycle.
Architecture overview
----------------------
app/
config.py settings (secret key, DB URL, admin e-mail)
database.py SQLAlchemy engine + session factory
models/ ORM table definitions (user, item, storage_bin)
schemas/ Pydantic request/response shapes
auth/ JWT creation & FastAPI dependencies
routers/
auth.py /auth/register, /auth/login, /auth/me
users.py /users CRUD (admin only)
items.py /bins and /items CRUD
Running locally
----------------
uvicorn app.main:app --reload
Then open http://127.0.0.1:8000/docs for the interactive Swagger UI.
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.database import create_tables
from app.routers import auth, items, users
# ---------------------------------------------------------------------------
# Lifespan: run startup/shutdown logic without deprecated @app.on_event hooks
# ---------------------------------------------------------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: create DB tables if they don't exist yet.
# In a production deployment with Alembic migrations this call would be
# replaced by running `alembic upgrade head` in the CI/CD pipeline.
create_tables()
yield
# (Nothing to clean up on shutdown for now.)
# ---------------------------------------------------------------------------
# Application instance
# ---------------------------------------------------------------------------
app = FastAPI(
title="Flipventory v2",
description=(
"Inventory management for small/medium resellers. "
"Tame the chaos: add items fast, sort later, track every flip."
),
version="0.1.0",
lifespan=lifespan,
)
# ---------------------------------------------------------------------------
# Routers
# ---------------------------------------------------------------------------
app.include_router(auth.router)
app.include_router(users.router)
app.include_router(items.router)
# ---------------------------------------------------------------------------
# Health check
# ---------------------------------------------------------------------------
@app.get("/health", tags=["meta"])
def health() -> dict:
"""Simple liveness probe. Returns 200 if the server is up."""
return {"status": "ok", "version": app.version}