seed_production.py
python
| 1 | """ |
| 2 | Production seed for musehub.ai |
| 3 | |
| 4 | Creates gabriel's account and profile only. |
| 5 | The muse repo is pushed from the actual codebase via `muse push`. |
| 6 | |
| 7 | Idempotent: safe to re-run (skips existing records). |
| 8 | |
| 9 | Run inside the container: |
| 10 | docker compose exec musehub python3 /app/scripts/seed_production.py |
| 11 | """ |
| 12 | from __future__ import annotations |
| 13 | |
| 14 | import asyncio |
| 15 | import hashlib |
| 16 | import uuid |
| 17 | from datetime import datetime, timedelta, timezone |
| 18 | |
| 19 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine |
| 20 | from sqlalchemy.orm import sessionmaker |
| 21 | from sqlalchemy import select, delete |
| 22 | |
| 23 | from musehub.config import settings |
| 24 | from musehub.db.models import User |
| 25 | from musehub.db.musehub_models import ( |
| 26 | MusehubBranch, |
| 27 | MusehubCommit, |
| 28 | MusehubIssue, |
| 29 | MusehubProfile, |
| 30 | MusehubRelease, |
| 31 | MusehubRepo, |
| 32 | ) |
| 33 | |
| 34 | UTC = timezone.utc |
| 35 | |
| 36 | |
| 37 | def _now(days: int = 0) -> datetime: |
| 38 | return datetime.now(tz=UTC) - timedelta(days=days) |
| 39 | |
| 40 | |
| 41 | def _uid(seed: str) -> str: |
| 42 | return str(uuid.UUID(bytes=hashlib.md5(seed.encode()).digest())) |
| 43 | |
| 44 | |
| 45 | GABRIEL_ID = _uid("prod-gabriel-cgcardona") |
| 46 | |
| 47 | # Slugs seeded in the past that should be removed if still present. |
| 48 | REMOVED_SLUGS = [ |
| 49 | "muse", |
| 50 | "musehub", |
| 51 | "agentception", |
| 52 | "maestro", |
| 53 | "stori", |
| 54 | "well-tempered-clavier", |
| 55 | "moonlight-sonata", |
| 56 | "neobaroque-sketches", |
| 57 | "modal-sessions", |
| 58 | "chopin-nocturnes", |
| 59 | ] |
| 60 | |
| 61 | |
| 62 | async def seed() -> None: |
| 63 | db_url = settings.database_url or "sqlite+aiosqlite:///./muse.db" |
| 64 | engine = create_async_engine(db_url) |
| 65 | Session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) |
| 66 | |
| 67 | async with Session() as session: |
| 68 | |
| 69 | # ── 1. Remove any previously-seeded repos ───────────────────────────── |
| 70 | for slug in REMOVED_SLUGS: |
| 71 | result = await session.execute( |
| 72 | select(MusehubRepo).where( |
| 73 | MusehubRepo.owner == "gabriel", |
| 74 | MusehubRepo.slug == slug, |
| 75 | ) |
| 76 | ) |
| 77 | repo = result.scalar_one_or_none() |
| 78 | if repo: |
| 79 | rid = repo.repo_id |
| 80 | await session.execute(delete(MusehubCommit).where(MusehubCommit.repo_id == rid)) |
| 81 | await session.execute(delete(MusehubBranch).where(MusehubBranch.repo_id == rid)) |
| 82 | await session.execute(delete(MusehubIssue).where(MusehubIssue.repo_id == rid)) |
| 83 | await session.execute(delete(MusehubRelease).where(MusehubRelease.repo_id == rid)) |
| 84 | await session.delete(repo) |
| 85 | print(f"[-] Removed gabriel/{slug}") |
| 86 | |
| 87 | await session.flush() |
| 88 | |
| 89 | # ── 2. Gabriel's core user record ───────────────────────────────────── |
| 90 | existing_user = await session.get(User, GABRIEL_ID) |
| 91 | if existing_user is None: |
| 92 | session.add(User(id=GABRIEL_ID, created_at=_now(365))) |
| 93 | print("[+] Created User: gabriel") |
| 94 | else: |
| 95 | print("[=] User gabriel already exists") |
| 96 | |
| 97 | # ── 3. Gabriel's public profile ─────────────────────────────────────── |
| 98 | existing_profile = await session.get(MusehubProfile, GABRIEL_ID) |
| 99 | if existing_profile is None: |
| 100 | session.add(MusehubProfile( |
| 101 | user_id=GABRIEL_ID, |
| 102 | username="gabriel", |
| 103 | display_name="Gabriel Cardona", |
| 104 | bio=( |
| 105 | "Building Muse — a domain-agnostic VCS for multidimensional state. " |
| 106 | "Code, music, narrative, genomics. If it changes over time, Muse can version it." |
| 107 | ), |
| 108 | location="San Francisco, CA", |
| 109 | website_url="https://musehub.ai", |
| 110 | twitter_handle="cgcardona", |
| 111 | is_verified=True, |
| 112 | pinned_repo_ids=[], |
| 113 | created_at=_now(365), |
| 114 | )) |
| 115 | print("[+] Created Profile: gabriel") |
| 116 | else: |
| 117 | print("[=] Profile gabriel already exists") |
| 118 | |
| 119 | await session.commit() |
| 120 | |
| 121 | print() |
| 122 | print("=" * 60) |
| 123 | print("SEED COMPLETE — push repos via `muse push`") |
| 124 | print("=" * 60) |
| 125 | print() |
| 126 | print("Mint your admin JWT:") |
| 127 | print() |
| 128 | print(" docker compose exec musehub python3 -c \"") |
| 129 | print(" from musehub.auth.tokens import generate_access_code") |
| 130 | print(f" print(generate_access_code(user_id='{GABRIEL_ID}', duration_days=365, is_admin=True))") |
| 131 | print(" \"") |
| 132 | print() |
| 133 | |
| 134 | |
| 135 | if __name__ == "__main__": |
| 136 | asyncio.run(seed()) |