refreshStudents method
- int courseOfferingId
Fetches the fresh roster from I-School Plus and writes it to the DB.
The watchStudents stream automatically emits the updated value. Network errors propagate to the caller.
Not every course-system offering exists on I-School Plus (e.g. internships, or special entries with no offering number) — these resolve to an empty roster. The fetch timestamp is recorded either way.
Implementation
Future<void> refreshStudents(int courseOfferingId) async {
final offering = await (_database.select(
_database.courseOfferings,
)..where((o) => o.id.equals(courseOfferingId))).getSingleOrNull();
if (offering == null) return;
final number = offering.number;
final dtos = number == null
? const <StudentDto>[]
: await _authRepository.withAuth(() async {
// Resolve the offering number to its internal I-School Plus handle.
final courses = await _iSchoolPlusService.getCourseList();
for (final course in courses) {
if (course.courseNumber == number) {
return _iSchoolPlusService.getStudents(course);
}
}
// Offering not available on I-School Plus.
return <StudentDto>[];
}, sso: [.iSchoolPlusService]);
await _database.transaction(() async {
// Replace the roster for this offering.
await (_database.delete(
_database.courseOfferingStudents,
)..where((s) => s.courseOffering.equals(courseOfferingId))).go();
for (final dto in dtos) {
// Students are keyed by their ID; skip rows missing one.
if (dto.id case final studentId?) {
final studentRowId = await _database.upsertStudent(
studentId: studentId,
name: dto.name,
);
await _database
.into(_database.courseOfferingStudents)
.insert(
CourseOfferingStudentsCompanion.insert(
courseOffering: courseOfferingId,
student: studentRowId,
),
mode: .insertOrIgnore,
);
}
}
await (_database.update(
_database.courseOfferings,
)..where((o) => o.id.equals(courseOfferingId))).write(
CourseOfferingsCompanion(rosterFetchedAt: Value(DateTime.now())),
);
});
}