Phase 8: optional accounts, per-user data, shared demo key
Guest-first multi-user mode behind AIDND_MULTI_USER (local installs unchanged): signed-cookie guest sessions bootstrapped by /api/auth/me, register upgrades the guest in place, login/logout, per-IP rate limits. Every router scoped by user_id; Settings become per-user with the API key Fernet-encrypted at rest and write-only through the API. Users without a key get a server-funded demo key (OpenRouter free models, 20 turns/day, memory bank disabled on demo turns). Public read-only demo scenarios (seed_demo.py); debug log restricted to local mode. Frontend: auth modal + guest nudge, 401 re-establish/retry, demo banner and key management in Settings. Migrations 13-23 adopt existing data under a local user and encrypt stored keys. Verified: migration on a copy of real data.db, two-session isolation + register/login via curl and Chrome, demo cap 429, live OpenRouter turn through the encrypted-key path, vite build + oxlint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
This commit is contained in:
co-authored by
Claude Fable 5
parent
253b533d3b
commit
de4db373f2
+14
-4
@@ -2,9 +2,14 @@
|
||||
|
||||
Run from the backend folder: .venv\\Scripts\\python.exe seed_demo.py
|
||||
Safe to rerun: it deletes any previous rows titled "[Demo] ..." first.
|
||||
|
||||
Phase 8: the scenario is seeded as PUBLIC (user_id NULL + is_public), so in
|
||||
multi-user mode every guest sees it as read-only starter content. The sample
|
||||
adventure and script-library copies belong to the local user (only relevant
|
||||
on single-user installs).
|
||||
"""
|
||||
|
||||
from app import models, migrations
|
||||
from app import auth, models, migrations
|
||||
from app.database import SessionLocal, engine
|
||||
|
||||
# create_all + user_version stamp; plain create_all would leave a fresh DB at
|
||||
@@ -205,6 +210,8 @@ STORY_CARDS = [
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
owner = auth.local_user(db)
|
||||
|
||||
# Remove earlier demo rows so reruns stay clean.
|
||||
for adv in db.query(models.Adventure).filter(models.Adventure.title.like(f"{DEMO_PREFIX}%")):
|
||||
db.delete(adv)
|
||||
@@ -214,12 +221,14 @@ try:
|
||||
db.delete(s)
|
||||
db.commit()
|
||||
|
||||
# Script library
|
||||
# Scripts attached to the public scenario are unowned (user_id NULL) so
|
||||
# they ship with it everywhere; they're copied into each adventure at
|
||||
# creation, so they never need to appear in anyone's script library.
|
||||
scripts = [models.Script(**s) for s in SCRIPTS]
|
||||
db.add_all(scripts)
|
||||
|
||||
# Scenario with cards and scripts attached
|
||||
scenario = models.Scenario(**SCENARIO)
|
||||
# Scenario with cards and scripts attached — public starter content.
|
||||
scenario = models.Scenario(**SCENARIO, is_public=True)
|
||||
scenario.scripts = scripts
|
||||
db.add(scenario)
|
||||
db.flush()
|
||||
@@ -228,6 +237,7 @@ try:
|
||||
|
||||
# Adventure created from the scenario, mirroring POST /api/adventures
|
||||
adventure = models.Adventure(
|
||||
user_id=owner.id,
|
||||
scenario_id=scenario.id,
|
||||
title=scenario.title,
|
||||
memory=scenario.memory,
|
||||
|
||||
Reference in New Issue
Block a user