A todo and personal organisation app
Architecture#
Overview#
Toadist uses a client-server architecture with offline-first capabilities.
┌─────────────────┐ ┌─────────────────┐
│ Flutter Client │────▶│ Dart Server │
│ │◀────│ │
│ - Local Storage│ │ - PostgreSQL │
│ - Noot Pool │ │ - REST API │
│ - Views │ │ - WebSocket │
└─────────────────┘ └─────────────────┘
Client Architecture#
Layer Structure#
┌────────────────────────────────────┐
│ Views (UI) │
├────────────────────────────────────┤
│ AppService │
├────────────────────────────────────┤
│ NootPool │ ApiClient │ Storage │
├────────────────────────────────────┤
│ Models (Noot, Activity) │
└────────────────────────────────────┘
Key Components#
| Component | Responsibility |
|---|---|
| AppService | Central service managing auth, sync, and connectivity |
| NootPool | In-memory store with filtering, sorting, and event streaming |
| ApiClient | HTTP client for server communication |
| LocalStorage | JSON file persistence for offline mode |
| EncryptionService | AES-256-GCM encryption for sensitive data |
Server Architecture#
Request Flow#
Request → CORS → Auth → Router → Handler → Response
↓
Database
Components#
| Component | Responsibility |
|---|---|
| AuthMiddleware | JWT token validation |
| DatabaseService | PostgreSQL connection pool |
| StorageService | Noot CRUD operations |
| WebSocketHandler | Real-time event broadcasting |
Data Model#
Noot Structure#
Noot
├── id: UUID
├── version: int
├── createdAt: DateTime
├── modifiedAt: DateTime
├── deleted: bool
├── activities: List<Activity>
└── checksum: SHA-256
Activity System#
- Base
Activityclass withActivityBaseProperties - Each activity type extends
Activity - Activities serialize to/from JSON
ActivityRegistrymanages type registration
Sync Protocol#
Checksum-Based Sync#
- Client sends local noot checksums
- Server compares with stored checksums
- Server returns:
- Changed noots (different checksums)
- Deleted noot IDs
- New noots from client to store
Conflict Resolution#
- Last-write-wins based on version number
- Higher version takes precedence
- Local changes queue until sync completes
Security#
Authentication#
- JWT tokens with 24-hour expiration
- Refresh tokens for 30-day sessions
- Secure token storage via SharedPreferences
Encryption#
- AES-256-GCM for sensitive activities
- PBKDF2 key derivation from user password
- Per-activity encryption (opt-in via
encryptedflag)