feat: M0-M2 骨架落地(服务端/前端/宿主/数据库/积分引擎)
- server: Go 单二进制骨架,embed 内嵌前端,配置/迁移/HTTP 路由 - store: system.db + event.db 两套迁移,业务表 + 种子数据 - domain: 积分引擎(并列三口径/计分通道/流水/排名/破纪录)+ 68 个测试 - web: Vite + Vue 三入口骨架 + shared 层(类型/API/WebSocket 客户端) - console: Electron 宿主骨架(选屏/防熄屏/进程守护/托盘)
This commit is contained in:
1 parent
a61d793d79
commit
7d5d94f815
76 files changed
+10131
No files matched your search
@@ -0,0 +1,14 @@
|
||||
// Package migrations 内嵌 SQL 迁移脚本。
|
||||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
// SystemFS 内嵌系统库(data/system.db)的迁移:system/*.sql。
|
||||
//
|
||||
//go:embed system/*.sql
|
||||
var SystemFS embed.FS
|
||||
|
||||
// EventFS 内嵌赛事库(data/event_projs/<项目名>/event.db)的迁移:event/*.sql。
|
||||
//
|
||||
//go:embed event/*.sql
|
||||
var EventFS embed.FS
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 赛事级配置:键值对,与系统库同名表结构一致,但各存各的。
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
short_name TEXT,
|
||||
logo_path TEXT,
|
||||
color TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_sort_order ON teams(sort_order, code);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS athletes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
gender TEXT CHECK (gender IN ('男', '女')),
|
||||
team_id INTEGER REFERENCES teams(id),
|
||||
employee_no TEXT,
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_athletes_team_id ON athletes(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_athletes_name ON athletes(name);
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE IF NOT EXISTS scoring_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
tie_mode TEXT NOT NULL DEFAULT 'skip' CHECK (tie_mode IN ('skip', 'average', 'higher')),
|
||||
record_bonus_mode TEXT NOT NULL DEFAULT 'max' CHECK (record_bonus_mode IN ('max', 'sum')),
|
||||
-- 文档只在规则包里写了 awardRanks / lastPlaceZero,落库是为了让积分引擎不必再读 YAML。
|
||||
award_ranks INTEGER NOT NULL DEFAULT 6 CHECK (award_ranks >= 0),
|
||||
last_place_zero INTEGER NOT NULL DEFAULT 0 CHECK (last_place_zero IN (0, 1)),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scoring_profile_rules (
|
||||
profile_id INTEGER NOT NULL REFERENCES scoring_profiles(id),
|
||||
rank INTEGER NOT NULL CHECK (rank >= 1),
|
||||
points REAL NOT NULL,
|
||||
PRIMARY KEY (profile_id, rank)
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
CREATE TABLE IF NOT EXISTS sports (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
short_name TEXT,
|
||||
default_profile_id INTEGER REFERENCES scoring_profiles(id),
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sports_sort_order ON sports(sort_order, code);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sport_id INTEGER NOT NULL REFERENCES sports(id),
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL DEFAULT 'individual'
|
||||
CHECK (event_type IN ('individual', 'team', 'match')),
|
||||
result_mode TEXT NOT NULL DEFAULT 'auto'
|
||||
CHECK (result_mode IN ('auto', 'manual')),
|
||||
result_type TEXT NOT NULL DEFAULT 'none'
|
||||
CHECK (result_type IN ('time', 'distance', 'count', 'score', 'none')),
|
||||
compare TEXT NOT NULL DEFAULT 'none'
|
||||
CHECK (compare IN ('asc', 'desc', 'none')),
|
||||
record_eligible INTEGER NOT NULL DEFAULT 1 CHECK (record_eligible IN (0, 1)),
|
||||
scored INTEGER NOT NULL DEFAULT 1 CHECK (scored IN (0, 1)),
|
||||
multiplier REAL NOT NULL DEFAULT 1 CHECK (multiplier >= 0),
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'ongoing', 'finished', 'published')),
|
||||
scheduled_at TEXT,
|
||||
venue TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
remark TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_events_sport_id ON events(sport_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_events_status ON events(status, sort_order);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS event_categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
name TEXT NOT NULL,
|
||||
multiplier REAL NOT NULL DEFAULT 1 CHECK (multiplier >= 0),
|
||||
gender TEXT CHECK (gender IN ('男', '女')),
|
||||
age_min INTEGER,
|
||||
age_max INTEGER,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (event_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_event_categories_event_id ON event_categories(event_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS event_score_channels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
scope TEXT NOT NULL CHECK (scope IN ('athlete', 'team')),
|
||||
basis TEXT NOT NULL CHECK (basis IN ('result', 'manual', 'aggregate')),
|
||||
profile_id INTEGER NOT NULL REFERENCES scoring_profiles(id),
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (event_id, scope, basis)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_event_score_channels_event_id ON event_score_channels(event_id);
|
||||
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE IF NOT EXISTS entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
category_id INTEGER NOT NULL REFERENCES event_categories(id),
|
||||
team_id INTEGER NOT NULL REFERENCES teams(id),
|
||||
athlete_id INTEGER REFERENCES athletes(id),
|
||||
member_names TEXT,
|
||||
bib TEXT,
|
||||
seed TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'registered'
|
||||
CHECK (status IN ('registered', 'scratched', 'dq', 'dns')),
|
||||
remark TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (category_id, team_id, athlete_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_entries_event_id ON entries(event_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_entries_team_id ON entries(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_entries_athlete_id ON entries(athlete_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_entries_category_id ON entries(category_id);
|
||||
-- athlete_id 为 NULL 时 UNIQUE 视为互不相等,团体项目的重复报名靠这条部分索引兜住。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_entries_team_unique
|
||||
ON entries(category_id, team_id) WHERE athlete_id IS NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS results (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
entry_id INTEGER NOT NULL UNIQUE REFERENCES entries(id),
|
||||
rank INTEGER CHECK (rank IS NULL OR rank >= 1),
|
||||
result_text TEXT,
|
||||
result_value REAL,
|
||||
unit TEXT,
|
||||
is_tie INTEGER NOT NULL DEFAULT 0 CHECK (is_tie IN (0, 1)),
|
||||
points_override REAL,
|
||||
status TEXT NOT NULL DEFAULT 'ok'
|
||||
CHECK (status IN ('ok', 'dq', 'dns', 'dnf', 'withdrawn')),
|
||||
state TEXT NOT NULL DEFAULT 'draft' CHECK (state IN ('draft', 'published')),
|
||||
-- recorded_by / verified_by 指向系统库 users,跨库不能建外键。
|
||||
recorded_by INTEGER,
|
||||
recorded_at TEXT,
|
||||
verified_by INTEGER,
|
||||
verified_at TEXT,
|
||||
note TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_results_event_state ON results(event_id, state);
|
||||
CREATE INDEX IF NOT EXISTS idx_results_event_rank ON results(event_id, rank);
|
||||
@@ -0,0 +1,33 @@
|
||||
CREATE TABLE IF NOT EXISTS records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
level TEXT NOT NULL CHECK (level IN ('meet', 'city', 'province', 'national')),
|
||||
result_value REAL,
|
||||
result_text TEXT,
|
||||
holder_name TEXT,
|
||||
holder_team_id INTEGER REFERENCES teams(id),
|
||||
achieved_at TEXT,
|
||||
is_current INTEGER NOT NULL DEFAULT 1 CHECK (is_current IN (0, 1)),
|
||||
source TEXT NOT NULL DEFAULT 'history' CHECK (source IN ('history', 'current')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_records_event_level ON records(event_id, level, is_current);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS record_breaks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
entry_id INTEGER NOT NULL REFERENCES entries(id),
|
||||
level TEXT NOT NULL CHECK (level IN ('meet', 'city', 'province', 'national')),
|
||||
result_value REAL,
|
||||
result_text TEXT,
|
||||
bonus_points REAL NOT NULL DEFAULT 0,
|
||||
state TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (state IN ('pending', 'approved', 'rejected')),
|
||||
-- approved_by 指向系统库 users,跨库不能建外键。
|
||||
approved_by INTEGER,
|
||||
approved_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_record_breaks_event_id ON record_breaks(event_id, state);
|
||||
@@ -0,0 +1,38 @@
|
||||
-- created_by / voided_by 指向系统库 users,跨库不能建外键,靠服务层校验。
|
||||
CREATE TABLE IF NOT EXISTS score_ledger (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_id INTEGER NOT NULL REFERENCES teams(id),
|
||||
athlete_id INTEGER REFERENCES athletes(id),
|
||||
event_id INTEGER REFERENCES events(id),
|
||||
entry_id INTEGER REFERENCES entries(id),
|
||||
result_id INTEGER REFERENCES results(id),
|
||||
source_type TEXT NOT NULL CHECK (source_type IN ('result', 'record_bonus', 'adjustment')),
|
||||
source_id INTEGER NOT NULL,
|
||||
points REAL NOT NULL,
|
||||
voided INTEGER NOT NULL DEFAULT 0 CHECK (voided IN (0, 1)),
|
||||
voided_by INTEGER,
|
||||
voided_at TEXT,
|
||||
void_reason TEXT,
|
||||
created_by INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_score_ledger_team_voided ON score_ledger(team_id, voided);
|
||||
CREATE INDEX IF NOT EXISTS idx_score_ledger_source ON score_ledger(source_type, source_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_score_ledger_athlete ON score_ledger(athlete_id, voided);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS point_adjustments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_id INTEGER NOT NULL REFERENCES teams(id),
|
||||
delta REAL NOT NULL,
|
||||
category TEXT NOT NULL CHECK (category IN ('penalty', 'bonus', 'correction')),
|
||||
reason TEXT NOT NULL,
|
||||
event_id INTEGER REFERENCES events(id),
|
||||
voided INTEGER NOT NULL DEFAULT 0 CHECK (voided IN (0, 1)),
|
||||
voided_by INTEGER,
|
||||
voided_at TEXT,
|
||||
created_by INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_point_adjustments_team_voided ON point_adjustments(team_id, voided);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 成绩版本图谱:parent_id 形成 DAG,撤销后从撤销点分支,仅用于可视化。
|
||||
CREATE TABLE IF NOT EXISTS result_revisions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
parent_id INTEGER REFERENCES result_revisions(id),
|
||||
event_id INTEGER NOT NULL REFERENCES events(id),
|
||||
action TEXT NOT NULL CHECK (action IN ('publish', 'unpublish', 'edit')),
|
||||
snapshot_json TEXT NOT NULL,
|
||||
ledger_ids TEXT NOT NULL DEFAULT '[]',
|
||||
-- actor_id 指向系统库 users,跨库不能建外键。
|
||||
actor_id INTEGER,
|
||||
message TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_result_revisions_event_id ON result_revisions(event_id, created_at);
|
||||
@@ -0,0 +1,13 @@
|
||||
-- id 就是实时协议的 version,必须单调递增、不重用,所以用 AUTOINCREMENT。
|
||||
CREATE TABLE IF NOT EXISTS change_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id INTEGER,
|
||||
action TEXT NOT NULL CHECK (action IN ('upsert', 'void', 'replace')),
|
||||
payload_json TEXT NOT NULL DEFAULT '{}',
|
||||
-- actor_id 指向系统库 users,跨库不能建外键。
|
||||
actor_id INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_change_log_entity ON change_log(entity_type, entity_id);
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 审计日志只读不可删;user_id 指向系统库 users,跨库不能建外键。
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
action TEXT NOT NULL,
|
||||
entity_type TEXT,
|
||||
entity_id INTEGER,
|
||||
before_json TEXT,
|
||||
after_json TEXT,
|
||||
ip TEXT,
|
||||
request_id TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
@@ -0,0 +1,45 @@
|
||||
-- 默认积分模板:个人 10/8/6/3/2/1,团体 15/10/8/4/2/1(见 02-积分与排名规则 第 2.1 节)。
|
||||
INSERT OR IGNORE INTO scoring_profiles (code, name, tie_mode, record_bonus_mode, award_ranks, last_place_zero)
|
||||
VALUES ('individual_top6', '个人项目前六名', 'skip', 'max', 6, 0),
|
||||
('team_top6', '团体项目前六名', 'skip', 'max', 6, 0);
|
||||
|
||||
INSERT OR IGNORE INTO scoring_profile_rules (profile_id, rank, points)
|
||||
SELECT p.id, r.rank, r.points
|
||||
FROM scoring_profiles p
|
||||
JOIN (SELECT 1 AS rank, 10.0 AS points UNION ALL
|
||||
SELECT 2, 8.0 UNION ALL
|
||||
SELECT 3, 6.0 UNION ALL
|
||||
SELECT 4, 3.0 UNION ALL
|
||||
SELECT 5, 2.0 UNION ALL
|
||||
SELECT 6, 1.0) r
|
||||
WHERE p.code = 'individual_top6';
|
||||
|
||||
INSERT OR IGNORE INTO scoring_profile_rules (profile_id, rank, points)
|
||||
SELECT p.id, r.rank, r.points
|
||||
FROM scoring_profiles p
|
||||
JOIN (SELECT 1 AS rank, 15.0 AS points UNION ALL
|
||||
SELECT 2, 10.0 UNION ALL
|
||||
SELECT 3, 8.0 UNION ALL
|
||||
SELECT 4, 4.0 UNION ALL
|
||||
SELECT 5, 2.0 UNION ALL
|
||||
SELECT 6, 1.0) r
|
||||
WHERE p.code = 'team_top6';
|
||||
|
||||
-- 赛事级默认配置;键名沿用 04-数据模型 的 event.name / display.rotate_seconds 风格。
|
||||
INSERT OR IGNORE INTO settings (key, value) VALUES
|
||||
('event.name', '未命名赛事'),
|
||||
('event.subtitle', ''),
|
||||
('event.organizer', ''),
|
||||
('event.logo_path', ''),
|
||||
('display.theme_color', '#1E88E5'),
|
||||
('display.rotate_seconds', '15'),
|
||||
('standings.mode', 'points'),
|
||||
('standings.individual_to_team_weight', '1'),
|
||||
('standings.show_individual_medals', 'true'),
|
||||
('standings.medal_counts_team_events', 'true'),
|
||||
('scoring.default_profile', 'individual_top6'),
|
||||
('results.review_required', 'false'),
|
||||
('record_bonus.enabled', 'false'),
|
||||
('record_bonus.mode', 'max'),
|
||||
('manual_adjustment.enabled', 'false'),
|
||||
('manual_adjustment.reason_required', 'true');
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 系统级配置:键值对
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 登录用户。
|
||||
-- role 不含 guest:guest 是「未登录」的默认身份,不落库(见 08-安全与可靠性 第 2 节)。
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL DEFAULT '',
|
||||
role TEXT NOT NULL DEFAULT 'recorder' CHECK (role IN ('recorder', 'admin')),
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
|
||||
must_change_password INTEGER NOT NULL DEFAULT 1 CHECK (must_change_password IN (0, 1)),
|
||||
last_login_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(active);
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 服务端会话;id 为随机字符串,token 只存哈希,便于强制下线。
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TEXT NOT NULL,
|
||||
ip TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_seen_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
||||
@@ -0,0 +1,11 @@
|
||||
-- 最近打开的赛事项目;启动时取 last_opened_at 最新的一条自动打开。
|
||||
CREATE TABLE IF NOT EXISTS recent_projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project TEXT NOT NULL UNIQUE, -- data/event_projs/<项目名>
|
||||
name TEXT NOT NULL DEFAULT '', -- 展示名,冗余自赛事库 settings
|
||||
db_path TEXT NOT NULL, -- 相对数据根目录的 event.db 路径
|
||||
open_count INTEGER NOT NULL DEFAULT 1,
|
||||
last_opened_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_recent_projects_last_opened ON recent_projects(last_opened_at DESC);
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 系统级默认配置;赛事级配置在 event.db 的 settings 里,两者不共用。
|
||||
INSERT OR IGNORE INTO settings (key, value) VALUES
|
||||
('app.name', '赛事积分榜系统'),
|
||||
('app.locale', 'zh-CN'),
|
||||
('security.session_hours', '12'),
|
||||
('security.login_rate_per_minute', '5'),
|
||||
('backup.interval_minutes', '5'),
|
||||
('backup.keep', '20');
|
||||
Reference in new issue
Block a user