mirror of
https://github.com/Xevion/banner.git
synced 2025-12-14 08:11:02 -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.
|
// 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")
|
// log.Debug().Str("url", r.Request.RawRequest.URL.Path).Msg("Session middleware")
|
||||||
|
|
||||||
// Reset session timer on successful requests to Banner API endpoints
|
// Reset session timer on successful requests to Banner API endpoints
|
||||||
@@ -56,6 +56,15 @@ func GenerateSession() string {
|
|||||||
return internal.RandomString(5) + internal.Nonce()
|
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 terms []BannerTerm
|
||||||
var lastTermUpdate time.Time
|
var lastTermUpdate time.Time
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// MaxPageSize is the maximum number of courses one can scrape per page.
|
||||||
MaxPageSize = 500
|
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.
|
// 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.
|
// It checks Redis for the "scraped" status of each major for the current term.
|
||||||
func (a *API) GetExpiredSubjects() ([]string, error) {
|
func (a *API) GetExpiredSubjects() ([]string, error) {
|
||||||
term := Default(time.Now()).ToString()
|
term := a.DefaultTerm(time.Now()).ToString()
|
||||||
subjects := make([]string, 0)
|
subjects := make([]string, 0)
|
||||||
|
|
||||||
// Create a timeout context for Redis operations
|
// Create a timeout context for Redis operations
|
||||||
@@ -114,7 +115,7 @@ func (a *API) ScrapeMajor(subject string) error {
|
|||||||
for {
|
for {
|
||||||
// Build & execute the query
|
// Build & execute the query
|
||||||
query := NewQuery().Offset(offset).MaxResults(MaxPageSize * 2).Subject(subject)
|
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)
|
result, err := a.Search(term, query, "subjectDescription", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("search failed: %w (%s)", err, query.String())
|
return fmt.Errorf("search failed: %w (%s)", err, query.String())
|
||||||
@@ -157,7 +158,7 @@ func (a *API) ScrapeMajor(subject string) error {
|
|||||||
break
|
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
|
// 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
|
var scrapeExpiry time.Duration
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (b *Bot) SetClosing() {
|
|||||||
// GetSession ensures a valid session is available and selects the default term.
|
// GetSession ensures a valid session is available and selects the default term.
|
||||||
func (b *Bot) GetSession() (string, error) {
|
func (b *Bot) GetSession() (string, error) {
|
||||||
sessionID := b.API.EnsureSession()
|
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")
|
log.Info().Str("term", term).Str("sessionID", sessionID).Msg("Setting selected term")
|
||||||
err := b.API.SelectTerm(term, sessionID)
|
err := b.API.SelectTerm(term, sessionID)
|
||||||
|
|||||||
@@ -26,12 +26,10 @@ type Config struct {
|
|||||||
Environment string
|
Environment string
|
||||||
// CentralTimeLocation is the time.Location for US Central Time.
|
// CentralTimeLocation is the time.Location for US Central Time.
|
||||||
CentralTimeLocation *time.Location
|
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.
|
// New creates a new Config instance with a cancellable context.
|
||||||
func New() (*Config, error) {
|
func New() (*Config, error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -42,10 +40,13 @@ func New() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seasonRanges := GetYearDayRange(loc, uint16(time.Now().Year()))
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
CancelFunc: cancel,
|
CancelFunc: cancel,
|
||||||
CentralTimeLocation: loc,
|
CentralTimeLocation: loc,
|
||||||
|
SeasonRanges: &seasonRanges,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"banner/internal/config"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,9 +12,12 @@ import (
|
|||||||
// Summer 2024, "fall" => Fall 2024
|
// Summer 2024, "fall" => Fall 2024
|
||||||
|
|
||||||
const (
|
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
|
Summer
|
||||||
Fall
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Term represents a school term, consisting of a year and a season.
|
// Term represents a school term, consisting of a year and a season.
|
||||||
@@ -24,13 +26,11 @@ type Term struct {
|
|||||||
Season uint8
|
Season uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// SeasonRanges represents the start and end day of each term within a year.
|
||||||
SpringRange, SummerRange, FallRange YearDayRange
|
type SeasonRanges struct {
|
||||||
)
|
Spring YearDayRange
|
||||||
|
Summer YearDayRange
|
||||||
func init() {
|
Fall YearDayRange
|
||||||
loc, _ := time.LoadLocation(config.CentralTimezoneName)
|
|
||||||
SpringRange, SummerRange, FallRange = GetYearDayRange(loc, uint16(time.Now().Year()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// YearDayRange represents the start and end day of a term within a year.
|
// 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.
|
// 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.
|
// 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()
|
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()
|
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()
|
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()
|
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()
|
fallEnd := time.Date(int(year), time.December, 10, 0, 0, 0, 0, loc).YearDay()
|
||||||
|
|
||||||
return YearDayRange{
|
return SeasonRanges{
|
||||||
|
Spring: YearDayRange{
|
||||||
Start: uint16(springStart),
|
Start: uint16(springStart),
|
||||||
End: uint16(springEnd),
|
End: uint16(springEnd),
|
||||||
}, YearDayRange{
|
},
|
||||||
|
Summer: YearDayRange{
|
||||||
Start: uint16(summerStart),
|
Start: uint16(summerStart),
|
||||||
End: uint16(summerEnd),
|
End: uint16(summerEnd),
|
||||||
}, YearDayRange{
|
},
|
||||||
|
Fall: YearDayRange{
|
||||||
Start: uint16(fallStart),
|
Start: uint16(fallStart),
|
||||||
End: uint16(fallEnd),
|
End: uint16(fallEnd),
|
||||||
}
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentTerm returns the current and next terms based on the provided time.
|
// 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 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.
|
// 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())
|
literalYear := uint16(now.Year())
|
||||||
dayOfYear := uint16(now.YearDay())
|
dayOfYear := uint16(now.YearDay())
|
||||||
|
|
||||||
// If we're past the end of the summer term, we're 'in' the next school year.
|
// If we're past the end of the summer term, we're 'in' the next school year.
|
||||||
var termYear uint16
|
var termYear uint16
|
||||||
if dayOfYear > SummerRange.End {
|
if dayOfYear > ranges.Summer.End {
|
||||||
termYear = literalYear + 1
|
termYear = literalYear + 1
|
||||||
} else {
|
} else {
|
||||||
termYear = literalYear
|
termYear = literalYear
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dayOfYear < SpringRange.Start) || (dayOfYear >= FallRange.End) {
|
if (dayOfYear < ranges.Spring.Start) || (dayOfYear >= ranges.Fall.End) {
|
||||||
// Fall over, Spring not yet begun
|
// Fall over, Spring not yet begun
|
||||||
return nil, &Term{Year: termYear, Season: Spring}
|
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
|
// Spring
|
||||||
return &Term{Year: termYear, Season: Spring}, &Term{Year: termYear, Season: Summer}
|
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
|
// Spring over, Summer not yet begun
|
||||||
return nil, &Term{Year: termYear, Season: Summer}
|
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
|
// Summer
|
||||||
return &Term{Year: termYear, Season: Summer}, &Term{Year: termYear, Season: Fall}
|
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
|
// Summer over, Fall not yet begun
|
||||||
return nil, &Term{Year: termYear, Season: Fall}
|
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
|
// Fall
|
||||||
return &Term{Year: termYear, Season: Fall}, nil
|
return &Term{Year: termYear, Season: Fall}, nil
|
||||||
}
|
}
|
||||||
@@ -134,12 +138,3 @@ func (term Term) ToString() string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%d%s", term.Year, season)
|
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