Three things needed for the optical-dev rollout. 1) Path migration /osop/ → /oliver-sales-ops-platform/ The public URL is https://optical-dev.oliver.solutions/oliver-sales-ops-platform/ Updated the basename across: - Vite base + proxy match - React Router basename - axios baseURL - MSAL redirectUri (preserves SSO when wired) - downloadQAPack URL - Backend app_path_prefix default - docker-compose APP_PATH_PREFIX default - Apache Location blocks 2) DEV_AUTH admin user Auth middleware now reads DEV_AUTH_EMAIL / DEV_AUTH_NAME / DEV_AUTH_ROLE when DEV_AUTH_BYPASS=true (defaults preserve the old dev@localhost / editor behaviour). The dev_bypass identity also promotes existing AppUser rows to admin if the env says so — so no manual SQL on the server when we want a different account exposed. New backend/scripts/seed_admin.py is idempotent and runs from start.sh after Alembic migrations. It upserts the configured DEV_AUTH_EMAIL with role=admin (or whatever DEV_AUTH_ROLE says). Smoke-tested locally: /api/users/me now returns admin@oliver.agency / role=admin. 3) Deploy assets under deploy/ - apache-osop.conf — drop-in vhost block (Location /…/api/ → 8003, Location /…/ → 3011, ProxyTimeout 300, redirect bare prefix to /). Sits alongside the existing /gsb/ V1 block on the same vhost. - deploy.sh — idempotent script: * sanity (.env present, docker on PATH) * port-conflict check (5435 db, 6380 redis, 8003 backend, 3011 frontend) — if our compose project is already running, skips the lsof check because the ports are ours; otherwise warns and exits if anything else is listening * git pull --ff-only (skip with --no-pull) * docker compose build && up -d (skip with --no-build) * health-poll backend /api/health for up to 60s * frontend probe at the new path * prints local + public URLs and the admin email on success V1 host ports (5432/8002/3010) and V2 host ports (5435/6380/8003/3011) are non-overlapping by design, so both stacks coexist on the same dev server. CLAUDE.md naming policy is satisfied — docker-compose.yml has name: oliver-sales-ops-platform pinned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""Idempotent admin-user seed script.
|
|
|
|
Runs at container start (after Alembic migrations) to ensure the dev-bypass
|
|
admin email exists as an AppUser with role=admin. If the user already
|
|
exists with a lower role, this script promotes them.
|
|
|
|
Reads the same DEV_AUTH_* env vars the auth middleware uses, so they stay
|
|
in sync. Pure-sync (psycopg2) — no async stack at boot.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
# Make sure /app is on the path when this is invoked from start.sh.
|
|
sys.path.insert(0, "/app")
|
|
|
|
from sqlalchemy import create_engine, text # noqa: E402
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s [%(name)s] %(message)s")
|
|
|
|
|
|
def main() -> None:
|
|
bypass = os.environ.get("DEV_AUTH_BYPASS", "").lower() in ("1", "true", "yes")
|
|
if not bypass:
|
|
logger.info("DEV_AUTH_BYPASS not set — skipping admin seed (real SSO will provision users on login)")
|
|
return
|
|
|
|
email = (os.environ.get("DEV_AUTH_EMAIL") or "admin@oliver.agency").strip().lower()
|
|
name = os.environ.get("DEV_AUTH_NAME") or "OSOP Admin"
|
|
role = (os.environ.get("DEV_AUTH_ROLE") or "admin").strip().lower()
|
|
if role not in {"viewer", "editor", "admin"}:
|
|
logger.warning("Unknown DEV_AUTH_ROLE=%s — falling back to admin", role)
|
|
role = "admin"
|
|
|
|
sync_url = os.environ.get("DATABASE_URL_SYNC")
|
|
if not sync_url:
|
|
logger.error("DATABASE_URL_SYNC not set — cannot seed admin")
|
|
return
|
|
|
|
engine = create_engine(sync_url)
|
|
try:
|
|
with engine.begin() as conn:
|
|
existing = conn.execute(
|
|
text("SELECT id, role FROM app_users WHERE email = :email"),
|
|
{"email": email},
|
|
).fetchone()
|
|
|
|
if existing is None:
|
|
conn.execute(
|
|
text(
|
|
"INSERT INTO app_users (email, name, role, last_login) "
|
|
"VALUES (:email, :name, CAST(:role AS userrole), NOW())"
|
|
),
|
|
{"email": email, "name": name, "role": role.upper()},
|
|
)
|
|
logger.info("Seeded AppUser %s (role=%s)", email, role)
|
|
elif existing.role.lower() != role and role == "admin":
|
|
conn.execute(
|
|
text("UPDATE app_users SET role = CAST(:role AS userrole) WHERE id = :id"),
|
|
{"role": role.upper(), "id": existing.id},
|
|
)
|
|
logger.info("Promoted existing AppUser %s to %s", email, role)
|
|
else:
|
|
logger.info("AppUser %s already exists with role=%s — no change", email, existing.role.lower())
|
|
finally:
|
|
engine.dispose()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|