getCourseOffering method
- String number
Reads number's offering detail (overview + schedule + teachers +
classes), falling back to the course system when it is not cached.
Not a refreshX — a looked-up offering is not cached, so the detail is
returned rather than written for a stream to re-emit, and only its
semester row is persisted. Submitted syllabuses are fetched separately
via watchSyllabuses. Returns null only when the course system
reports that the number does not exist.
Course numbers are globally unique and identify at most one offering.
Implementation
Future<CourseOfferingDetail?> getCourseOffering(String number) async {
final overviews = _database.courseOfferingOverviews;
final cached =
await (_database.select(overviews).join([
innerJoin(
_database.semesters,
_database.semesters.id.equalsExp(overviews.semester),
),
])
..where(overviews.number.equals(number))
..orderBy([
.desc(_database.semesters.year),
.desc(_database.semesters.term),
])
..limit(1))
.getSingleOrNull();
if (cached?.readTable(overviews) case final overview?) {
final offeringId = overview.id;
final (schedule, teachers, classes) = await (
_readOfferingSchedule(offeringId),
_readOfferingTeachers(offeringId),
_readOfferingClasses(offeringId),
).wait;
return (
overview: overview,
schedule: schedule,
teachers: teachers,
classes: classes,
);
}
final dto = await _authRepository.withAuth(
() => _courseService.getCourseOffering(number),
sso: [.courseService],
);
if (dto == null) return null;
final semester = await _database.getOrCreateSemester(
dto.semester.year!,
dto.semester.term!,
);
final schedule = dto.schedule;
return (
// TODO: sentinel id because the offering itself is not persisted, so it
// has no row to report. No caller reads `overview.id` — give
// CourseOfferingDetail a DTO-backed variant if that stops being true.
overview: CourseOfferingOverview(
id: -1,
courseCode: schedule.course?.id,
semester: semester.id,
number: schedule.number,
nameZh: schedule.course?.nameZh,
nameEn: schedule.course?.nameEn,
credits: schedule.credits,
hours: schedule.hours,
phase: schedule.phase,
courseType: dto.courseType,
status: schedule.status,
language: schedule.language,
remarks: schedule.remarks,
enrolled: dto.enrolled,
withdrawn: dto.withdrawn,
fetchedAt: DateTime.now(),
),
schedule: [
for (final slot
in schedule.schedule ??
const <
({
DayOfWeek day,
Period period,
ReferenceDto? classroom,
})
>[])
(
day: slot.day,
period: slot.period,
classroomNameZh: slot.classroom?.name,
classroomNameEn: switch (slot.classroom?.name) {
final name? => translateClassroomName(name),
null => null,
},
),
],
teachers: [
for (final teacher in schedule.teachers ?? const <LocalizedRefDto>[])
if (teacher case LocalizedRefDto(:final id?, :final nameZh?))
(code: id, nameZh: nameZh, nameEn: teacher.nameEn),
],
classes: [
for (final entity in schedule.classes ?? const <LocalizedRefDto>[])
if (entity case LocalizedRefDto(:final id?, :final nameZh?))
(code: id, nameZh: nameZh, nameEn: entity.nameEn),
],
);
}