Seed public demo scenarios on startup
Idempotent seeder inserts public, unowned demo scenarios from backend/app/seed_data/*.json if a public scenario with the same title is missing, so a fresh Neon database (and any clone) gets demo content guests can play immediately. Ships two scenarios: the Sunken Crypt of Vharos (all four script hooks) and Signal from the Derelict (sci-fi, context-engine focused). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
68c165585c
commit
c96b8cdf2f
@@ -0,0 +1,119 @@
|
||||
"""Seed public demo scenarios on startup.
|
||||
|
||||
Every JSON file in ``seed_data/`` describes one demo scenario in the same
|
||||
model-native shape the export endpoint produces. Seeded scenarios have a NULL
|
||||
owner and ``is_public=True``, so every visitor (including guests) sees them and
|
||||
can start an adventure from them, while nobody can edit them. Starting an
|
||||
adventure copies the scenario's story cards and scripts into the adventure, so
|
||||
the seeded scripts run for guests too.
|
||||
|
||||
The seed is idempotent: a scenario is inserted only if no public, unowned
|
||||
scenario with the same title already exists, so it is safe to run on every boot
|
||||
and it re-heals a database that lost its demo content.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
from . import models
|
||||
from .database import SessionLocal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SEED_DIR = Path(__file__).resolve().parent / "seed_data"
|
||||
|
||||
|
||||
def seed_public_scenarios(engine: Engine) -> None:
|
||||
if not SEED_DIR.is_dir():
|
||||
return
|
||||
files = sorted(SEED_DIR.glob("*.json"))
|
||||
if not files:
|
||||
return
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
created = 0
|
||||
for path in files:
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
logger.warning("Skipping seed file %s: %s", path.name, exc)
|
||||
continue
|
||||
|
||||
title = (data.get("title") or "").strip()
|
||||
if not title:
|
||||
continue
|
||||
|
||||
exists = (
|
||||
db.query(models.Scenario)
|
||||
.filter(
|
||||
models.Scenario.title == title,
|
||||
models.Scenario.user_id.is_(None),
|
||||
models.Scenario.is_public.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if exists is not None:
|
||||
continue
|
||||
|
||||
_insert_scenario(db, data)
|
||||
created += 1
|
||||
|
||||
if created:
|
||||
db.commit()
|
||||
logger.info("Seeded %d public demo scenario(s).", created)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
# A seed failure must never take the app down; log and carry on.
|
||||
logger.exception("Seeding public scenarios failed; continuing without them.")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _insert_scenario(db, data: dict) -> None:
|
||||
scenario = models.Scenario(
|
||||
user_id=None,
|
||||
is_public=True,
|
||||
title=data.get("title", ""),
|
||||
description=data.get("description", ""),
|
||||
prompt=data.get("prompt", ""),
|
||||
memory=data.get("memory", ""),
|
||||
authors_note=data.get("authors_note", ""),
|
||||
ai_instructions=data.get("ai_instructions", ""),
|
||||
tags=data.get("tags", ""),
|
||||
)
|
||||
db.add(scenario)
|
||||
db.flush()
|
||||
|
||||
for card in data.get("story_cards") or []:
|
||||
if not isinstance(card, dict):
|
||||
continue
|
||||
db.add(
|
||||
models.StoryCard(
|
||||
scenario_id=scenario.id,
|
||||
type=card.get("type", ""),
|
||||
name=card.get("name", ""),
|
||||
keys=card.get("keys", ""),
|
||||
entry=card.get("entry", ""),
|
||||
notes=card.get("notes", ""),
|
||||
)
|
||||
)
|
||||
|
||||
for item in data.get("scripts") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
script = models.Script(
|
||||
user_id=None,
|
||||
name=item.get("name", "Script"),
|
||||
description=item.get("description", ""),
|
||||
library_js=item.get("library_js", ""),
|
||||
input_js=item.get("input_js", ""),
|
||||
context_js=item.get("context_js", ""),
|
||||
output_js=item.get("output_js", ""),
|
||||
)
|
||||
db.add(script)
|
||||
db.flush()
|
||||
scenario.scripts.append(script)
|
||||
Reference in New Issue
Block a user