Files
story-edit-web/web/db.py
邓雨鹏 65424a4dfb feat(web): 海选按场景分组 + 删场景点位页签 + 演出真实底图 + 破缓存
- 海选审核左侧改两列:场景列(按新字段 ir.scene 手动归类聚合,含全部/未分类) + 该场景事件列
- 删独立「场景/点位」页签(pointview.js 保留未引用)
- 演出配置 Timeline 接真实场景俯视底图(setupShot 覆盖投影范围 + drawStage 叠图,复用 /api/pointsets 的 shot)
- 事件 meta 加「所属场景」归类输入框(datalist 提示已有场景名)
- db: events 加 scene 列 + 旧库 ALTER 迁移;upsert 镜像 ir.scene;list 返回
- app.py: 首页按文件 mtime 给 js/css 注入 ?v= 破浏览器缓存(根治新html配旧缓存js崩溃→弹口令)
2026-06-15 11:46:59 +08:00

154 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""M5 Web 编辑器的 SQLite 存储层。
表 eventsgroup(PK)/title/theme/status/ir_json/updated_at/updated_by/notes。
末次写入生效(设计接受),不做锁。
表 sessionstoken(PK)/user/expires_at——登录会话cookie 只存随机 token不存口令
"""
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,
scene TEXT DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
ir_json TEXT NOT NULL,
updated_at TEXT,
updated_by TEXT,
notes TEXT
)"""
)
# 旧库迁移:补 scene 列(海选审核按场景分组的归类维度,镜像自 ir.scene
try:
c.execute("ALTER TABLE events ADD COLUMN scene TEXT DEFAULT ''")
except sqlite3.OperationalError:
pass # 已存在
c.execute(
"""CREATE TABLE IF NOT EXISTS sessions (
token TEXT PRIMARY KEY,
user TEXT NOT NULL,
expires_at REAL NOT NULL
)"""
)
# ---------- 会话 ----------
def create_session(token, user, expires_at, path=None):
with _conn(path) as c:
c.execute("INSERT OR REPLACE INTO sessions (token, user, expires_at) "
"VALUES (?,?,?)", (token, user, expires_at))
def get_session_user(token, now, path=None):
"""token 有效返回用户名;过期则顺手删除并返回 None。"""
with _conn(path) as c:
r = c.execute("SELECT user, expires_at FROM sessions WHERE token=?",
(token,)).fetchone()
if not r:
return None
if r["expires_at"] < now:
c.execute("DELETE FROM sessions WHERE token=?", (token,))
return None
return r["user"]
def delete_session(token, path=None):
with _conn(path) as c:
c.execute("DELETE FROM sessions WHERE token=?", (token,))
def purge_sessions(now, path=None):
with _conn(path) as c:
c.execute("DELETE FROM sessions WHERE expires_at < ?", (now,))
def list_events(status=None, path=None):
"""列表(不含 ir_json轻量"""
sql = ('SELECT "group", title, theme, scene, 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", "")
scene = ir.get("scene", "") or "" # 海选审核分组维度,镜像自 ir.scene
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=?, scene=?, ir_json=?, updated_at=?, '
'updated_by=?, notes=COALESCE(?, notes) WHERE "group"=?',
(title, theme, scene, ir_str, now, by, notes, group),
)
else:
c.execute(
'INSERT INTO events ("group", title, theme, scene, status, ir_json, '
"updated_at, updated_by, notes) VALUES (?,?,?,?,?,?,?,?,?)",
(group, title, theme, scene, "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]