Add verified camera settings, family version recovery and account self service
This commit is contained in:
+57
-4
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user