109 lines
3.6 KiB
Python
109 lines
3.6 KiB
Python
# -*- 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]
|