diff --git a/api/app/content.py b/api/app/content.py index f95efd0..69f135d 100644 --- a/api/app/content.py +++ b/api/app/content.py @@ -8,6 +8,7 @@ scenes into play. """ import json +import os from pathlib import Path @@ -48,3 +49,30 @@ def unresolved_refs(origin: dict, world: dict[str, set[str]]) -> list[str]: if npc_id not in world["npcs"]: missing.append(f"npc:{npc_id}") return missing + + +def _content_root() -> Path: + """Locate the authored content root. Honours CONTENT_ROOT, else walks up + from this file for a dir containing world/ — repo-root/content in a local + checkout, /app/content when bundled into the Docker image. Mirrors + canon_log._schema_dir(). + """ + env = os.environ.get("CONTENT_ROOT") + if env: + return Path(env) + here = Path(__file__).resolve() + for parent in here.parents: + candidate = parent / "content" + if (candidate / "world").is_dir(): + return candidate + raise RuntimeError("content root not found") + + +def load_npc(npc_id: str) -> dict | None: + """Load one NPC's authored file (persona + knowledge + capabilities), or + None if no file resolves. Server-only fields never travel to the client.""" + path = _content_root() / "world" / "npcs" / f"{npc_id}.json" + if not path.is_file(): + return None + with open(path) as f: + return json.load(f) diff --git a/api/tests/test_content_npc.py b/api/tests/test_content_npc.py new file mode 100644 index 0000000..9366e33 --- /dev/null +++ b/api/tests/test_content_npc.py @@ -0,0 +1,27 @@ +import json + +import app.content as content + + +def test_load_npc_returns_parsed_file(tmp_path, monkeypatch): + root = tmp_path / "content" + (root / "world" / "npcs").mkdir(parents=True) + (root / "world" / "npcs" / "fenn.json").write_text( + json.dumps({"id": "fenn", "name": "Fenn", "persona": "clerk", + "knowledge": ["a"], "capabilities": {}}) + ) + monkeypatch.setenv("CONTENT_ROOT", str(root)) + npc = content.load_npc("fenn") + assert npc["name"] == "Fenn" + assert npc["knowledge"] == ["a"] + + +def test_load_npc_unknown_returns_none(tmp_path, monkeypatch): + (tmp_path / "content" / "world").mkdir(parents=True) + monkeypatch.setenv("CONTENT_ROOT", str(tmp_path / "content")) + assert content.load_npc("nobody") is None + + +def test_content_root_env_override(tmp_path, monkeypatch): + monkeypatch.setenv("CONTENT_ROOT", str(tmp_path)) + assert content._content_root() == tmp_path