feat(todo): the person's tasks, sections and labels, sealed and synced (ADR 0033, D6)
A table's rows are sealed for their table and row by one SealedRows, which the repositories of projects, sections, tasks and labels share, each table with its own sync gateway, a deletion blanking its rows first in one transaction. A task's payload holds its title, description, project, section, parent, place, date (a day, an optional wall-clock time and duration), deadline, priority, labels and when it was done; deleting a task deletes its sub-tasks, and a project or a section goes with its tasks. Rows that don't open show locked; a device without the account's keys writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
128bcb127b
commit
bd3710ae58
1 changed files with 1557 additions and 224 deletions
|
|
@ -10,7 +10,7 @@ import 'package:halkyon_todo_flutter/src/feature/project/model/stored_project.da
|
|||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
|
||||
import '../../support/memory_content_key_gateway.dart';
|
||||
import '../../support/memory_project_gateway.dart';
|
||||
import '../../support/memory_sealed_row_gateway.dart';
|
||||
|
||||
/// The person's projects, sealed on the way to the instance and opened on
|
||||
/// the way back (ADR 0033, D5, D6), with libsodium.
|
||||
|
|
@ -19,7 +19,7 @@ void main() {
|
|||
late UnlockedAccountKeys alice;
|
||||
late UnlockedAccountKeys bob;
|
||||
late MemoryContentKeyGateway keyRows;
|
||||
late MemoryProjectGateway rows;
|
||||
late MemorySealedRowGateway rows;
|
||||
final opened = <TodoKeys>[];
|
||||
|
||||
const home = ProjectContent(
|
||||
|
|
@ -42,7 +42,7 @@ void main() {
|
|||
|
||||
setUp(() {
|
||||
keyRows = MemoryContentKeyGateway();
|
||||
rows = MemoryProjectGateway();
|
||||
rows = MemorySealedRowGateway();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
|
|
@ -61,7 +61,7 @@ void main() {
|
|||
|
||||
final laptop = await device(alice);
|
||||
expect(await laptop.ensureInbox(), id);
|
||||
expect(rows.rows.keys.where((key) => key == id), hasLength(1));
|
||||
expect(rows.byId.keys.where((key) => key == id), hasLength(1));
|
||||
final all = await laptop.all();
|
||||
expect(all.first, StoredProject(id, ProjectContent.inbox, inbox: true));
|
||||
expect(all.where((project) => project.inbox), hasLength(1));
|
||||
|
|
@ -78,7 +78,7 @@ void main() {
|
|||
]);
|
||||
|
||||
expect(ids.toSet(), hasLength(1));
|
||||
expect(rows.rows, hasLength(1));
|
||||
expect(rows.byId, hasLength(1));
|
||||
});
|
||||
|
||||
test("names nothing of the person's that the instance could read: its "
|
||||
|
|
@ -94,7 +94,7 @@ void main() {
|
|||
final id = await phone.ensureInbox();
|
||||
|
||||
await expectLater(phone.delete(id), throwsA(isA<InboxKept>()));
|
||||
expect(rows.rows.keys, contains(id));
|
||||
expect(rows.byId.keys, contains(id));
|
||||
});
|
||||
|
||||
test('is only the row under its ID: a row claiming to be it elsewhere is '
|
||||
|
|
@ -142,7 +142,7 @@ void main() {
|
|||
'JSON', () async {
|
||||
await (await device(alice)).save(home);
|
||||
|
||||
final sealed = rows.rows.values.single;
|
||||
final sealed = rows.byId.values.single;
|
||||
expect(sealed.keyId, isNotNull);
|
||||
final text = utf8.decode(
|
||||
Uint8List.sublistView(sealed.payload),
|
||||
|
|
@ -156,8 +156,8 @@ void main() {
|
|||
test("another person's project, a readable one, or one moved to another "
|
||||
'row shows locked, never as theirs', () async {
|
||||
final id = await (await device(alice)).save(home);
|
||||
final sealed = rows.rows[id]!;
|
||||
rows.rows
|
||||
final sealed = rows.byId[id]!;
|
||||
rows.byId
|
||||
..['moved'] = sealed
|
||||
..['readable'] = (
|
||||
keyId: null,
|
||||
|
|
@ -184,7 +184,7 @@ void main() {
|
|||
final phone = await device(alice);
|
||||
final other = await phone.save(home);
|
||||
final inbox = phone.inboxId;
|
||||
rows.rows[inbox] = rows.rows[other]!;
|
||||
rows.byId[inbox] = rows.byId[other]!;
|
||||
|
||||
final all = await phone.all();
|
||||
expect(all.firstWhere((project) => project.inbox).content, isNull);
|
||||
|
|
@ -198,7 +198,7 @@ void main() {
|
|||
expect((await stranger.all()).single.content, isNull);
|
||||
await expectLater(stranger.save(home), throwsA(isA<KeysMissing>()));
|
||||
await expectLater(stranger.ensureInbox(), throwsA(isA<KeysMissing>()));
|
||||
expect(rows.rows, hasLength(1));
|
||||
expect(rows.byId, hasLength(1));
|
||||
});
|
||||
|
||||
test('a change here or from another device is told', () async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue