Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ MVVM pattern with Riverpod for DI and reactive state (manual providers, no codeg

- AuthRepository — User identity, session, profile. Lazy auth via `withAuth<T>()` with SSO and re-auth coalescing (Completer pattern). Session persistence via flutter_secure_storage. Never-completing future on auth failure (harmless — session-scoped providers are already being disposed).
- PreferencesRepository — Typed `PrefKey<T>` enum with SharedPreferencesAsync. Cloud sync via avatar payload.
- CourseRepository — Course catalog, schedules, and offering details. Normalizes bilingual names from multiple sources (catalog vs offering). Layout computation for course table grid (multi-period spans, noon-crossing, unscheduled courses).
- CourseRepository — Course catalog, schedules, offering details, and related I-School Plus data. Normalizes bilingual names from multiple sources (catalog vs offering). Layout computation for course table grid (multi-period spans, noon-crossing, unscheduled courses).
- CalendarRepository — Academic calendar events from NTUT portal. Sliding window caching keyed to enrolled semesters.
- StudentRepository — Academic records, GPA, rankings. Parallel course code resolution via CourseRepository.getCourse().
- **Method pattern:** `watchX()` returns a `Stream` backed by Drift `.watch()` — emits cached data immediately, then background-fetches if empty or stale (each method has its own hard-coded TTL `const`). Network errors are absorbed (stale data preferred over errors). `refreshX()` is the imperative counterpart for pull-to-refresh — fetches from network, writes to DB, and lets the stream re-emit.
Expand Down Expand Up @@ -146,4 +146,4 @@ These apOu codes are the SSO target identifiers used by PortalService to obtain

## Backlog

Open work is tracked in [GitHub Issues](https://github.com/NTUT-NPC/tattoo/issues). Key areas: remaining NTUT service methods (ISchoolPlus announcements, StudentQuery extensions), repository layer gaps (materials, rosters), and file download infrastructure.
Open work is tracked in [GitHub Issues](https://github.com/NTUT-NPC/tattoo/issues). Key areas: remaining NTUT service methods (ISchoolPlus announcements, StudentQuery extensions), repository layer gaps (materials), and file download infrastructure.
20 changes: 20 additions & 0 deletions lib/database/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,24 @@ extension DatabaseActions on AppDatabase {
),
)).id;
}

/// Returns the ID of an existing student row, or creates/updates one.
///
/// A null [name] never overwrites a previously recorded name — some
/// I-School Plus rosters omit the name for certain students.
Future<int> upsertStudent({
required String studentId,
String? name,
}) async {
return (await into(students).insertReturning(
StudentsCompanion.insert(
studentId: studentId,
name: Value(name),
),
onConflict: DoUpdate(
(old) => StudentsCompanion(name: .absentIfNull(name)),
target: [students.studentId],
),
)).id;
}
}
87 changes: 84 additions & 3 deletions lib/database/database.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/database/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ class CourseOfferings extends Table with AutoIncrementId, Fetchable {
/// Teacher-authored remarks from the syllabus page (備註).
late final syllabusRemarks = text().nullable()();

/// When the I-School Plus roster (classmates) was last fetched for this
/// offering. Null until the first roster fetch.
late final rosterFetchedAt = dateTime().nullable()();

@override
List<Set<Column>> get uniqueKeys => [
{semester, number},
Expand Down
Loading