Remove PostgreSQL and move to SQLite3
This commit is contained in:
+18
-3
@@ -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
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user