From 331f3dd91c8de139266d0fc093b82ba2f24c6514 Mon Sep 17 00:00:00 2001 From: Kev Wang Date: Sun, 22 Oct 2023 17:22:16 -0700 Subject: [PATCH] Move db files to models and update SIS models Updated SIS models based on official documentation instead of just sampling. Also, since these schemas also describe API responses, I felt it more appropriate to (re)name them to models. --- backend/src/bootstrap/loaders/passport.ts | 2 +- backend/src/db/class.ts | 115 --------- backend/src/db/course.ts | 207 ---------------- backend/src/db/section.ts | 264 --------------------- backend/src/models/class.ts | 90 +++++++ backend/src/models/common.ts | 7 + backend/src/models/course.ts | 150 ++++++++++++ backend/src/{db => models}/grade.ts | 6 +- backend/src/{db => models}/schedule.ts | 22 +- backend/src/models/section.ts | 141 +++++++++++ backend/src/models/semester.ts | 13 + backend/src/{db => models}/user.ts | 6 +- backend/src/modules/catalog/controller.ts | 8 +- backend/src/modules/catalog/formatter.ts | 14 +- backend/src/modules/grade/controller.ts | 2 +- backend/src/modules/schedule/controller.ts | 2 +- backend/src/modules/schedule/formatter.ts | 2 +- backend/src/modules/user/controller.ts | 6 +- backend/src/modules/user/formatter.ts | 2 +- backend/src/utils/course.ts | 2 +- backend/src/utils/sis.ts | 30 +++ 21 files changed, 468 insertions(+), 623 deletions(-) delete mode 100644 backend/src/db/class.ts delete mode 100644 backend/src/db/course.ts delete mode 100644 backend/src/db/section.ts create mode 100644 backend/src/models/class.ts create mode 100644 backend/src/models/common.ts create mode 100644 backend/src/models/course.ts rename backend/src/{db => models}/grade.ts (78%) rename backend/src/{db => models}/schedule.ts (74%) create mode 100644 backend/src/models/section.ts create mode 100644 backend/src/models/semester.ts rename backend/src/{db => models}/user.ts (90%) create mode 100644 backend/src/utils/sis.ts diff --git a/backend/src/bootstrap/loaders/passport.ts b/backend/src/bootstrap/loaders/passport.ts index 2025b276c..1b036f705 100644 --- a/backend/src/bootstrap/loaders/passport.ts +++ b/backend/src/bootstrap/loaders/passport.ts @@ -9,7 +9,7 @@ import type { Application } from "express"; import session from "express-session"; import passport from "passport"; import GoogleStrategy from "passport-google-oauth20"; -import { UserModel } from "../../db/user"; +import { UserModel } from "../../models/user"; import { config } from "../../config"; const LOGIN_ROUTE = "/login"; diff --git a/backend/src/db/class.ts b/backend/src/db/class.ts deleted file mode 100644 index b7cc4473c..000000000 --- a/backend/src/db/class.ts +++ /dev/null @@ -1,115 +0,0 @@ -import mongoose, { Schema, InferSchemaType } from "mongoose"; -import { createHistorySchema } from "../utils/history"; - -const ClassSchemaObject = { - _id: Schema.Types.ObjectId, - _created: Date, - _updated: Date, - _version: Number, - aggregateEnrollmentStatus: { - enrolledCount: Number, - instructorAddConsentRequired: Boolean, - instructorDropConsentRequired: Boolean, - maxEnroll: Number, - maxWaitlist: Number, - minEnroll: Number, - status: { - code: String, - description: String, - }, - waitlistedCount: Number, - }, - allowedUnits: { - forAcademicProgress: Number, - forFinancialAid: Number, - maximum: Number, - minimum: Number, - }, - anyFeesExist: Boolean, - anyPrintInScheduleOfClasses: Boolean, - anyPrintInstructors: Boolean, - assignedClassMaterials: { - noneAssigned: Boolean, - status: { - }, - }, - blindGrading: Boolean, - classDescription: String, // coverage: 2.3% - classTitle: String, // coverage: 2.2% - contactHours: Number, - course: { - catalogNumber: { - formatted: String, - number: String, - prefix: String, // coverage: 12.9% - suffix: String, // coverage: 24.3% - }, - displayName: String, - identifiers: [{ - id: String, - type: { type: String }, - }], - requisites: { // coverage: 0.8% - code: String, - description: String, - formalDescription: String, - }, - subjectArea: { - code: String, - description: String, // coverage: 99.1% - }, - title: String, - transcriptTitle: String, - }, - displayName: String, - finalExam: { - code: String, - description: String, - }, - gradingBasis: { - code: String, - description: String, - }, - instructionMode: { - code: String, - description: String, // coverage: 99.3% - }, - lastCancelled: String, // coverage: 1.6% - number: String, - offeringNumber: Number, - primaryComponent: { - code: String, - description: String, - }, - requirementDesignation: { // coverage: 2.1% - code: String, - description: String, - }, - requisites: { // coverage: 0.5% - code: String, - description: String, - formalDescription: String, - }, - session: { - id: String, - name: String, - term: { - id: String, - name: String, - }, - }, - status: { - code: String, - description: String, - }, -} - -const ClassSchema = new Schema(ClassSchemaObject); - -export const ClassModel = mongoose.model("sis_class", ClassSchema, "sis_class"); -export type ClassType = InferSchemaType; - -const ClassHistorySchema = createHistorySchema(ClassSchemaObject, "sis_class"); - -export const ClassHistoryModel = mongoose.model("sis_class_history", ClassHistorySchema, "sis_class_history"); -export type ClassHistoryType = InferSchemaType; \ No newline at end of file diff --git a/backend/src/db/course.ts b/backend/src/db/course.ts deleted file mode 100644 index d65f244f6..000000000 --- a/backend/src/db/course.ts +++ /dev/null @@ -1,207 +0,0 @@ -import mongoose, { Schema, InferSchemaType } from "mongoose"; -import { createHistorySchema } from "../utils/history"; - -const CourseSchemaObject = { - _id: Schema.Types.ObjectId, - _created: Date, - _updated: Date, - _version: Number, - academicCareer: { - code: String, - description: String, - }, - academicGroup: { - code: String, - description: String, - }, - academicOrganization: { - code: String, - description: String, - }, - allowMultipleEnrollments: Boolean, - anyFeesExist: Boolean, - blindGrading: Boolean, - catalogNumber: { - formatted: String, - number: String, - prefix: String, - suffix: String, - }, - cip: { - code: String, - description: String, - }, - classDisplayName: String, - classSubjectArea: { - code: String, - description: String, - }, - contactHours: Number, - courseObjectives: [String], // coverage: 1.4% - createdDate: String, - credit: { - type: { type: String }, - value: { - discrete: { // coverage: 1.7% - units: [Number], - }, - fixed: { // coverage: 82.9% - units: Number, - }, - range: { // coverage: 15.4% - maxUnits: Number, - minUnits: Number, - }, - }, - }, - creditRestriction: { // coverage: 43.5% - restrictionCourses: { - creditRestrictionCourses: [{ - course: { - displayName: String, - identifiers: [{ - id: String, - type: { type: String }, - }], - }, - maxCreditPercentage: Number, // coverage: 88.9% - }], - }, - restrictionText: String, - }, - crossListing: { // coverage: 3.5% - count: Number, - courses: [String], - }, - departmentNicknames: String, - description: String, - displayName: String, // use classDisplayName instead, since that is consistent with the other API data - finalExam: { - code: String, // coverage: 99.7% - description: String, // coverage: 99.7% - }, - formatsOffered: { - description: String, - formats: [{ - aggregateMaxContactHours: Number, - aggregateMinContactHours: Number, - anyFeesExist: Boolean, - components: [{ - feesExist: Boolean, - finalExam: { - }, - instructionMethod: { - code: String, - description: String, - }, - maxContactHours: Number, - minContactHours: Number, - primary: Boolean, - }], - description: String, // coverage: 99.2% - maxWorkloadHours: Number, - minWorkloadHours: Number, - sessionType: String, - termsAllowed: { - termNames: [String], - }, - }], - summerOnly: Boolean, - typicallyOffered: { - comments: String, - terms: { - termNames: [String], - }, - }, - }, - formerDisplayName: String, - fromDate: String, - gradeReplacement: { // coverage: 13.8% - gradeReplacementCourses: [{ // coverage: 28.3% - catalogNumber: { - }, - displayName: String, - identifiers: [{ - id: String, - type: { type: String }, - }], - subjectArea: { - }, - }], - gradeReplacementGroup: String, // coverage: 18.1% - gradeReplacementText: String, // coverage: 73.9% - }, - gradingBasis: { - code: String, - description: String, - }, - hegis: { - code: String, - description: String, - }, - identifiers: [{ - id: String, - type: { type: String }, - }], - instructorAddConsentRequired: Boolean, // coverage: 21.3% - instructorDropConsentRequired: Boolean, - multipleTermNumber: Number, // coverage: 91.3% - preparation: { - requiredCourses: { - courses: [{ // coverage: 19.5% - displayName: String, - identifiers: [{ - id: String, - type: { type: String }, - }], - }], - }, - requiredText: String, // coverage: 60.5% - }, - primaryInstructionMethod: { - code: String, - description: String, - }, - printInCatalog: Boolean, - printInstructors: Boolean, - proposedInstructors: [String], - repeatability: { - description: String, // coverage: 31.1% - maxCount: Number, // coverage: 0.2% - maxCredit: Number, // coverage: 0.9% - repeatable: Boolean, - }, - requirementsFulfilled: [{ // coverage: 3.6% - code: String, - description: String, - }], - spansMultipleTerms: Boolean, // coverage: 91.3% - status: { - code: String, - description: String, - }, - studentLearningOutcomes: [String], // coverage: 1.5% - subjectArea: { // use classSubjectArea instead, since that is consistent with the other API data - code: String, - description: String, - }, - tie: { - code: String, // coverage: 47.3% - description: String, // coverage: 47.3% - }, - title: String, - toDate: String, - transcriptTitle: String, - updatedDate: String, - workloadHours: Number, -} - -const CourseSchema = new Schema(CourseSchemaObject); - -export const CourseModel = mongoose.model("sis_course", CourseSchema, "sis_course"); -export type CourseType = InferSchemaType; - -const CourseHistorySchema = createHistorySchema(CourseSchemaObject, "sis_course"); - -export const CourseHistoryModel = mongoose.model("sis_course_history", CourseHistorySchema, "sis_course_history"); -export type CourseHistoryType = InferSchemaType; \ No newline at end of file diff --git a/backend/src/db/section.ts b/backend/src/db/section.ts deleted file mode 100644 index 4845413b8..000000000 --- a/backend/src/db/section.ts +++ /dev/null @@ -1,264 +0,0 @@ -import mongoose, { Schema, InferSchemaType } from "mongoose" -import { createHistorySchema } from "../utils/history"; - -const SectionSchemaObject = { - _id: Schema.Types.ObjectId, - _created: Date, - _updated: Date, - _version: Number, - academicGroup: { - code: String, - description: String, - formalDescription: String, - }, - academicOrganization: { - code: String, - description: String, - formalDescription: String, - }, - addConsentRequired: { - code: String, - description: String, - }, - association: { - associatedClass: Number, - primary: Boolean, - primaryAssociatedComponent: { - code: String, // coverage: 98.3% - description: String, // coverage: 98.3% - }, - primaryAssociatedSectionId: Number, - primaryAssociatedSectionIds: [Number], - }, - cancelDate: String, // coverage: 1.2% - class: { - allowedUnits: { - forAcademicProgress: Number, - forFinancialAid: Number, - maximum: Number, - minimum: Number, - }, - course: { - catalogNumber: { - formatted: String, - number: String, - prefix: String, // coverage: 14.3% - suffix: String, // coverage: 26.9% - }, - displayName: String, - identifiers: [{ - id: String, - type: { type: String }, - }], - requisites: { // coverage: 1.2% - code: String, - description: String, - formalDescription: String, - }, - subjectArea: { - code: String, - description: String, // coverage: 99.9% - }, - title: String, - transcriptTitle: String, - }, - displayName: String, - gradingBasis: { - code: String, // coverage: 98.3% - description: String, // coverage: 98.3% - }, - number: String, - offeringNumber: Number, - requirementDesignation: { // coverage: 2.6% - code: String, - description: String, - }, - requisites: { // coverage: 0.4% - code: String, - description: String, - formalDescription: String, - }, - session: { - id: String, - name: String, - term: { - id: String, - name: String, - }, - }, - }, - combination: { // coverage: 3.9% - combinedSections: [Number], - description: String, - enrolledCountCombinedSections: Number, - id: String, - maxEnrollCombinedSections: Number, - maxWaitlistCombinedSections: Number, - type: { - code: String, - description: String, - }, - waitlistedCountCombinedSections: Number, - }, - component: { - code: String, - description: String, - }, - displayName: String, - dropConsentRequired: { - code: String, - description: String, - }, - endDate: String, - enrollmentStatus: { - enrolledCount: Number, - maxEnroll: Number, - maxWaitlist: Number, - minEnroll: Number, - openReserved: Number, - reservedCount: Number, - seatReservations: [{ // coverage: 3.6% - enrolledCount: Number, - fromDate: String, - maxEnroll: Number, - number: Number, - requirementGroup: { - code: String, - description: String, - }, - }], - status: { - code: String, - description: String, - }, - waitlistedCount: Number, - }, - exams: [{ // coverage: 4.5% - building: { - code: String, // coverage: 72.5% - description: String, // coverage: 72.5% - }, - date: String, - endTime: String, - location: { - code: String, // coverage: 80.4% - description: String, // coverage: 72.5% - }, - number: Number, - startTime: String, - type: { - code: String, - description: String, - }, - }], - feesExist: Boolean, - graded: Boolean, - id: Number, - instructionMode: { - code: String, - description: String, // coverage: 99.0% - }, - meetings: [{ // coverage: 84.7% - assignedInstructors: [{ // coverage: 83.9% - assignmentNumber: Number, - contactMinutes: Number, - gradeRosterAccess: { - code: String, // coverage: 98.3% - description: String, // coverage: 98.3% - formalDescription: String, // coverage: 98.3% - }, - instructor: { - identifiers: [{ - disclose: Boolean, - id: String, - type: { type: String }, - }], - names: [{ // coverage: 99.1% - disclose: Boolean, - familyName: String, - formattedName: String, - fromDate: String, - givenName: String, - type: { - code: String, - description: String, - }, - uiControl: { - code: String, - description: String, - }, - }], - }, - printInScheduleOfClasses: Boolean, - role: { - code: String, - description: String, - formalDescription: String, - }, - }], - building: { - code: String, // coverage: 33.4% - description: String, // coverage: 33.4% - }, - endDate: String, - endTime: String, - location: { - code: String, // coverage: 37.8% - description: String, // coverage: 33.4% - }, - meetingDescription: String, // coverage: 0.5% - meetingTopic: { - }, - meetsDays: String, // coverage: 40.5% - meetsFriday: Boolean, - meetsMonday: Boolean, - meetsSaturday: Boolean, - meetsSunday: Boolean, - meetsThursday: Boolean, - meetsTuesday: Boolean, - meetsWednesday: Boolean, - number: Number, - startDate: String, - startTime: String, - }], - number: String, - printInScheduleOfClasses: Boolean, - roomCharacteristics: [{ // coverage: 3.6% - code: String, - description: String, - quantity: Number, - }], - roomShare: Boolean, - sectionAttributes: [{ // coverage: 99.9% - attribute: { - code: String, - description: String, // coverage: 96.7% - formalDescription: String, - }, - value: { - code: String, - description: String, // coverage: 96.7% - formalDescription: String, // coverage: 100.0% - }, - }], - startDate: String, - status: { - code: String, - description: String, - }, - type: { - code: String, - description: String, - formalDescription: String, - }, -} - -const SectionSchema = new Schema(SectionSchemaObject) - -export const SectionModel = mongoose.model("sis_class_section", SectionSchema, "sis_class_section"); -export type SectionType = InferSchemaType; - -const SectionHistorySchema = createHistorySchema(SectionSchemaObject, "sis_class_section") - -export const SectionHistoryModel = mongoose.model("sis_class_section_history", SectionHistorySchema, "sis_class_section_history"); -export type SectionHistoryType = InferSchemaType; \ No newline at end of file diff --git a/backend/src/models/class.ts b/backend/src/models/class.ts new file mode 100644 index 000000000..aafd59188 --- /dev/null +++ b/backend/src/models/class.ts @@ -0,0 +1,90 @@ +import { schemaOptions } from './common'; +import { descriptor } from '../utils/sis'; +import { courseSchema } from './course'; +import mongoose, { Schema, InferSchemaType } from 'mongoose'; + +// source: https://developers.api.berkeley.edu/api/18/interactive-docs +const classSchemaObject = { + course: courseSchema, + offeringNumber: Number, + session: { + id: String, + name: String, + term: { + id: String, + name: String, + }, + timePeriods: [{ + period: descriptor, + endDate: Date, + }], + }, + number: String, + displayName: String, + classTitle: String, + classTranscriptTitle: String, + classDescription: String, + primaryComponent: descriptor, + allowedUnits: { + minimum: Number, + maximum: Number, + forAcademicProgress: Number, + forFinancialAid: Number, + }, + gradingBasis: descriptor, + requirementDesignation: descriptor, + contactHours: Number, + blindGrading: Boolean, + assignedClassMaterials: { + status: descriptor, + noneAssigned: Boolean, + instructions: String, + classMaterials: [{ + sequenceNumber: Number, + type: { type: descriptor }, + status: descriptor, + title: String, + author: String, + isbn: String, + yearPublished: String, + publisher: String, + edition: String, + price: { + amount: Number, + currency: descriptor, + }, + notes: String, + }], + }, + instructionMode: descriptor, + status: descriptor, + lastCancelled: String, + anyPrintInScheduleOfClasses: Boolean, + anyPrintInstructors: Boolean, + anyFeesExist: Boolean, + finalExam: descriptor, + aggregateEnrollmentStatus: { + status: descriptor, + enrolledCount: Number, + reservedCount: Number, + waitlistedCount: Number, + minEnroll: Number, + maxEnroll: Number, + maxWaitlist: Number, + openReserved: Number, + instructorAddConsentRequired: Boolean, + instructorDropConsentRequired: Boolean, + seatReservations: [{ + number: Number, + requirementGroup: descriptor, + fromDate: Date, + maxEnroll: Number, + enrolledCount: Number, + }], + }, + requisites: descriptor, // not in the API spec but can still appear here instead of in course.requisites +} + +export const classSchema = new Schema(classSchemaObject, schemaOptions); +export const ClassModel = mongoose.model('Class', classSchema); +export type ClassType = InferSchemaType; diff --git a/backend/src/models/common.ts b/backend/src/models/common.ts new file mode 100644 index 000000000..6ad0ec5e8 --- /dev/null +++ b/backend/src/models/common.ts @@ -0,0 +1,7 @@ +export const schemaOptions = { + id: false, + timestamps: { + createdAt: '_createdAt', + updatedAt: '_updatedAt' + } +} \ No newline at end of file diff --git a/backend/src/models/course.ts b/backend/src/models/course.ts new file mode 100644 index 000000000..6f03b22a8 --- /dev/null +++ b/backend/src/models/course.ts @@ -0,0 +1,150 @@ +import mongoose, { InferSchemaType, Schema } from 'mongoose'; +import { schemaOptions } from './common'; +import { descriptor, identifier } from '../utils/sis'; + +const t = (_: any) => true; + +// source: https://developers.api.berkeley.edu/api/100/interactive-docs +const minimalCourse = { + identifiers: [identifier], + subjectArea: descriptor, + catalogNumber: { + prefix: String, + number: String, + suffix: String, + formatted: String, + }, + displayName: String, + title: String, + transcriptTitle: String, +} + +const courseSchemaObject = { + identifiers: [identifier], + subjectArea: descriptor, + catalogNumber: { + prefix: String, + number: String, + suffix: String, + formatted: String, + }, + classSubjectArea: descriptor, + displayName: String, + classDisplayName: String, + formerDisplayName: String, + title: String, + transcriptTitle: String, + description: String, + academicCareer: descriptor, + academicGroup: descriptor, + academicOrganization: descriptor, + departmentNicknames: String, + primaryInstructionMethod: descriptor, + credit: { + type: { type: String }, + value: { + fixed: { + units: Number, + }, + range: { + minUnits: Number, + maxUnits: Number, + }, + discrete: { + units: [Number], + }, + }, + }, + gradingBasis: descriptor, + blindGrading: Boolean, + status: descriptor, + fromDate: Date, + toDate: Date, + createdDate: Date, + updatedDate: Date, + printInCatalog: Boolean, + printInstructors: Boolean, + anyFeesExist: Boolean, + finalExam: descriptor, + instructorAddConsentRequired: Boolean, + instructorDropConsentRequired: Boolean, + allowMultipleEnrollments: Boolean, + spansMultipleTerms: Boolean, + multipleTermNumber: Number, + contactHours: Number, + workloadHours: Number, + enrollmentUnitLoadCalculator: descriptor, + tie: descriptor, + cip: descriptor, + hegis: descriptor, + repeatability: { + repeatable: Boolean, + description: String, + maxCredit: Number, + maxCount: Number, + }, + preparation: { + recommendedText: String, + recommendedCourses: [minimalCourse], + requiredText: String, + requiredCourses: [minimalCourse], + }, + requisites: descriptor, + creditRestriction: { + restrictionText: String, + restrictionCourses: [{ + course: minimalCourse, + maxCreditPercentage: Number, + }], + }, + gradeReplacement: { + gradeReplacementGroup: String, + gradeReplacementText: String, + gradeReplacementCourses: [minimalCourse], + }, + courseObjectives: [String], + studentLearningOutcomes: [String], + proposedInstructors: [String], + formatsOffered: { + description: String, + formats: [{ + termsAllowed: [String], + sessionType: String, + description: String, + aggregateCountactHours: Number, + aggregateMinContactHours: Number, + aggregateMaxContactHours: Number, + minWorkloadHours: Number, + maxWorkloadHours: Number, + anyFeesExist: Boolean, + finalExam: descriptor, + components: [{ + instructionMethod: descriptor, + primary: Boolean, + contactHours: Number, + minContactHours: Number, + maxContactHours: Number, + finalExam: descriptor, + feesExist: Boolean, + }], + }], + typicallyOffered: { + terms: [String], + comments: String, + }, + summerOnly: Boolean, + }, + crossListing: { + count: Number, + courses: [String], + }, + classCrossListing: { + count: Number, + courses: [String], + }, + requirementsFulfilled: [descriptor], +} + +export const courseSchema = new Schema(courseSchemaObject, schemaOptions); +export const CourseModel = mongoose.model('Course', courseSchema) +export type CourseType = InferSchemaType diff --git a/backend/src/db/grade.ts b/backend/src/models/grade.ts similarity index 78% rename from backend/src/db/grade.ts rename to backend/src/models/grade.ts index 34e0fc1d5..a8c981110 100644 --- a/backend/src/db/grade.ts +++ b/backend/src/models/grade.ts @@ -1,6 +1,6 @@ import mongoose, { Schema, InferSchemaType } from "mongoose"; -const GradeSchema = new Schema({ +const gradeSchema = new Schema({ _id: Schema.Types.ObjectId, _created: Date, _updated: Date, @@ -23,5 +23,5 @@ const GradeSchema = new Schema({ }, }) -export const GradeModel = mongoose.model("calanswers_grade", GradeSchema, "calanswers_grade"); -export type GradeType = InferSchemaType; +export const GradeModel = mongoose.model("calanswers_grade", gradeSchema, "calanswers_grade"); +export type GradeType = InferSchemaType; diff --git a/backend/src/db/schedule.ts b/backend/src/models/schedule.ts similarity index 74% rename from backend/src/db/schedule.ts rename to backend/src/models/schedule.ts index 5fa3cf784..74a5a8217 100644 --- a/backend/src/db/schedule.ts +++ b/backend/src/models/schedule.ts @@ -1,6 +1,6 @@ import mongoose, { Schema, InferSchemaType, Document } from "mongoose"; -export const TermSchema = new Schema({ +export const termSchema = new Schema({ year: { type: Number, required: true, @@ -12,7 +12,7 @@ export const TermSchema = new Schema({ }, }, { _id : false }); -export const CustomEventSchema = new Schema({ +export const customEventSchema = new Schema({ start_time: { type: String, required: true, @@ -49,7 +49,7 @@ export const CustomEventSchema = new Schema({ }); -export const ScheduleSchema = new Schema({ +export const scheduleSchema = new Schema({ name: { type: String, required: false @@ -82,18 +82,18 @@ export const ScheduleSchema = new Schema({ required: false }, term: { - type: TermSchema, + type: termSchema, required: true }, custom_events: { - type: [CustomEventSchema], + type: [customEventSchema], required: false } }, { timestamps: true }); -export type TermType = Document & InferSchemaType; -export const TermModel = mongoose.model("outputTerm", TermSchema, "outputTerm"); -export const CustomEventModel = mongoose.model("customEvent", CustomEventSchema, "customEvent"); -export type CustomEventType = Document & InferSchemaType; -export const ScheduleModel = mongoose.model("schedule", ScheduleSchema, "schedule"); -export type ScheduleType = Document & InferSchemaType; +export const TermModel = mongoose.model("outputTerm", termSchema, "outputTerm"); +export type TermType = Document & InferSchemaType; +export const CustomEventModel = mongoose.model("customEvent", customEventSchema, "customEvent"); +export type CustomEventType = Document & InferSchemaType; +export const ScheduleModel = mongoose.model("schedule", scheduleSchema, "schedule"); +export type ScheduleType = Document & InferSchemaType; diff --git a/backend/src/models/section.ts b/backend/src/models/section.ts new file mode 100644 index 000000000..e4170024c --- /dev/null +++ b/backend/src/models/section.ts @@ -0,0 +1,141 @@ +import { schemaOptions } from './common'; +import { descriptor, identifier } from '../utils/sis'; +import { classSchema } from './class'; +import mongoose, { InferSchemaType, Schema } from 'mongoose'; + +const party = { + id: String, + name: String, +} + +// source: https://developers.api.berkeley.edu/api/18/interactive-docs +const sectionSchemaObject = { + id: Number, + class: classSchema, + number: String, + component: descriptor, + displayName: String, + instructionMode: descriptor, + type: { type: descriptor }, + academicOrganization: descriptor, + academicGroup: descriptor, + startDate: Date, + endDate: Date, + status: descriptor, + cancelDate: Date, // The docs say "cancelleDate" but the API returns "cancelDate" + association: { + primary: Boolean, + primaryAssociatedComponent: descriptor, + primaryAssociatedSectionId: Number, + primaryAssociatedSectionIds: [Number], + associatedClass: Number, + }, + enrollmentStatus: { + status: descriptor, + enrolledCount: Number, + reservedCount: Number, + waitlistedCount: Number, + minEnroll: Number, + maxEnroll: Number, + maxWaitlist: Number, + openReserved: Number, + instructorAddConsentRequired: Boolean, + instructorDropConsentRequired: Boolean, + seatReservations: [{ + number: Number, + requirementGroup: descriptor, + fromDate: Date, + maxEnroll: Number, + enrolledCount: Number, + }], + }, + printInScheduleOfClasses: Boolean, + addConsentRequired: descriptor, + dropConsentRequired: descriptor, + graded: Boolean, + feesExist: Boolean, + characteristics: [descriptor], + roomShare: Boolean, + sectionAttributes: [{ + attribute: descriptor, + value: descriptor, + }], + roomCharacteristics: [{ + code: String, + description: String, + formalDescription: String, + active: Boolean, + fromDate: Date, + toDate: Date, + quantity: Number, + }], + meetings: [{ + number: Number, + meetsDays: String, + meetsMonday: Boolean, + meetsTuesday: Boolean, + meetsWednesday: Boolean, + meetsThursday: Boolean, + meetsFriday: Boolean, + meetsSaturday: Boolean, + meetsSunday: Boolean, + startTime: String, + endTime: String, + location: descriptor, + building: descriptor, + assignedInstructors: [{ + assignmentNumber: Number, + instructor: { + identifiers: [identifier], + names: [{ + type: { type: descriptor }, + familyName: String, + givenName: String, + middleName: String, + suffixName: String, + formattedName: String, + perferred: Boolean, + disclose: Boolean, + uiControl: descriptor, + lastChangedBy: party, + fromDate: Date, + toDate: Date, + }], + /* There are additional optional fields in the "person" model, + but they are not typically included in a section response, + so I shall exclude them from here to minimize clutter. */ + }, + role: descriptor, + contactMinutes: Number, + printInScheduleOfClasses: Boolean, + gradeRosterAccess: descriptor, + }], + startDate: Date, + endDate: Date, + meetingTopic: descriptor, + meetingDescription: String, + }], + exams: [{ + number: Number, + type: { type: descriptor }, + location: descriptor, + building: descriptor, + date: Date, + startTime: String, + endTime: String, + }], + combination: { + id: String, + description: String, + type: { type: descriptor }, + enrolledCountCombinedSections: Number, + waitlistedCountCombinedSections: Number, + maxEnrollCombinedSections: Number, + maxWaitlistCombinedSections: Number, + combinedSections: [Number], + }, +} + +const sectionSchema = new Schema(sectionSchemaObject, schemaOptions); +export const SectionModel = mongoose.model('Section', sectionSchema) +export type SectionType = InferSchemaType diff --git a/backend/src/models/semester.ts b/backend/src/models/semester.ts new file mode 100644 index 000000000..a3864309f --- /dev/null +++ b/backend/src/models/semester.ts @@ -0,0 +1,13 @@ +import { schemaOptions } from './common'; +import mongoose, { Schema, InferSchemaType } from 'mongoose'; + +const semesterSchemaObject = { + term: { type: String, enum: ['Fall', 'Spring', 'Summer'], required: true }, + year: { type: Number, required: true }, + active: { type: Boolean, required: true }, + // TODO: add other semester-specific info +} + +const semesterSchema = new Schema(semesterSchemaObject, schemaOptions); +export const SemesterModel = mongoose.model('Semester', semesterSchema); +export type SemesterType = InferSchemaType \ No newline at end of file diff --git a/backend/src/db/user.ts b/backend/src/models/user.ts similarity index 90% rename from backend/src/db/user.ts rename to backend/src/models/user.ts index 8d74d33e8..2ed08eeaa 100644 --- a/backend/src/db/user.ts +++ b/backend/src/models/user.ts @@ -1,6 +1,6 @@ import mongoose, { Schema, InferSchemaType, Document } from "mongoose"; -export const UserSchema = new Schema({ +export const userSchema = new Schema({ google_id: { type: String, trim: true, @@ -79,5 +79,5 @@ export const UserSchema = new Schema({ }, }, { timestamps: true }); -export const UserModel = mongoose.model("user", UserSchema, "user"); -export type UserType = Document & InferSchemaType; +export const UserModel = mongoose.model("user", userSchema, "user"); +export type UserType = Document & InferSchemaType; diff --git a/backend/src/modules/catalog/controller.ts b/backend/src/modules/catalog/controller.ts index 125f654d8..de10f20e9 100644 --- a/backend/src/modules/catalog/controller.ts +++ b/backend/src/modules/catalog/controller.ts @@ -1,10 +1,10 @@ import { CatalogItem, TermInput } from "../../generated-types/graphql"; -import { ClassModel } from "../../db/class"; +import { ClassModel, ClassType } from "../../models/class"; import { getTermStartMonth, termToString } from "../../utils/term"; -import { GradeModel, GradeType } from "../../db/grade"; +import { GradeModel, GradeType } from "../../models/grade"; import { getAverage } from "../grade/controller"; -import { CourseModel, CourseType } from "../../db/course"; -import { SectionModel } from "../../db/section"; +import { CourseModel, CourseType } from "../../models/course"; +import { SectionModel } from "../../models/section"; import { formatClass, formatCourse, formatSection } from "./formatter"; import { getCourseKey, getCsCourseId } from "../../utils/course"; import { isNil } from "lodash"; diff --git a/backend/src/modules/catalog/formatter.ts b/backend/src/modules/catalog/formatter.ts index 09623a741..b1f3f6b09 100644 --- a/backend/src/modules/catalog/formatter.ts +++ b/backend/src/modules/catalog/formatter.ts @@ -1,6 +1,6 @@ -import { ClassType } from "../../db/class"; -import { CourseType } from "../../db/course"; -import { SectionType } from "../../db/section"; +import { ClassType } from "../../models/class"; +import { CourseType } from "../../models/course"; +import { SectionType } from "../../models/section"; import { TermInput } from "../../generated-types/graphql"; import { getCsCourseId } from "../../utils/course"; import { stringToTerm } from "../../utils/term"; @@ -85,8 +85,8 @@ export function formatSection(section: SectionType | null): any { }, ccn: section.id as number, - dateEnd: meeting?.endDate as string, - dateStart: meeting?.startDate as string, + dateEnd: meeting?.endDate, + dateStart: meeting?.startDate, days: meeting != null ? [ meeting.meetsSunday, meeting.meetsMonday, @@ -124,7 +124,7 @@ export function formatCourse(course: CourseType | null, term?: TermInput | null) }, description: course.description as string, - fromDate: course.fromDate as string, + fromDate: course.fromDate, gradingBasis: course.gradingBasis?.description as string, level: course.academicCareer?.description as string, number: course.catalogNumber?.formatted as string, @@ -132,7 +132,7 @@ export function formatCourse(course: CourseType | null, term?: TermInput | null) subject: course.classSubjectArea?.code as string, subjectName: course.classSubjectArea?.description as string, title: course.title as string, - toDate: course.toDate as string, + toDate: course.toDate, ...formatMetadata(course), } diff --git a/backend/src/modules/grade/controller.ts b/backend/src/modules/grade/controller.ts index c75c4d9c3..1284100c7 100644 --- a/backend/src/modules/grade/controller.ts +++ b/backend/src/modules/grade/controller.ts @@ -1,4 +1,4 @@ -import { GradeModel, GradeType } from "../../db/grade"; +import { GradeModel, GradeType } from "../../models/grade"; import { TermInput } from "../../generated-types/graphql"; import { omitBy, isNil } from "lodash"; diff --git a/backend/src/modules/schedule/controller.ts b/backend/src/modules/schedule/controller.ts index 0d76176f2..fd324f2c1 100644 --- a/backend/src/modules/schedule/controller.ts +++ b/backend/src/modules/schedule/controller.ts @@ -1,6 +1,6 @@ import { formatSchedule } from "./formatter"; import { Schedule, ScheduleInput, CustomEventInput } from "../../generated-types/graphql"; -import { ScheduleModel } from "../../db/schedule"; +import { ScheduleModel } from "../../models/schedule"; import { omitBy } from "lodash"; diff --git a/backend/src/modules/schedule/formatter.ts b/backend/src/modules/schedule/formatter.ts index bf447f39d..98b4a0657 100644 --- a/backend/src/modules/schedule/formatter.ts +++ b/backend/src/modules/schedule/formatter.ts @@ -1,5 +1,5 @@ import { ScheduleModule } from "./generated-types/module-types"; -import { CustomEventType, ScheduleType } from "../../db/schedule"; +import { CustomEventType, ScheduleType } from "../../models/schedule"; export function formatSchedule(schedule: ScheduleType): ScheduleModule.Schedule { return { diff --git a/backend/src/modules/user/controller.ts b/backend/src/modules/user/controller.ts index 2cfa6d54f..ab5b397d2 100644 --- a/backend/src/modules/user/controller.ts +++ b/backend/src/modules/user/controller.ts @@ -1,7 +1,7 @@ import { ObjectId } from "mongodb"; import { UserInput } from "../../generated-types/graphql"; import { formatUser } from "./formatter"; -import { UserModel, UserType } from "../../db/user"; +import { UserModel, UserType } from "../../models/user"; import { omitBy } from "lodash"; function resolveAndFormat(user: UserType | null) { @@ -24,11 +24,11 @@ export async function updateUserInfo(id: ObjectId, newUserInfo: UserInput) { const user = await UserModel.findByIdAndUpdate(id, newUserInfo, { new: true, lean: true }); - return resolveAndFormat(user); + return resolveAndFormat(user as UserType); } export async function deleteUser(id: ObjectId) { const user = await UserModel.findByIdAndDelete(id, { lean: true }); - return resolveAndFormat(user); + return resolveAndFormat(user as UserType); } diff --git a/backend/src/modules/user/formatter.ts b/backend/src/modules/user/formatter.ts index c2089d938..d784b5ff9 100644 --- a/backend/src/modules/user/formatter.ts +++ b/backend/src/modules/user/formatter.ts @@ -1,5 +1,5 @@ import { UserModule } from "./generated-types/module-types"; -import { UserType } from "../../db/user"; +import { UserType } from "../../models/user"; export function formatUser(user: UserType): UserModule.User { return { diff --git a/backend/src/utils/course.ts b/backend/src/utils/course.ts index e76ee59b3..dd6eb9652 100644 --- a/backend/src/utils/course.ts +++ b/backend/src/utils/course.ts @@ -1,4 +1,4 @@ -import { CourseType } from "../db/course"; +import { CourseType } from "../models/course"; /** * Used to map between courses and grades diff --git a/backend/src/utils/sis.ts b/backend/src/utils/sis.ts new file mode 100644 index 000000000..cbc5f807e --- /dev/null +++ b/backend/src/utils/sis.ts @@ -0,0 +1,30 @@ +export const descriptor = { + code: String, + description: String, + formalDescription: String, + active: Boolean, + fromDate: Date, + toDate: Date, +} + +export const identifier = { + type: { type: String }, + id: String, + primary: Boolean, + disclose: Boolean, + fromDate: Date, + toDate: Date +} + +export interface SISResponse { + apiResponse: { + correlationId?: string; + httpStatus: { + code?: string; + description?: string; + }; + response: { + [key: string]: T[]; + }; + }; +} \ No newline at end of file