Remove PostgreSQL and move to SQLite3

This commit is contained in:
2026-08-01 18:02:37 +02:00
parent 97ad77f2a4
commit 873627c757
12 changed files with 444 additions and 64 deletions
+1
View File
@@ -1,6 +1,7 @@
FROM python:3.12-slim
WORKDIR /app
RUN mkdir -p /app/data
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
+18 -3
View File
@@ -1,10 +1,25 @@
import os
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
DATABASE_URL = os.environ["DATABASE_URL"]
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./data/cluedo.db")
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
engine_options = {"pool_pre_ping": True}
if DATABASE_URL.startswith("sqlite"):
# SQLite does not allow connections to be used from another thread by
# default. FastAPI/SQLAlchemy may use a connection across request threads.
engine_options["connect_args"] = {"check_same_thread": False}
# Make the parent directory available for the default local database.
if DATABASE_URL.startswith("sqlite:///"):
sqlite_path = DATABASE_URL.removeprefix("sqlite:///")
if sqlite_path not in (":memory:", ""):
Path(sqlite_path).expanduser().parent.mkdir(parents=True, exist_ok=True)
engine = create_engine(DATABASE_URL, **engine_options)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
class Base(DeclarativeBase):
@@ -16,4 +31,4 @@ def get_db():
yield db
finally:
db.close()
+4 -22
View File
@@ -1,4 +1,3 @@
import os
import random
import string
@@ -8,11 +7,11 @@ from sqlalchemy import text
from sqlalchemy.orm import Session
from .db import Base, engine, SessionLocal
from .models import User, Entry, Category, Role, Game, GameMember
from .security import hash_password
from .models import User, Entry, Category, Game, GameMember
from .routes.auth import router as auth_router
from .routes.admin import router as admin_router
from .routes.games import router as games_router
from .routes.setup import router as setup_router
app = FastAPI(title="Cluedo Sheet")
@@ -31,6 +30,7 @@ app.add_middleware(
app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(games_router)
app.include_router(setup_router)
def _rand_join_code(n: int = 6) -> str:
@@ -276,23 +276,6 @@ def seed_entries(db: Session):
db.commit()
def ensure_admin(db: Session):
admin_email = os.environ.get("ADMIN_EMAIL", "admin@local").lower().strip()
admin_pw = os.environ.get("ADMIN_PASSWORD", "ChangeMeNow123!")
u = db.query(User).filter(User.email == admin_email).first()
if not u:
db.add(
User(
email=admin_email,
password_hash=hash_password(admin_pw),
role=Role.admin.value,
theme_key="default",
display_name="Admin",
)
)
db.commit()
@app.on_event("startup")
def on_startup():
# create new tables
@@ -301,8 +284,7 @@ def on_startup():
db = SessionLocal()
try:
_auto_migrate(db)
ensure_admin(db)
seed_entries(db)
finally:
db.close()
+58
View File
@@ -0,0 +1,58 @@
import re
from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy.orm import Session
from ..db import get_db
from ..models import Role, User
from ..security import hash_password, make_session_value, set_session
router = APIRouter(prefix="/setup", tags=["setup"])
def _has_admin(db: Session) -> bool:
return db.query(User).filter(User.role == Role.admin.value).first() is not None
@router.get("/status")
def setup_status(db: Session = Depends(get_db)):
return {"setup_required": not _has_admin(db)}
@router.post("/admin")
def create_initial_admin(
data: dict,
resp: Response,
db: Session = Depends(get_db),
):
# The setup endpoint is only open until the first admin exists.
if _has_admin(db):
raise HTTPException(status_code=409, detail="setup already completed")
email = (data.get("email") or "").lower().strip()
display_name = (data.get("display_name") or "").strip()
password = data.get("password") or ""
if not re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", email):
raise HTTPException(status_code=400, detail="valid email required")
if len(password) < 8:
raise HTTPException(status_code=400, detail="password too short (min 8)")
if not display_name:
display_name = email.split("@", 1)[0]
if db.query(User).filter(User.email == email).first():
raise HTTPException(status_code=409, detail="email exists")
user = User(
email=email,
password_hash=hash_password(password),
role=Role.admin.value,
display_name=display_name,
)
db.add(user)
db.commit()
db.refresh(user)
# Log the installer in immediately after successful setup.
set_session(resp, make_session_value(user.id))
return {"ok": True, "id": user.id, "email": user.email}
-1
View File
@@ -1,7 +1,6 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
SQLAlchemy==2.0.34
psycopg[binary]==3.2.2
python-multipart==0.0.9
passlib==1.7.4