mirror of
https://github.com/Xevion/banner.git
synced 2025-12-10 16:06:34 -06:00
feat: move default term acquisition & season range from global into config state, LoadLocation once, remove bare constants
This commit is contained in:
@@ -37,7 +37,7 @@ var (
|
||||
)
|
||||
|
||||
// SessionMiddleware creates a Resty middleware that resets the session timer on each successful Banner API call.
|
||||
func SessionMiddleware(c *resty.Client, r *resty.Response) error {
|
||||
func SessionMiddleware(_ *resty.Client, r *resty.Response) error {
|
||||
// log.Debug().Str("url", r.Request.RawRequest.URL.Path).Msg("Session middleware")
|
||||
|
||||
// Reset session timer on successful requests to Banner API endpoints
|
||||
@@ -56,6 +56,15 @@ func GenerateSession() string {
|
||||
return internal.RandomString(5) + internal.Nonce()
|
||||
}
|
||||
|
||||
// DefaultTerm returns the default term, which is the current term if it exists, otherwise the next term.
|
||||
func (a *API) DefaultTerm(t time.Time) config.Term {
|
||||
currentTerm, nextTerm := config.GetCurrentTerm(*a.config.SeasonRanges, t)
|
||||
if currentTerm == nil {
|
||||
return *nextTerm
|
||||
}
|
||||
return *currentTerm
|
||||
}
|
||||
|
||||
var terms []BannerTerm
|
||||
var lastTermUpdate time.Time
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxPageSize is the maximum number of courses one can scrape per page.
|
||||
MaxPageSize = 500
|
||||
)
|
||||
|
||||
@@ -75,7 +76,7 @@ func (a *API) Scrape() error {
|
||||
// GetExpiredSubjects returns a list of subjects that have expired and should be scraped again.
|
||||
// It checks Redis for the "scraped" status of each major for the current term.
|
||||
func (a *API) GetExpiredSubjects() ([]string, error) {
|
||||
term := Default(time.Now()).ToString()
|
||||
term := a.DefaultTerm(time.Now()).ToString()
|
||||
subjects := make([]string, 0)
|
||||
|
||||
// Create a timeout context for Redis operations
|
||||
@@ -114,7 +115,7 @@ func (a *API) ScrapeMajor(subject string) error {
|
||||
for {
|
||||
// Build & execute the query
|
||||
query := NewQuery().Offset(offset).MaxResults(MaxPageSize * 2).Subject(subject)
|
||||
term := Default(time.Now()).ToString()
|
||||
term := a.DefaultTerm(time.Now()).ToString()
|
||||
result, err := a.Search(term, query, "subjectDescription", false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("search failed: %w (%s)", err, query.String())
|
||||
@@ -157,7 +158,7 @@ func (a *API) ScrapeMajor(subject string) error {
|
||||
break
|
||||
}
|
||||
|
||||
term := Default(time.Now()).ToString()
|
||||
term := a.DefaultTerm(time.Now()).ToString()
|
||||
|
||||
// Calculate the expiry time for the scrape (1 hour for every 200 classes, random +-15%) with a minimum of 1 hour
|
||||
var scrapeExpiry time.Duration
|
||||
|
||||
@@ -32,7 +32,7 @@ func (b *Bot) SetClosing() {
|
||||
// GetSession ensures a valid session is available and selects the default term.
|
||||
func (b *Bot) GetSession() (string, error) {
|
||||
sessionID := b.API.EnsureSession()
|
||||
term := api.Default(time.Now()).ToString()
|
||||
term := b.API.DefaultTerm(time.Now()).ToString()
|
||||
|
||||
log.Info().Str("term", term).Str("sessionID", sessionID).Msg("Setting selected term")
|
||||
err := b.API.SelectTerm(term, sessionID)
|
||||
|
||||
@@ -26,12 +26,10 @@ type Config struct {
|
||||
Environment string
|
||||
// CentralTimeLocation is the time.Location for US Central Time.
|
||||
CentralTimeLocation *time.Location
|
||||
// SeasonRanges is the time.Location for US Central Time.
|
||||
SeasonRanges *SeasonRanges
|
||||
}
|
||||
|
||||
const (
|
||||
CentralTimezoneName = "America/Chicago"
|
||||
)
|
||||
|
||||
// New creates a new Config instance with a cancellable context.
|
||||
func New() (*Config, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -42,10 +40,13 @@ func New() (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seasonRanges := GetYearDayRange(loc, uint16(time.Now().Year()))
|
||||
|
||||
return &Config{
|
||||
Ctx: ctx,
|
||||
CancelFunc: cancel,
|
||||
CentralTimeLocation: loc,
|
||||
SeasonRanges: &seasonRanges,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
package config
|
||||
|
||||
import (
|
||||
"banner/internal/config"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -13,9 +12,12 @@ import (
|
||||
// Summer 2024, "fall" => Fall 2024
|
||||
|
||||
const (
|
||||
Spring = iota
|
||||
// Fall is the first term of the school year.
|
||||
Fall = iota
|
||||
// Spring is the second term of the school year.
|
||||
Spring
|
||||
// Summer is the third term of the school year.
|
||||
Summer
|
||||
Fall
|
||||
)
|
||||
|
||||
// Term represents a school term, consisting of a year and a season.
|
||||
@@ -24,13 +26,11 @@ type Term struct {
|
||||
Season uint8
|
||||
}
|
||||
|
||||
var (
|
||||
SpringRange, SummerRange, FallRange YearDayRange
|
||||
)
|
||||
|
||||
func init() {
|
||||
loc, _ := time.LoadLocation(config.CentralTimezoneName)
|
||||
SpringRange, SummerRange, FallRange = GetYearDayRange(loc, uint16(time.Now().Year()))
|
||||
// SeasonRanges represents the start and end day of each term within a year.
|
||||
type SeasonRanges struct {
|
||||
Spring YearDayRange
|
||||
Summer YearDayRange
|
||||
Fall YearDayRange
|
||||
}
|
||||
|
||||
// YearDayRange represents the start and end day of a term within a year.
|
||||
@@ -41,7 +41,7 @@ type YearDayRange struct {
|
||||
|
||||
// GetYearDayRange returns the start and end day of each term for the given year.
|
||||
// The ranges are inclusive of the start day and exclusive of the end day.
|
||||
func GetYearDayRange(loc *time.Location, year uint16) (YearDayRange, YearDayRange, YearDayRange) {
|
||||
func GetYearDayRange(loc *time.Location, year uint16) SeasonRanges {
|
||||
springStart := time.Date(int(year), time.January, 14, 0, 0, 0, 0, loc).YearDay()
|
||||
springEnd := time.Date(int(year), time.May, 1, 0, 0, 0, 0, loc).YearDay()
|
||||
summerStart := time.Date(int(year), time.May, 25, 0, 0, 0, 0, loc).YearDay()
|
||||
@@ -49,49 +49,53 @@ func GetYearDayRange(loc *time.Location, year uint16) (YearDayRange, YearDayRang
|
||||
fallStart := time.Date(int(year), time.August, 18, 0, 0, 0, 0, loc).YearDay()
|
||||
fallEnd := time.Date(int(year), time.December, 10, 0, 0, 0, 0, loc).YearDay()
|
||||
|
||||
return YearDayRange{
|
||||
return SeasonRanges{
|
||||
Spring: YearDayRange{
|
||||
Start: uint16(springStart),
|
||||
End: uint16(springEnd),
|
||||
}, YearDayRange{
|
||||
},
|
||||
Summer: YearDayRange{
|
||||
Start: uint16(summerStart),
|
||||
End: uint16(summerEnd),
|
||||
}, YearDayRange{
|
||||
},
|
||||
Fall: YearDayRange{
|
||||
Start: uint16(fallStart),
|
||||
End: uint16(fallEnd),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCurrentTerm returns the current and next terms based on the provided time.
|
||||
// The current term can be nil if the time falls between terms.
|
||||
// The 'year' in the term corresponds to the academic year, which may differ from the calendar year.
|
||||
func GetCurrentTerm(now time.Time) (*Term, *Term) {
|
||||
func GetCurrentTerm(ranges SeasonRanges, now time.Time) (*Term, *Term) {
|
||||
literalYear := uint16(now.Year())
|
||||
dayOfYear := uint16(now.YearDay())
|
||||
|
||||
// If we're past the end of the summer term, we're 'in' the next school year.
|
||||
var termYear uint16
|
||||
if dayOfYear > SummerRange.End {
|
||||
if dayOfYear > ranges.Summer.End {
|
||||
termYear = literalYear + 1
|
||||
} else {
|
||||
termYear = literalYear
|
||||
}
|
||||
|
||||
if (dayOfYear < SpringRange.Start) || (dayOfYear >= FallRange.End) {
|
||||
if (dayOfYear < ranges.Spring.Start) || (dayOfYear >= ranges.Fall.End) {
|
||||
// Fall over, Spring not yet begun
|
||||
return nil, &Term{Year: termYear, Season: Spring}
|
||||
} else if (dayOfYear >= SpringRange.Start) && (dayOfYear < SpringRange.End) {
|
||||
} else if (dayOfYear >= ranges.Spring.Start) && (dayOfYear < ranges.Spring.End) {
|
||||
// Spring
|
||||
return &Term{Year: termYear, Season: Spring}, &Term{Year: termYear, Season: Summer}
|
||||
} else if dayOfYear < SummerRange.Start {
|
||||
} else if dayOfYear < ranges.Summer.Start {
|
||||
// Spring over, Summer not yet begun
|
||||
return nil, &Term{Year: termYear, Season: Summer}
|
||||
} else if (dayOfYear >= SummerRange.Start) && (dayOfYear < SummerRange.End) {
|
||||
} else if (dayOfYear >= ranges.Summer.Start) && (dayOfYear < ranges.Summer.End) {
|
||||
// Summer
|
||||
return &Term{Year: termYear, Season: Summer}, &Term{Year: termYear, Season: Fall}
|
||||
} else if dayOfYear < FallRange.Start {
|
||||
} else if dayOfYear < ranges.Fall.Start {
|
||||
// Summer over, Fall not yet begun
|
||||
return nil, &Term{Year: termYear, Season: Fall}
|
||||
} else if (dayOfYear >= FallRange.Start) && (dayOfYear < FallRange.End) {
|
||||
} else if (dayOfYear >= ranges.Fall.Start) && (dayOfYear < ranges.Fall.End) {
|
||||
// Fall
|
||||
return &Term{Year: termYear, Season: Fall}, nil
|
||||
}
|
||||
@@ -134,12 +138,3 @@ func (term Term) ToString() string {
|
||||
|
||||
return fmt.Sprintf("%d%s", term.Year, season)
|
||||
}
|
||||
|
||||
// Default returns the default term, which is the current term if it exists, otherwise the next term.
|
||||
func Default(t time.Time) Term {
|
||||
currentTerm, nextTerm := GetCurrentTerm(t)
|
||||
if currentTerm == nil {
|
||||
return *nextTerm
|
||||
}
|
||||
return *currentTerm
|
||||
}
|
||||
Reference in New Issue
Block a user