init: 剧情事件协作 Web 编辑器独立仓(从 SGame/tools/event_authoring 拆出)

This commit is contained in:
bia
2026-06-08 16:50:27 +08:00
commit f5669dc01d
32 changed files with 3497 additions and 0 deletions

108
web/db.py Normal file
View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
"""M5 Web 编辑器的 SQLite 存储层。
单表 eventsgroup(PK)/title/theme/status/ir_json/updated_at/updated_by/notes。
末次写入生效(设计接受),不做锁。
"""
import json
import os
import sqlite3
# DB 路径:容器内用 STORY_DB_PATH 指向挂载卷(持久化);本地默认同目录。
_DB_PATH = os.environ.get("STORY_DB_PATH") or \
os.path.join(os.path.dirname(os.path.abspath(__file__)), "story_events.db")
STATUSES = ("pending", "confirmed", "discarded")
def _conn(path=None):
c = sqlite3.connect(path or _DB_PATH)
c.row_factory = sqlite3.Row
return c
def init_db(path=None):
d = os.path.dirname(path or _DB_PATH)
if d and not os.path.isdir(d):
os.makedirs(d, exist_ok=True)
with _conn(path) as c:
c.execute(
"""CREATE TABLE IF NOT EXISTS events (
"group" TEXT PRIMARY KEY,
title TEXT,
theme TEXT,
status TEXT NOT NULL DEFAULT 'pending',
ir_json TEXT NOT NULL,
updated_at TEXT,
updated_by TEXT,
notes TEXT
)"""
)
def list_events(status=None, path=None):
"""列表(不含 ir_json轻量"""
sql = ('SELECT "group", title, theme, status, updated_at, updated_by, notes '
"FROM events")
args = []
if status and status != "all":
sql += " WHERE status = ?"
args.append(status)
sql += " ORDER BY updated_at DESC"
with _conn(path) as c:
return [dict(r) for r in c.execute(sql, args).fetchall()]
def get_event(group, path=None):
with _conn(path) as c:
r = c.execute('SELECT * FROM events WHERE "group" = ?', (group,)).fetchone()
if not r:
return None
d = dict(r)
d["ir"] = json.loads(d.pop("ir_json"))
return d
def upsert_event(ir, by, now, notes=None, keep_status=True, path=None):
"""插入或更新。已存在时默认保留状态(仅刷新 ir/title/theme/元信息)。"""
group = ir["id"]
title = ir.get("title", "")
theme = ir.get("theme", "")
ir_str = json.dumps(ir, ensure_ascii=False)
with _conn(path) as c:
exists = c.execute('SELECT status FROM events WHERE "group" = ?',
(group,)).fetchone()
if exists:
c.execute(
'UPDATE events SET title=?, theme=?, ir_json=?, updated_at=?, '
'updated_by=?, notes=COALESCE(?, notes) WHERE "group"=?',
(title, theme, ir_str, now, by, notes, group),
)
else:
c.execute(
'INSERT INTO events ("group", title, theme, status, ir_json, '
"updated_at, updated_by, notes) VALUES (?,?,?,?,?,?,?,?)",
(group, title, theme, "pending", ir_str, now, by, notes or ""),
)
return group
def set_status(group, status, by, now, path=None):
if status not in STATUSES:
raise ValueError("非法状态: %r" % status)
with _conn(path) as c:
cur = c.execute(
'UPDATE events SET status=?, updated_at=?, updated_by=? WHERE "group"=?',
(status, now, by, group),
)
return cur.rowcount > 0
def confirmed_events(path=None):
"""所有 confirmed 事件的 (group, ir) 列表,供导出编译。"""
with _conn(path) as c:
rows = c.execute(
'SELECT "group", ir_json FROM events WHERE status=? ORDER BY "group"',
("confirmed",),
).fetchall()
return [(r["group"], json.loads(r["ir_json"])) for r in rows]