feat: isolate households and add explicit ownership succession
This commit is contained in:
+23
-11
@@ -24,35 +24,44 @@ class Family:
|
||||
self.a.DB.commit()
|
||||
|
||||
def authorize(self, user, edit=False):
|
||||
if user['role'] != 'admin' and user.get('familyAccess', 'none') not in (('edit',) if edit else ('read', 'edit')):
|
||||
self.a.HOUSEHOLDS.scope(user)
|
||||
if not self.a.HOUSEHOLDS.manager(user) and user.get('familyAccess', 'none') not in (('edit',) if edit else ('read', 'edit')):
|
||||
raise self.a.Problem('没有家谱' + ('编辑' if edit else '查看') + '权限', 403)
|
||||
|
||||
def snapshot(self, user):
|
||||
self.authorize(user)
|
||||
with self.a.LOCK:
|
||||
can_edit = user['role'] == 'admin' or user.get('familyAccess') == 'edit'
|
||||
return {'revision': self.a.setting('familyRevision', 0), 'people': self.a.objects('people'),
|
||||
'links': self.a.objects('family_links'),
|
||||
'events': [e for e in self.a.objects('family_events')
|
||||
can_edit = self.a.HOUSEHOLDS.manager(user) or user.get('familyAccess') == 'edit'
|
||||
people = self.a.HOUSEHOLDS.objects(user, 'people')
|
||||
return {'revision': self.a.setting(self.a.HOUSEHOLDS.revision_key(user), 0), 'people': people,
|
||||
'links': self.a.HOUSEHOLDS.objects(user, 'family_links'),
|
||||
'events': [e for e in self.a.HOUSEHOLDS.objects(user, 'family_events')
|
||||
if can_edit or (e.get('status') == 'published' and not e.get('archived'))],
|
||||
'canEdit': can_edit,
|
||||
'selfId': user.get('personId', '')}
|
||||
'selfId': user.get('personId', '') if any(p['id'] == user.get('personId') for p in people) else '',
|
||||
'familyId': self.a.HOUSEHOLDS.scope(user)}
|
||||
|
||||
def save(self, kind, data, user, restored_from=None):
|
||||
self.authorize(user, True)
|
||||
a = self.a
|
||||
with a.LOCK:
|
||||
revision = a.setting('familyRevision', 0)
|
||||
revision_key = a.HOUSEHOLDS.revision_key(user)
|
||||
revision = a.setting(revision_key, 0)
|
||||
if type(data.get('revision')) is not int or data['revision'] != revision:
|
||||
raise a.Problem('家谱已被更新,请重新载入后再编辑', 409)
|
||||
if kind not in ('person', 'link', 'event'):
|
||||
raise a.Problem('家谱记录类型不存在', 404)
|
||||
table = {'person': 'people', 'link': 'family_links', 'event': 'family_events'}[kind]
|
||||
family_id = a.HOUSEHOLDS.prepare(user, table, data,
|
||||
(('people', 'fromId'), ('people', 'toId')) if kind == 'link' else ())
|
||||
if kind == 'event' and isinstance(data.get('personIds', []), list):
|
||||
for person in data.get('personIds', []):
|
||||
a.HOUSEHOLDS.owns(user, a.get_object('people', person))
|
||||
old = a.get_object(table, data.get('id'))
|
||||
if data.get('id') and not old:
|
||||
raise a.Problem('家谱记录不存在', 404)
|
||||
item = {'person': self.person, 'link': self.link, 'event': self.event}[kind](data, old)
|
||||
item.update(updatedAt=dt.datetime.now(dt.timezone.utc).isoformat(), updatedBy=user['username'])
|
||||
item.update(familyId=family_id, updatedAt=dt.datetime.now(dt.timezone.utc).isoformat(), updatedBy=user['username'])
|
||||
if kind == 'event':
|
||||
item.update(createdAt=old.get('createdAt', item['updatedAt']) if old else item['updatedAt'],
|
||||
createdBy=old.get('createdBy', user['username']) if old else user['username'])
|
||||
@@ -63,10 +72,11 @@ class Family:
|
||||
item['publishedAt'] = item['updatedAt']
|
||||
# Revision and record are committed together, so concurrent forms cannot overwrite silently.
|
||||
with a.DB:
|
||||
change_revision = a.DB.execute('SELECT COALESCE(MAX(revision),0)+1 FROM family_changes').fetchone()[0]
|
||||
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 OR REPLACE INTO settings VALUES (?,?)', (revision_key, json.dumps(revision + 1)))
|
||||
a.DB.execute('INSERT INTO family_changes VALUES (?,?,?,?,?,?,?,?)',
|
||||
(revision + 1, kind, item['id'], json.dumps(old) if old else None,
|
||||
(change_revision, 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}
|
||||
@@ -79,6 +89,7 @@ class Family:
|
||||
with a.LOCK:
|
||||
if not table or not a.get_object(table, target_id):
|
||||
raise a.Problem('家谱记录不存在', 404)
|
||||
a.HOUSEHOLDS.owns(user, a.get_object(table, target_id))
|
||||
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<? '
|
||||
@@ -91,7 +102,7 @@ class Family:
|
||||
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))
|
||||
revision=a.setting(a.HOUSEHOLDS.revision_key(user), 0))
|
||||
|
||||
def change(self, user, change_revision):
|
||||
self.authorize(user, True)
|
||||
@@ -100,6 +111,7 @@ class Family:
|
||||
row = self.a.DB.execute('SELECT * FROM family_changes WHERE revision=?', (change_revision,)).fetchone()
|
||||
if not row:
|
||||
raise self.a.Problem('修改记录不存在', 404)
|
||||
self.a.HOUSEHOLDS.owns(user, json.loads(row['after_json']))
|
||||
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'])
|
||||
|
||||
Reference in New Issue
Block a user