Add verified camera settings, family version recovery and account self service

This commit is contained in:
Codex
2026-10-04 04:52:39 +08:00
parent 56a24c80e2
commit 96f0ef31fc
18 changed files with 969 additions and 37 deletions
+57 -4
View File
@@ -15,6 +15,11 @@ class Family:
CREATE TABLE IF NOT EXISTS people (id TEXT PRIMARY KEY, body TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS family_links (id TEXT PRIMARY KEY, body TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS family_events (id TEXT PRIMARY KEY, body TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS family_changes (
revision INTEGER PRIMARY KEY, kind TEXT NOT NULL, target_id TEXT NOT NULL,
before_json TEXT, after_json TEXT NOT NULL, actor TEXT NOT NULL,
at TEXT NOT NULL, restored_from INTEGER);
CREATE INDEX IF NOT EXISTS family_changes_target ON family_changes(kind,target_id,revision);
''')
self.a.DB.commit()
@@ -33,7 +38,7 @@ class Family:
'canEdit': can_edit,
'selfId': user.get('personId', '')}
def save(self, kind, data, user):
def save(self, kind, data, user, restored_from=None):
self.authorize(user, True)
a = self.a
with a.LOCK:
@@ -57,12 +62,60 @@ class Family:
elif item['status'] == 'published':
item['publishedAt'] = item['updatedAt']
# Revision and record are committed together, so concurrent forms cannot overwrite silently.
a.DB.execute('INSERT OR REPLACE INTO ' + table + ' VALUES (?,?)', (item['id'], json.dumps(item)))
a.DB.execute('INSERT OR REPLACE INTO settings VALUES (?,?)', ('familyRevision', json.dumps(revision + 1)))
a.DB.commit()
with a.DB:
a.DB.execute('INSERT OR REPLACE INTO ' + table + ' VALUES (?,?)', (item['id'], json.dumps(item)))
a.DB.execute('INSERT OR REPLACE INTO settings VALUES (?,?)', ('familyRevision', json.dumps(revision + 1)))
a.DB.execute('INSERT INTO family_changes VALUES (?,?,?,?,?,?,?,?)',
(revision + 1, kind, item['id'], json.dumps(old) if old else None,
json.dumps(item), user['username'], item['updatedAt'], restored_from))
a.audit({'person': '更新家谱人物', 'link': '更新家谱关系', 'event': '更新家族记事'}[kind], user['username'])
return {'item': item, 'revision': revision + 1}
def history(self, user, kind, target_id, before=None):
# Previous drafts and withdrawn text are visible only to family editors.
self.authorize(user, True)
a = self.a
table = {'person': 'people', 'link': 'family_links', 'event': 'family_events'}.get(kind)
with a.LOCK:
if not table or not a.get_object(table, target_id):
raise a.Problem('家谱记录不存在', 404)
if before is not None:
a.integer(before, 1, 2**53 - 1)
rows = a.DB.execute('SELECT * FROM family_changes WHERE kind=? AND target_id=? AND revision<? '
'ORDER BY revision DESC LIMIT 21', (kind, target_id, before or 2**53 - 1)).fetchall()
items = []
for row in rows[:20]:
old, new = json.loads(row['before_json'] or '{}'), json.loads(row['after_json'])
fields = [k for k in new if k not in ('id', 'updatedAt', 'updatedBy', 'createdAt', 'createdBy', 'publishedAt')
and old.get(k) != new[k]]
items.append(dict(revision=row['revision'], at=row['at'], actor=row['actor'], fields=fields,
created=row['before_json'] is None, restoredFrom=row['restored_from']))
return dict(items=items, nextBefore=items[-1]['revision'] if len(rows) > 20 else None,
revision=a.setting('familyRevision', 0))
def change(self, user, change_revision):
self.authorize(user, True)
self.a.integer(change_revision, 1, 2**53 - 1)
with self.a.LOCK:
row = self.a.DB.execute('SELECT * FROM family_changes WHERE revision=?', (change_revision,)).fetchone()
if not row:
raise self.a.Problem('修改记录不存在', 404)
return dict(revision=row['revision'], kind=row['kind'], targetId=row['target_id'],
before=json.loads(row['before_json']) if row['before_json'] else None,
after=json.loads(row['after_json']), at=row['at'], actor=row['actor'])
def restore(self, data, user):
self.authorize(user, True)
with self.a.LOCK:
change = self.change(user, data.get('changeRevision'))
side = data.get('side')
if side not in ('before', 'after') or change[side] is None:
raise self.a.Problem('请选择有效的历史版本;创建前没有可恢复的内容')
# Reuse all current relationship/date/publication validation. Restore is a new edit,
# not an overwrite of history or a rollback of other people's records.
return self.save(change['kind'], dict(change[side], revision=data.get('revision')),
user, restored_from=change['revision'])
def event(self, data, old):
a = self.a
item = {'id': old['id'] if old else secrets.token_hex(8)}