refreshSyllabuses method
- required String courseNumber,
- required SyllabusLanguage language,
Fetches every associated teacher's language syllabus for the offering
identified by courseNumber and persists the complete result atomically.
Responses without a last-updated timestamp or any non-blank section content are treated as unsubmitted: their sections are removed while a metadata row retains the fetch timestamp for TTL caching. Submitted metadata and ordered sections are upserted, instructor emails are written to TeacherSemesters, and shared header fields are copied from the first submitted response. Missing offerings and offerings without teachers are no-ops; network errors propagate without writes.
Implementation
Future<void> refreshSyllabuses({
required String courseNumber,
required SyllabusLanguage language,
}) async {
final context = await _readSyllabusContext(courseNumber);
if (context == null || context.teachers.isEmpty) return;
final offering = context.offering;
final teachers = context.teachers;
final results = await _authRepository.withAuth(
() => Future.wait([
for (final teacher in teachers)
_courseService
.getSyllabus(
courseNumber: courseNumber,
teacherId: teacher.teacher.code,
language: language,
)
.then((syllabus) => (teacher: teacher, syllabus: syllabus)),
]),
sso: [.courseService],
);
await _database.transaction(() async {
final fetchedAt = DateTime.now();
for (final result in results) {
final teacher = result.teacher.teacher;
final teacherSemester = result.teacher.teacherSemester;
final syllabus = result.syllabus;
if (syllabus == null ||
syllabus.lastUpdated == null ||
!syllabus.sections.any(
(section) => section.content?.trim().isNotEmpty ?? false,
)) {
final storedSyllabus = await _database
.into(_database.syllabuses)
.insertReturning(
SyllabusesCompanion.insert(
courseOffering: offering.id,
teacher: teacher.id,
language: language,
fetchedAt: fetchedAt,
),
onConflict: DoUpdate(
(_) => SyllabusesCompanion(
updatedAt: const Value(null),
fetchedAt: Value(fetchedAt),
),
target: [
_database.syllabuses.courseOffering,
_database.syllabuses.teacher,
_database.syllabuses.language,
],
),
);
await (_database.delete(_database.syllabusSections)..where(
(section) => section.syllabus.equals(storedSyllabus.id),
))
.go();
continue;
}
final storedSyllabus = await _database
.into(_database.syllabuses)
.insertReturning(
SyllabusesCompanion.insert(
courseOffering: offering.id,
teacher: teacher.id,
language: language,
updatedAt: Value(syllabus.lastUpdated),
fetchedAt: fetchedAt,
),
onConflict: DoUpdate(
(_) => SyllabusesCompanion(
updatedAt: Value(syllabus.lastUpdated),
fetchedAt: Value(fetchedAt),
),
target: [
_database.syllabuses.courseOffering,
_database.syllabuses.teacher,
_database.syllabuses.language,
],
),
);
await (_database.delete(_database.syllabusSections)..where(
(section) => section.syllabus.equals(storedSyllabus.id),
))
.go();
await _database.batch((batch) {
batch.insertAll(_database.syllabusSections, [
for (final (position, section) in syllabus.sections.indexed)
SyllabusSectionsCompanion.insert(
syllabus: storedSyllabus.id,
title: section.title,
content: Value(section.content),
position: position,
),
]);
});
await (_database.update(
_database.teacherSemesters,
)..where((stored) => stored.id.equals(teacherSemester.id))).write(
TeacherSemestersCompanion(email: Value(syllabus.email)),
);
}
final firstSubmitted = results
.map((result) => result.syllabus)
.nonNulls
.where(
(syllabus) =>
syllabus.lastUpdated != null &&
syllabus.sections.any(
(section) => section.content?.trim().isNotEmpty ?? false,
),
)
.firstOrNull;
if (firstSubmitted case final firstSubmitted?) {
await (_database.update(
_database.courseOfferings,
)..where((stored) => stored.id.equals(offering.id))).write(
CourseOfferingsCompanion(
courseType: Value(firstSubmitted.type),
enrolled: Value(firstSubmitted.enrolled),
withdrawn: Value(firstSubmitted.withdrawn),
fetchedAt: Value(fetchedAt),
),
);
}
});
}