timetracker/.tasks/done/TT-004-repository-implementations.md
2026-08-03 21:51:48 +02:00

92 lines
3.2 KiB
Markdown

# TT-004 — Repository Implementations
**Type:** Story
**Priority:** High
**Labels:** data-layer, repositories
**Depends on:** TT-002, TT-003
**Blocks:** TT-005
---
## Summary
Implement the concrete repository classes for each feature. Repositories act
as the single access point for the data layer; they delegate to Drift DAOs
and map DB rows to domain models.
## Repositories to implement
### `ProjectsRepository`
File: `lib/features/projects/data/projects_repository.dart`
```dart
abstract class ProjectsRepository {
Stream<List<Project>> watchAll();
Stream<List<Project>> watchActive(); // archivedAt IS NULL
Future<Project> getById(int id);
Future<int> create({required String name, required int colorValue, String? description});
Future<void> update(Project project);
Future<void> archive(int id); // sets archivedAt = now
Future<void> delete(int id);
}
```
### `TimeEntriesRepository`
File: `lib/features/entries/data/entries_repository.dart`
```dart
abstract class TimeEntriesRepository {
Stream<List<TimeEntry>> watchAll();
Stream<List<TimeEntry>> watchByProject(int projectId);
Stream<List<TimeEntry>> watchByDateRange(DateTime from, DateTime to);
Future<TimeEntry?> getActiveEntry(); // end_time IS NULL
Future<int> create(TimeEntry entry);
Future<void> update(TimeEntry entry);
Future<void> delete(int id);
}
```
### `TagsRepository`
File: `lib/features/entries/data/tags_repository.dart`
```dart
abstract class TagsRepository {
Stream<List<Tag>> watchAll();
Future<Tag> findOrCreate(String name);
Future<void> setTagsForEntry(int entryId, List<String> tagNames);
Future<List<Tag>> getTagsForEntry(int entryId);
}
```
### `ReportsRepository`
File: `lib/features/reports/data/reports_repository.dart`
```dart
abstract class ReportsRepository {
Future<Duration> getTotalDuration(DateTime from, DateTime to, {int? projectId});
Future<Map<int, Duration>> getDurationByProject(DateTime from, DateTime to);
Future<Map<int, Duration>> getDurationByWeekday(DateTime weekStart);
Future<Map<int, Duration>> getDurationByDay(DateTime monthStart);
}
```
## Acceptance Criteria
- [ ] Abstract interface + concrete `DriftXxxRepository` implementation for each
- [ ] Riverpod provider for each concrete implementation
- [ ] All DB access goes through DAOs — no raw SQL in repositories
- [ ] Mapper extension methods convert `Drift row → domain model`
- [ ] Unit tests for each repository method using in-memory Drift DB
## Files to create / modify
- `lib/features/projects/data/projects_repository.dart` (replace stub)
- `lib/features/projects/data/drift_projects_repository.dart`
- `lib/features/entries/data/entries_repository.dart` (replace stub)
- `lib/features/entries/data/drift_entries_repository.dart`
- `lib/features/entries/data/tags_repository.dart`
- `lib/features/entries/data/drift_tags_repository.dart`
- `lib/features/reports/data/reports_repository.dart` (replace stub)
- `lib/features/reports/data/drift_reports_repository.dart`
- `test/features/*/` — unit tests for each
## Notes
- Keep repositories free of Flutter/UI imports
- Streams from Drift are reactive — repositories expose them directly to Riverpod providers