mirror of
https://github.com/Xevion/banner.git
synced 2025-12-10 08:06:35 -06:00
refactor: complete refactor into cmd/ & internal/ submodules
This commit is contained in:
322
internal/models/types.go
Normal file
322
internal/models/types.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"banner/internal/config"
|
||||
"banner/internal/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const JsonContentType = "application/json"
|
||||
|
||||
type FacultyItem struct {
|
||||
BannerId string `json:"bannerId"`
|
||||
Category *string `json:"category"`
|
||||
Class string `json:"class"`
|
||||
CourseReferenceNumber string `json:"courseReferenceNumber"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Email string `json:"emailAddress"`
|
||||
Primary bool `json:"primaryIndicator"`
|
||||
Term string `json:"term"`
|
||||
}
|
||||
|
||||
type MeetingTimeResponse struct {
|
||||
Category *string `json:"category"`
|
||||
Class string `json:"class"`
|
||||
CourseReferenceNumber string `json:"courseReferenceNumber"`
|
||||
Faculty []FacultyItem
|
||||
MeetingTime struct {
|
||||
Category string `json:"category"`
|
||||
// Some sort of metadata used internally by Banner (net.hedtech.banner.student.schedule.SectionSessionDecorator)
|
||||
Class string `json:"class"`
|
||||
// The start date of the meeting time in MM/DD/YYYY format (e.g. 01/16/2024)
|
||||
StartDate string `json:"startDate"`
|
||||
// The end date of the meeting time in MM/DD/YYYY format (e.g. 05/10/2024)
|
||||
EndDate string `json:"endDate"`
|
||||
// The start time of the meeting time in 24-hour format, hours & minutes, digits only (e.g. 1630)
|
||||
BeginTime string `json:"beginTime"`
|
||||
// The end time of the meeting time in 24-hour format, hours & minutes, digits only (e.g. 1745)
|
||||
EndTime string `json:"endTime"`
|
||||
// The room number within the building this course takes place at (e.g. 3.01.08, 200A)
|
||||
Room string `json:"room"`
|
||||
// The internal identifier for the term this course takes place in (e.g. 202420)
|
||||
Term string `json:"term"`
|
||||
// The internal identifier for the building this course takes place at (e.g. SP1)
|
||||
Building string `json:"building"`
|
||||
// The long name of the building this course takes place at (e.g. San Pedro I - Data Science)
|
||||
BuildingDescription string `json:"buildingDescription"`
|
||||
// The internal identifier for the campus this course takes place at (e.g. 1DT)
|
||||
Campus string `json:"campus"`
|
||||
// The long name of the campus this course takes place at (e.g. Main Campus, Downtown Campus)
|
||||
CampusDescription string `json:"campusDescription"`
|
||||
CourseReferenceNumber string `json:"courseReferenceNumber"`
|
||||
// The number of credit hours this class is worth (assumably)
|
||||
CreditHourSession float64 `json:"creditHourSession"`
|
||||
// The number of hours per week this class meets (e.g. 2.5)
|
||||
HoursWeek float64 `json:"hoursWeek"`
|
||||
// Unknown meaning - e.g. AFF, AIN, AHB, FFF, AFF, EFF, DFF, IFF, EHB, JFF, KFF, BFF, BIN
|
||||
MeetingScheduleType string `json:"meetingScheduleType"`
|
||||
// The short identifier for the meeting type (e.g. FF, HB, OS, OA)
|
||||
MeetingType string `json:"meetingType"`
|
||||
// The long name of the meeting type (e.g. Traditional in-person)
|
||||
MeetingTypeDescription string `json:"meetingTypeDescription"`
|
||||
// A boolean indicating if the class will meet on each Monday of the term
|
||||
Monday bool `json:"monday"`
|
||||
// A boolean indicating if the class will meet on each Tuesday of the term
|
||||
Tuesday bool `json:"tuesday"`
|
||||
// A boolean indicating if the class will meet on each Wednesday of the term
|
||||
Wednesday bool `json:"wednesday"`
|
||||
// A boolean indicating if the class will meet on each Thursday of the term
|
||||
Thursday bool `json:"thursday"`
|
||||
// A boolean indicating if the class will meet on each Friday of the term
|
||||
Friday bool `json:"friday"`
|
||||
// A boolean indicating if the class will meet on each Saturday of the term
|
||||
Saturday bool `json:"saturday"`
|
||||
// A boolean indicating if the class will meet on each Sunday of the term
|
||||
Sunday bool `json:"sunday"`
|
||||
} `json:"meetingTime"`
|
||||
Term string `json:"term"`
|
||||
}
|
||||
|
||||
func (m *MeetingTimeResponse) String() string {
|
||||
switch m.MeetingTime.MeetingType {
|
||||
case "HB":
|
||||
return fmt.Sprintf("%s\nHybrid %s", m.TimeString(), m.PlaceString())
|
||||
case "H2":
|
||||
return fmt.Sprintf("%s\nHybrid %s", m.TimeString(), m.PlaceString())
|
||||
case "H1":
|
||||
return fmt.Sprintf("%s\nHybrid %s", m.TimeString(), m.PlaceString())
|
||||
case "OS":
|
||||
return fmt.Sprintf("%s\nOnline Only", m.TimeString())
|
||||
case "OA":
|
||||
return "No Time\nOnline Asynchronous"
|
||||
case "OH":
|
||||
return fmt.Sprintf("%s\nOnline Partial", m.TimeString())
|
||||
case "ID":
|
||||
return "To Be Arranged"
|
||||
case "FF":
|
||||
return fmt.Sprintf("%s\n%s", m.TimeString(), m.PlaceString())
|
||||
}
|
||||
|
||||
// TODO: Add error log
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
func (m *MeetingTimeResponse) TimeString() string {
|
||||
startTime := m.StartTime()
|
||||
endTime := m.EndTime()
|
||||
|
||||
if startTime == nil || endTime == nil {
|
||||
return "???"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s-%s", utils.WeekdaysToString(m.Days()), m.StartTime().String(), m.EndTime().String())
|
||||
}
|
||||
|
||||
// PlaceString returns a formatted string best representing the place of the meeting time
|
||||
func (m *MeetingTimeResponse) PlaceString() string {
|
||||
mt := m.MeetingTime
|
||||
|
||||
// TODO: ADd format case for partial online classes
|
||||
if mt.Room == "" {
|
||||
return "Online"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s | %s | %s %s", mt.CampusDescription, mt.BuildingDescription, mt.Building, mt.Room)
|
||||
}
|
||||
|
||||
func (m *MeetingTimeResponse) Days() map[time.Weekday]bool {
|
||||
days := map[time.Weekday]bool{}
|
||||
|
||||
days[time.Monday] = m.MeetingTime.Monday
|
||||
days[time.Tuesday] = m.MeetingTime.Tuesday
|
||||
days[time.Wednesday] = m.MeetingTime.Wednesday
|
||||
days[time.Thursday] = m.MeetingTime.Thursday
|
||||
days[time.Friday] = m.MeetingTime.Friday
|
||||
days[time.Saturday] = m.MeetingTime.Saturday
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
// Returns the BYDAY value for the iCalendar RRule format
|
||||
func (m *MeetingTimeResponse) ByDay() string {
|
||||
days := []string{}
|
||||
|
||||
if m.MeetingTime.Sunday {
|
||||
days = append(days, "SU")
|
||||
}
|
||||
if m.MeetingTime.Monday {
|
||||
days = append(days, "MO")
|
||||
}
|
||||
if m.MeetingTime.Tuesday {
|
||||
days = append(days, "TU")
|
||||
}
|
||||
if m.MeetingTime.Wednesday {
|
||||
days = append(days, "WE")
|
||||
}
|
||||
if m.MeetingTime.Thursday {
|
||||
days = append(days, "TH")
|
||||
}
|
||||
if m.MeetingTime.Friday {
|
||||
days = append(days, "FR")
|
||||
}
|
||||
if m.MeetingTime.Saturday {
|
||||
days = append(days, "SA")
|
||||
}
|
||||
|
||||
return strings.Join(days, ",")
|
||||
}
|
||||
|
||||
const layout = "01/02/2006"
|
||||
|
||||
// StartDay returns the start date of the meeting time as a time.Time object
|
||||
// This is not cached and is parsed on each invocation. It may also panic without handling.
|
||||
func (m *MeetingTimeResponse) StartDay() time.Time {
|
||||
t, err := time.Parse(layout, m.MeetingTime.StartDate)
|
||||
if err != nil {
|
||||
log.Panic().Stack().Err(err).Str("raw", m.MeetingTime.StartDate).Msg("Cannot parse start date")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// EndDay returns the end date of the meeting time as a time.Time object.
|
||||
// This is not cached and is parsed on each invocation. It may also panic without handling.
|
||||
func (m *MeetingTimeResponse) EndDay() time.Time {
|
||||
t, err := time.Parse(layout, m.MeetingTime.EndDate)
|
||||
if err != nil {
|
||||
log.Panic().Stack().Err(err).Str("raw", m.MeetingTime.EndDate).Msg("Cannot parse end date")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// StartTime returns the start time of the meeting time as a NaiveTime object
|
||||
// This is not cached and is parsed on each invocation. It may also panic without handling.
|
||||
func (m *MeetingTimeResponse) StartTime() *utils.NaiveTime {
|
||||
raw := m.MeetingTime.BeginTime
|
||||
if raw == "" {
|
||||
log.Panic().Stack().Msg("Start time is empty")
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(raw, 10, 32)
|
||||
if err != nil {
|
||||
log.Panic().Stack().Err(err).Str("raw", raw).Msg("Cannot parse start time integer")
|
||||
}
|
||||
|
||||
return utils.ParseNaiveTime(value)
|
||||
}
|
||||
|
||||
// EndTime returns the end time of the meeting time as a NaiveTime object
|
||||
// This is not cached and is parsed on each invocation. It may also panic without handling.
|
||||
func (m *MeetingTimeResponse) EndTime() *utils.NaiveTime {
|
||||
raw := m.MeetingTime.EndTime
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(raw, 10, 32)
|
||||
if err != nil {
|
||||
log.Panic().Stack().Err(err).Str("raw", raw).Msg("Cannot parse end time integer")
|
||||
}
|
||||
|
||||
return utils.ParseNaiveTime(value)
|
||||
}
|
||||
|
||||
// Converts the meeting time to a string that satisfies the iCalendar RRule format
|
||||
func (m *MeetingTimeResponse) RRule() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString("FREQ=WEEKLY;")
|
||||
sb.WriteString(fmt.Sprintf("UNTIL=%s;", m.EndDay().UTC().Format(config.ICalTimestampFormatUtc)))
|
||||
sb.WriteString(fmt.Sprintf("BYDAY=%s;", m.ByDay()))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Success bool `json:"success"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
PageOffset int `json:"pageOffset"`
|
||||
PageMaxSize int `json:"pageMaxSize"`
|
||||
PathMode string `json:"pathMode"`
|
||||
SearchResultsConfig []struct {
|
||||
Config string `json:"config"`
|
||||
Display string `json:"display"`
|
||||
} `json:"searchResultsConfig"`
|
||||
Data []Course `json:"data"`
|
||||
}
|
||||
|
||||
type Course struct {
|
||||
// A internal identifier not used outside of the Banner system
|
||||
Id int `json:"id"`
|
||||
// The internal identifier for the term this class is in (e.g. 202420)
|
||||
Term string `json:"term"`
|
||||
// The human-readable name of the term this class is in (e.g. Fall 2021)
|
||||
TermDesc string `json:"termDesc"`
|
||||
// The specific identifier that describes this individual course. CRNs are unique to a term. (TODO: Verify this is true)
|
||||
CourseReferenceNumber string `json:"courseReferenceNumber"`
|
||||
// A rarely used identifier that species which part of the given term this course is in. By default, this is "1" to encompass the entire term. (e.g. B6, B5)
|
||||
PartOfTerm string `json:"partOfTerm"`
|
||||
// The 4-digit course code that defines this course (e.g. 3743, 0120, 4855, 7339), but not the specific instance (see CourseReferenceNumber)
|
||||
CourseNumber string `json:"courseNumber"`
|
||||
// The short acronym of the course subject (e.g. CS, AEPI)
|
||||
Subject string `json:"subject"`
|
||||
// The full name of the course subject (e.g. Computer Science, Academic English Program-Intl.)
|
||||
SubjectDescription string `json:"subjectDescription"`
|
||||
// The specific section of the course (e.g. 001, 002)
|
||||
SequenceNumber string `json:"sequenceNumber"`
|
||||
// The long name of the campus this course takes place at (e.g. Main Campus, Downtown Campus)
|
||||
CampusDescription string `json:"campusDescription"`
|
||||
// e.g. Lecture, Seminar, Dissertation, Internship, Independent Study, Thesis, Self-paced, Laboratory
|
||||
ScheduleTypeDescription string `json:"scheduleTypeDescription"`
|
||||
// The long name of the course (generally)
|
||||
CourseTitle string `json:"courseTitle"`
|
||||
CreditHours int `json:"creditHours"`
|
||||
// The maximum number of students that can enroll in this course
|
||||
MaximumEnrollment int `json:"maximumEnrollment"`
|
||||
// The number of students currently enrolled in this course
|
||||
Enrollment int `json:"enrollment"`
|
||||
// The number of seats available in this course (MaximumEnrollment - Enrollment)
|
||||
SeatsAvailable int `json:"seatsAvailable"`
|
||||
// The number of students that could waitlist for this course
|
||||
WaitCapacity int `json:"waitCapacity"`
|
||||
// The number of students currently on the waitlist for this course
|
||||
WaitCount int `json:"waitCount"`
|
||||
CrossList *string `json:"crossList"`
|
||||
CrossListCapacity *int `json:"crossListCapacity"`
|
||||
CrossListCount *int `json:"crossListCount"`
|
||||
CrossListAvailable *int `json:"crossListAvailable"`
|
||||
CreditHourHigh *int `json:"creditHourHigh"`
|
||||
CreditHourLow *int `json:"creditHourLow"`
|
||||
CreditHourIndicator *string `json:"creditHourIndicator"`
|
||||
OpenSection bool `json:"openSection"`
|
||||
LinkIdentifier *string `json:"linkIdentifier"`
|
||||
IsSectionLinked bool `json:"isSectionLinked"`
|
||||
// A combination of the subject and course number (e.g. subject=CS, courseNumber=3443 => "CS3443")
|
||||
SubjectCourse string `json:"subjectCourse"`
|
||||
ReservedSeatSummary *string `json:"reservedSeatSummary"`
|
||||
InstructionalMethod string `json:"instructionalMethod"`
|
||||
InstructionalMethodDescription string `json:"instructionalMethodDescription"`
|
||||
SectionAttributes []struct {
|
||||
// A internal API class identifier used by Banner
|
||||
Class string `json:"class"`
|
||||
CourseReferenceNumber string `json:"courseReferenceNumber"`
|
||||
// UPPR, ZIEP, AIS, LEWR, ZZSL, 090, GRAD, ZZTL, 020, BU, CLEP
|
||||
Code string `json:"code"`
|
||||
// Seems to be the fully qualified meaning of the Code (Upper, Intensive English Program...)
|
||||
Description string `json:"description"`
|
||||
TermCode string `json:"termCode"`
|
||||
// Unknown; always false
|
||||
IsZtcAttribute bool `json:"isZTCAttribute"`
|
||||
} `json:"sectionAttributes"`
|
||||
Faculty []FacultyItem `json:"faculty"`
|
||||
MeetingsFaculty []MeetingTimeResponse `json:"meetingsFaculty"`
|
||||
}
|
||||
|
||||
func (course Course) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(course)
|
||||
}
|
||||
Reference in New Issue
Block a user