init: 剧情事件协作 Web 编辑器独立仓(从 SGame/tools/event_authoring 拆出)
This commit is contained in:
108
web/db.py
Normal file
108
web/db.py
Normal file
@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""M5 Web 编辑器的 SQLite 存储层。
|
||||
|
||||
单表 events:group(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]
|
||||
Reference in New Issue
Block a user