Add super admin panel and update README
- Admin account bootstrapped from ADMIN_EMAIL/ADMIN_PASSWORD env vars on startup - Admin panel: list users, view content, reset passwords, disable/delete accounts - is_admin and is_disabled columns on users table - Disabled accounts blocked at login - README updated with admin setup instructions and panel docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -7,10 +8,11 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from database import SessionLocal
|
||||
from models import Settings, NotificationLog
|
||||
from models import Settings, NotificationLog, User
|
||||
from routers import varieties, batches, dashboard, settings, notifications
|
||||
from routers import auth as auth_router
|
||||
from routers import auth as auth_router, admin as admin_router
|
||||
from routers.notifications import build_daily_summary, send_ntfy
|
||||
from auth import hash_password
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("sproutly")
|
||||
@@ -49,8 +51,37 @@ def get_notification_schedule(db) -> tuple[int, int]:
|
||||
return 7, 0
|
||||
|
||||
|
||||
def bootstrap_admin(retries: int = 5, delay: float = 2.0):
|
||||
import time
|
||||
admin_email = os.environ.get("ADMIN_EMAIL")
|
||||
admin_password = os.environ.get("ADMIN_PASSWORD")
|
||||
if not admin_email or not admin_password:
|
||||
return
|
||||
for attempt in range(retries):
|
||||
db = SessionLocal()
|
||||
try:
|
||||
admin = db.query(User).filter(User.email == admin_email).first()
|
||||
if admin:
|
||||
admin.hashed_password = hash_password(admin_password)
|
||||
admin.is_admin = True
|
||||
admin.is_disabled = False
|
||||
else:
|
||||
admin = User(email=admin_email, hashed_password=hash_password(admin_password), is_admin=True)
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
logger.info(f"Admin user ready: {admin_email}")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(f"Admin bootstrap attempt {attempt + 1} failed: {e}")
|
||||
time.sleep(delay)
|
||||
finally:
|
||||
db.close()
|
||||
logger.error("Admin bootstrap failed after all retries")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
bootstrap_admin()
|
||||
db = SessionLocal()
|
||||
hour, minute = get_notification_schedule(db)
|
||||
db.close()
|
||||
@@ -77,6 +108,7 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
app.include_router(auth_router.router)
|
||||
app.include_router(admin_router.router)
|
||||
app.include_router(varieties.router)
|
||||
app.include_router(batches.router)
|
||||
app.include_router(dashboard.router)
|
||||
|
||||
Reference in New Issue
Block a user