A todo and personal organisation app

Sync API#

Base URL: /api/sync

All endpoints require authentication.

Overview#

The sync protocol uses checksums to efficiently synchronize noots between client and server.

Endpoints#

POST /#

Full sync operation.

Request:

{
  "noots": [
    {
      "id": "uuid",
      "version": 2,
      "checksum": "sha256...",
      "activities": [...]
    }
  ],
  "knownNoots": [
    {"id": "uuid-1", "checksum": "sha256..."},
    {"id": "uuid-2", "checksum": "sha256..."}
  ]
}

Response (200):

{
  "serverNoots": [
    {
      "id": "uuid",
      "version": 3,
      "checksum": "sha256...",
      "activities": [...]
    }
  ],
  "deletedIds": ["uuid-3", "uuid-4"],
  "accepted": ["uuid-1"],
  "conflicts": []
}

Request Fields#

Field Description
noots Local noots with pending changes
knownNoots Checksums of all local noots

Response Fields#

Field Description
serverNoots Noots that differ from client
deletedIds Noots deleted on server
accepted Client noots accepted by server
conflicts Noots with version conflicts

GET /checksums#

Get server checksums for comparison.

Response (200):

{
  "noots": [
    {"id": "uuid-1", "checksum": "sha256..."},
    {"id": "uuid-2", "checksum": "sha256..."}
  ]
}

POST /fetch#

Fetch specific noots by ID.

Request:

{
  "nootIds": ["uuid-1", "uuid-2", "uuid-3"]
}

Response (200):

{
  "noots": [
    {
      "id": "uuid-1",
      "version": 1,
      "activities": [...]
    }
  ]
}

Sync Algorithm#

Client-Side#

  1. Get local checksums
  2. Send to /sync with pending changes
  3. Apply server changes
  4. Mark conflicts for resolution
// Simplified sync flow
final localChecksums = nootPool.getChecksums();
final pendingNoots = nootPool.syncQueue
    .map((e) => e.noot.toJson())
    .toList();

final result = await apiClient.sync(
  noots: pendingNoots,
  knownNoots: localChecksums,
);

// Apply server changes
nootPool.mergeFromServer(
  result.serverNoots,
  result.deletedIds,
);

// Clear synced items
nootPool.clearSyncQueue();

Conflict Resolution#

Strategy: Last-Write-Wins

  • Higher version number takes precedence
  • If versions equal, server wins
  • Conflicts array returned for manual resolution

Checksum Format#

SHA-256 hash of normalized JSON:

String generateChecksum(Noot noot) {
  final normalized = jsonEncode({
    'activities': noot.activities.map((a) => a.toJson()).toList(),
  });
  return 'sha256:${sha256.convert(utf8.encode(normalized))}';
}