timetracker/.ai/database.md
2026-08-03 21:51:48 +02:00

2.7 KiB

Database — Drift Schema & Conventions

Tables

projects

Column Type Notes
id INTEGER PK, autoincrement
name TEXT NOT NULL, unique
color_value INTEGER NOT NULL (ARGB int)
description TEXT nullable
archived_at DATETIME nullable; null = active
created_at DATETIME NOT NULL, default now

time_entries

Column Type Notes
id INTEGER PK, autoincrement
project_id INTEGER FK → projects.id, NOT NULL
start_time DATETIME NOT NULL
end_time DATETIME nullable; null = timer still running
duration_s INTEGER seconds; computed on stop
note TEXT nullable
created_at DATETIME NOT NULL, default now

tags

Column Type Notes
id INTEGER PK, autoincrement
name TEXT NOT NULL, unique

time_entry_tags (junction)

Column Type Notes
time_entry_id INTEGER FK → time_entries
tag_id INTEGER FK → tags
PRIMARY KEY (time_entry_id, tag_id)

Drift Setup

// lib/core/database/app_database.dart
@DriftDatabase(tables: [Projects, TimeEntries, Tags, TimeEntryTags])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dir = await getApplicationDocumentsDirectory();
    final file = File(p.join(dir.path, 'timetrack.db'));
    return NativeDatabase.createInBackground(file);
  });
}

DAOs

  • ProjectsDao — CRUD for projects, watchAll(), watchActive()
  • TimeEntriesDao — CRUD, watchByProject(), watchByDateRange(), getActiveEntry()
  • TagsDao — CRUD, watchAll()

Migration Strategy

  • Schema version starts at 1
  • Increment schemaVersion for every breaking change
  • Add a MigrationStrategy with onUpgrade steps
  • Never drop columns — use nullable columns for additions
  • Test migrations with drift_dev schema tests

Riverpod Provider

@Riverpod(keepAlive: true)
AppDatabase appDatabase(AppDatabaseRef ref) {
  final db = AppDatabase();
  ref.onDispose(db.close);
  return db;
}