From 0696f11f12a01b9b2b298a87e2e7641fd65b8366 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sun, 8 Dec 2024 21:49:37 +0100 Subject: [PATCH] chore(config): switch to yaml --- README.md | 6 +- cmd/backend/backend.go | 12 +- config/development.toml | 78 ------------ config/development.yaml | 113 ++++++++++++++++++ config/production.toml | 0 config/production.yaml | 86 +++++++++++++ .../20241121141143_add_zess_table.sql | 6 +- db/queries/season.sql | 4 + internal/cmd/api.go | 2 +- internal/cmd/event.go | 4 +- internal/cmd/gamification.go | 2 +- internal/cmd/tap.go | 2 +- internal/cmd/zess.go | 4 +- internal/pkg/buzzer/buzzer.go | 2 +- internal/pkg/db/dto/dto.go | 4 +- internal/pkg/db/dto/scan.go | 2 +- internal/pkg/db/dto/season.go | 21 ++-- internal/pkg/db/sqlc/models.go | 4 +- internal/pkg/db/sqlc/season.sql.go | 20 +++- internal/pkg/event/api.go | 15 ++- internal/pkg/event/event.go | 10 +- internal/pkg/gamification/api.go | 6 +- internal/pkg/gamification/gamification.go | 8 +- internal/pkg/song/account.go | 2 +- internal/pkg/song/api.go | 8 +- internal/pkg/song/song.go | 4 +- internal/pkg/tap/api.go | 4 +- internal/pkg/tap/tap.go | 8 +- internal/pkg/zess/api.go | 7 +- internal/pkg/zess/zess.go | 52 ++------ pkg/config/config.go | 4 +- pkg/date/date.go | 44 +++++++ tui/screen/cammie/cammie.go | 4 +- tui/view/event/event.go | 2 +- tui/view/gamification/gamification.go | 2 +- tui/view/message/message.go | 2 +- tui/view/song/song.go | 40 +++++-- tui/view/tap/tap.go | 2 +- tui/view/zess/zess.go | 6 +- 39 files changed, 392 insertions(+), 210 deletions(-) delete mode 100644 config/development.toml create mode 100644 config/development.yaml delete mode 100644 config/production.toml create mode 100644 config/production.yaml create mode 100644 pkg/date/date.go diff --git a/README.md b/README.md index d0176ca..8efdf8f 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Displays the cammie chat along with some other statistics. - `APP_ENV`. Available options are: - `development` - `production` - - `SONG_SPOTIFY_CLIENT_ID` - - `SONG_SPOTIFY_CLIENT_SECRET` -2. Configure the appropriate settings in the corresponding configuration file located in the [config directory](./config) + - `BACKEND_SONG_SPOTIFY_CLIENT_ID` + - `BACKEND_SONG_SPOTIFY_CLIENT_SECRET` +2. Configure the appropriate settings in the corresponding configuration file located in the [config directory](./config). you can either set them as environment variables or inside the configuration file. ## DB diff --git a/cmd/backend/backend.go b/cmd/backend/backend.go index 678b4e4..6c464bc 100644 --- a/cmd/backend/backend.go +++ b/cmd/backend/backend.go @@ -31,14 +31,14 @@ func main() { zap.S().Fatal("DB: Fatal error\n", err) } - // // Tap - // _, _ = cmd.Tap(db) + // Tap + _, _ = cmd.Tap(db) - // // Zess - // _, _, _ = cmd.Zess(db) + // Zess + _, _, _ = cmd.Zess(db) - // // Gamification - // _, _ = cmd.Gamification(db) + // Gamification + _, _ = cmd.Gamification(db) // Event _, _ = cmd.Event(db) diff --git a/config/development.toml b/config/development.toml deleted file mode 100644 index d32a4b3..0000000 --- a/config/development.toml +++ /dev/null @@ -1,78 +0,0 @@ -[server] -host = "localhost" -port = 3000 - -[song] -spotify_api = "https://api.spotify.com/v1" -spotify_account = "https://accounts.spotify.com/api/token" -lrclib_api = "https://lrclib.net/api" - -[tap] -api = "https://tap.zeus.gent" -interval_s = 60 -beers = [ - "Schelfaut", - "Duvel", - "Fourchette", - "Jupiler", - "Karmeliet", - "Kriek", - "Chouffe", - "Maes", - "Somersby", - "Sportzot", - "Stella", -] - -[zess] -api = "http://localhost:4000/api" -interval_season_s = 300 -interval_scan_s = 60 - -[gamification] -api = "https://gamification.zeus.gent" -interval_s = 3600 - -[event] -api = "https://zeus.gent/events" -api_poster = "https://git.zeus.gent/ZeusWPI/visueel/raw/branch/master" -interval_s = 86400 - -[buzzer] -song = [ - "-n", "-f880", "-l100", "-d0", - "-n", "-f988", "-l100", "-d0", - "-n", "-f588", "-l100", "-d0", - "-n", "-f989", "-l100", "-d0", - "-n", "-f660", "-l200", "-d0", - "-n", "-f660", "-l200", "-d0", - "-n", "-f588", "-l100", "-d0", - "-n", "-f555", "-l100", "-d0", - "-n", "-f495", "-l100", "-d0", -] - -[tui] - -[tui.screen] -cammie_interval_change_s = 300 - -[tui.zess] -weeks = 10 -interval_scan_s = 60 -interval_season_s = 3600 - -[tui.message] -interval_s = 1 - -[tui.tap] -interval_s = 60 - -[tui.gamification] -interval_s = 3600 - -[tui.song] -interval_current_s = 5 -interval_top_s = 3600 - -[tui.event] -interval_s = 3600 diff --git a/config/development.yaml b/config/development.yaml new file mode 100644 index 0000000..157510d --- /dev/null +++ b/config/development.yaml @@ -0,0 +1,113 @@ +server: + host: "localhost" + port: 3000 + +db: + host: "localhost" + port: 5432 + user: "postgres" + password: "postgres" + +backend: + buzzer: + song: + - "-n" + - "-f880" + - "-l100" + - "-d0" + - "-n" + - "-f988" + - "-l100" + - "-d0" + - "-n" + - "-f588" + - "-l100" + - "-d0" + - "-n" + - "-f989" + - "-l100" + - "-d0" + - "-n" + - "-f660" + - "-l200" + - "-d0" + - "-n" + - "-f660" + - "-l200" + - "-d0" + - "-n" + - "-f588" + - "-l100" + - "-d0" + - "-n" + - "-f555" + - "-l100" + - "-d0" + - "-n" + - "-f495" + - "-l100" + - "-d0" + + event: + website: "https://zeus.gent/events" + website_poster: "https://git.zeus.gent/ZeusWPI/visueel/raw/branch/master" + interval_s: 3600 + + gamification: + api: "https://gamification.zeus.gent" + interval_s: 3600 + + song: + spotify_api: "https://api.spotify.com/v1" + spotify_api_account: "https://accounts.spotify.com/api/token" + lrclib_api: "https://lrclib.net/api" + + tap: + api: "https://tap.zeus.gent" + beers: + - "Schelfaut" + - "Duvel" + - "Fourchette" + - "Jupiler" + - "Karmeliet" + - "Kriek" + - "Chouffe" + - "Maes" + - "Somersby" + - "Sportzot" + - "Stella" + interval_s: 60 + + zess: + api: "https://zess.zeus.gent/api" + interval_season_s: 300 + interval_scan_s: 60 + + +tui: + screen: + cammie: + interval_s: 300 + + view: + event: + interval_s: 3600 + + gamification: + interval_s: 3600 + + message: + interval_s: 1 + + song: + interval_current_s: 5 + interval_history_s: 5 + interval_top_s: 3600 + + tap: + interval_s: 60 + + zess: + weeks: 10 + interval_scan_s: 60 + interval_season_s: 3600 diff --git a/config/production.toml b/config/production.toml deleted file mode 100644 index e69de29..0000000 diff --git a/config/production.yaml b/config/production.yaml new file mode 100644 index 0000000..72fe481 --- /dev/null +++ b/config/production.yaml @@ -0,0 +1,86 @@ +# [server] +# host = "localhost" +# port = 3000 + +# [db] +# host = "localhost" +# port = 5432 +# database = "scc" +# user = "postgres" +# password = "postgres" + +# [song] +# spotify_api = "https://api.spotify.com/v1" +# spotify_account = "https://accounts.spotify.com/api/token" +# lrclib_api = "https://lrclib.net/api" + +# [tap] +# api = "https://tap.zeus.gent" +# interval_s = 60 +# beers = [ +# "Schelfaut", +# "Duvel", +# "Fourchette", +# "Jupiler", +# "Karmeliet", +# "Kriek", +# "Chouffe", +# "Maes", +# "Somersby", +# "Sportzot", +# "Stella", +# ] + +# [zess] +# api = "http://localhost:4000/api" +# interval_season_s = 300 +# interval_scan_s = 60 + +# [gamification] +# api = "https://gamification.zeus.gent" +# interval_s = 3600 + +# [event] +# api = "https://zeus.gent/events" +# api_poster = "https://git.zeus.gent/ZeusWPI/visueel/raw/branch/master" +# interval_s = 86400 + +# [buzzer] +# song = [ +# "-n", "-f880", "-l100", "-d0", +# "-n", "-f988", "-l100", "-d0", +# "-n", "-f588", "-l100", "-d0", +# "-n", "-f989", "-l100", "-d0", +# "-n", "-f660", "-l200", "-d0", +# "-n", "-f660", "-l200", "-d0", +# "-n", "-f588", "-l100", "-d0", +# "-n", "-f555", "-l100", "-d0", +# "-n", "-f495", "-l100", "-d0", +# ] + +# [tui] + +# [tui.screen] +# cammie_interval_change_s = 300 + +# [tui.zess] +# weeks = 10 +# interval_scan_s = 60 +# interval_season_s = 3600 + +# [tui.message] +# interval_s = 1 + +# [tui.tap] +# interval_s = 60 + +# [tui.gamification] +# interval_s = 3600 + +# [tui.song] +# interval_current_s = 5 +# interval_history_s = 5 +# interval_top_s = 3600 + +# [tui.event] +# interval_s = 3600 diff --git a/db/migrations/20241121141143_add_zess_table.sql b/db/migrations/20241121141143_add_zess_table.sql index a5a6f67..f202edc 100644 --- a/db/migrations/20241121141143_add_zess_table.sql +++ b/db/migrations/20241121141143_add_zess_table.sql @@ -3,12 +3,12 @@ CREATE TABLE IF NOT EXISTS season ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, - start TIMESTAMP WITH TIME ZONE NOT NULL, - "end" TIMESTAMP WITH TIME ZONE NOT NULL + start TIMESTAMP WITHOUT TIME ZONE NOT NULL, + "end" TIMESTAMP WITHOUT TIME ZONE NOT NULL ); CREATE TABLE IF NOT EXISTS scan ( - id INTEGER PRIMARY KEY, + id SERIAL PRIMARY KEY, scan_time TIMESTAMP WITH TIME ZONE NOT NULL ); -- +goose StatementEnd diff --git a/db/queries/season.sql b/db/queries/season.sql index 90682ad..94287be 100644 --- a/db/queries/season.sql +++ b/db/queries/season.sql @@ -24,6 +24,10 @@ RETURNING *; DELETE FROM season WHERE id = $1; +-- name: DeleteSeasonAll :execrows +DELETE FROM season; + + -- Other diff --git a/internal/cmd/api.go b/internal/cmd/api.go index c021fd7..1c70276 100644 --- a/internal/cmd/api.go +++ b/internal/cmd/api.go @@ -31,7 +31,7 @@ func API(db *db.DB, song *song.Song) { apiGroup := app.Group("/api") api.New(apiGroup, db, song) - host := config.GetDefaultString("server.host", "127.0.0.1") + host := config.GetDefaultString("server.host", "localhost") port := config.GetDefaultInt("server.port", 3000) zap.S().Fatal("API: Fatal server error", app.Listen(fmt.Sprintf("%s:%d", host, port))) diff --git a/internal/cmd/event.go b/internal/cmd/event.go index ff392a6..aad0111 100644 --- a/internal/cmd/event.go +++ b/internal/cmd/event.go @@ -20,8 +20,8 @@ func Event(db *db.DB) (*event.Event, chan bool) { } func eventPeriodicUpdate(ev *event.Event, done chan bool) { - interval := config.GetDefaultInt("event.interval_s", 3600) - zap.S().Info("EventL Starting periodic leaderboard update with an interval of ", interval, " seconds") + interval := config.GetDefaultInt("backend.event.interval_s", 3600) + zap.S().Info("Event: Starting periodic leaderboard update with an interval of ", interval, " seconds") ticker := time.NewTimer(time.Duration(interval) * time.Second) defer ticker.Stop() diff --git a/internal/cmd/gamification.go b/internal/cmd/gamification.go index aae1918..b1d88ce 100644 --- a/internal/cmd/gamification.go +++ b/internal/cmd/gamification.go @@ -20,7 +20,7 @@ func Gamification(db *db.DB) (*gamification.Gamification, chan bool) { } func gamificationPeriodicUpdate(gam *gamification.Gamification, done chan bool) { - interval := config.GetDefaultInt("gamification.interval_s", 3600) + interval := config.GetDefaultInt("backend.gamification.interval_s", 3600) zap.S().Info("Gamification: Starting periodic leaderboard update with an interval of ", interval, " seconds") ticker := time.NewTicker(time.Duration(interval) * time.Second) diff --git a/internal/cmd/tap.go b/internal/cmd/tap.go index a2fcc8f..5c80425 100644 --- a/internal/cmd/tap.go +++ b/internal/cmd/tap.go @@ -20,7 +20,7 @@ func Tap(db *db.DB) (*tap.Tap, chan bool) { } func tapPeriodicUpdate(tap *tap.Tap, done chan bool) { - interval := config.GetDefaultInt("tap.interval_s", 60) + interval := config.GetDefaultInt("backend.tap.interval_s", 60) zap.S().Info("Tap: Starting periodic update with an interval of ", interval, " seconds") ticker := time.NewTicker(time.Duration(interval) * time.Second) diff --git a/internal/cmd/zess.go b/internal/cmd/zess.go index c40ab34..f2fd499 100644 --- a/internal/cmd/zess.go +++ b/internal/cmd/zess.go @@ -22,7 +22,7 @@ func Zess(db *db.DB) (*zess.Zess, chan bool, chan bool) { } func zessPeriodicSeasonUpdate(zess *zess.Zess, done chan bool) { - interval := config.GetDefaultInt("zess.interval_season_s", 300) + interval := config.GetDefaultInt("backend.zess.interval_season_s", 300) zap.S().Info("Zess: Starting periodic season update with an interval of ", interval, " seconds") ticker := time.NewTicker(time.Duration(interval) * time.Second) @@ -50,7 +50,7 @@ func zessPeriodicSeasonUpdate(zess *zess.Zess, done chan bool) { } func zessPeriodicScanUpdate(zess *zess.Zess, done chan bool) { - interval := config.GetDefaultInt("zess.interval_scan_s", 60) + interval := config.GetDefaultInt("backend.zess.interval_scan_s", 60) zap.S().Info("Zess: Starting periodic scan update with an interval of ", interval, " seconds") ticker := time.NewTicker(time.Duration(interval) * time.Second) diff --git a/internal/pkg/buzzer/buzzer.go b/internal/pkg/buzzer/buzzer.go index ab19a3b..6b7a3c8 100644 --- a/internal/pkg/buzzer/buzzer.go +++ b/internal/pkg/buzzer/buzzer.go @@ -27,7 +27,7 @@ var defaultSong = []string{ // New returns a new buzzer instance func New() *Buzzer { - song := config.GetDefaultStringSlice("buzzer.song", defaultSong) + song := config.GetDefaultStringSlice("backend.buzzer.song", defaultSong) return &Buzzer{ Song: song, } diff --git a/internal/pkg/db/dto/dto.go b/internal/pkg/db/dto/dto.go index 9382061..08133bd 100644 --- a/internal/pkg/db/dto/dto.go +++ b/internal/pkg/db/dto/dto.go @@ -1,7 +1,9 @@ // Package dto provides the data transfer objects for the database package dto -import "github.com/go-playground/validator/v10" +import ( + "github.com/go-playground/validator/v10" +) // Validate is a validator instance for JSON transferable objects var Validate = validator.New(validator.WithRequiredStructEnabled()) diff --git a/internal/pkg/db/dto/scan.go b/internal/pkg/db/dto/scan.go index ea0183c..03eb265 100644 --- a/internal/pkg/db/dto/scan.go +++ b/internal/pkg/db/dto/scan.go @@ -23,7 +23,7 @@ func ScanDTO(scan sqlc.Scan) *Scan { // CreateParams converts a Scan to sqlc.CreateScanParams func (s *Scan) CreateParams() pgtype.Timestamptz { - return pgtype.Timestamptz{Time: s.ScanTime} + return pgtype.Timestamptz{Time: s.ScanTime, Valid: true} } // UpdateParams converts a Scan to sqlc.UpdateScanParams diff --git a/internal/pkg/db/dto/season.go b/internal/pkg/db/dto/season.go index 6b33e17..08329d2 100644 --- a/internal/pkg/db/dto/season.go +++ b/internal/pkg/db/dto/season.go @@ -1,19 +1,18 @@ package dto import ( - "time" - "github.com/jackc/pgx/v5/pgtype" "github.com/zeusWPI/scc/internal/pkg/db/sqlc" + "github.com/zeusWPI/scc/pkg/date" ) // Season is the DTO for the season type Season struct { ID int32 `json:"id"` Name string `json:"name" validate:"required"` - Start time.Time `json:"start" validate:"required"` - End time.Time `json:"end" validate:"required"` - Current bool `json:"is_current" validate:"required"` + Start date.Date `json:"start" validate:"required"` + End date.Date `json:"end" validate:"required"` + Current bool `json:"is_current"` // FIXME: This should have `required`. However when added the validation fails even though it's present } // SeasonDTO converts a sqlc.Season to a Season @@ -21,8 +20,8 @@ func SeasonDTO(season sqlc.Season) *Season { return &Season{ ID: season.ID, Name: season.Name, - Start: season.Start.Time, - End: season.End.Time, + Start: date.Date(season.Start.Time), + End: date.Date(season.End.Time), Current: season.Current, } } @@ -41,8 +40,8 @@ func SeasonCmp(s1, s2 *Season) int { func (s *Season) CreateParams() sqlc.CreateSeasonParams { return sqlc.CreateSeasonParams{ Name: s.Name, - Start: pgtype.Timestamptz{Time: s.Start, Valid: true}, - End: pgtype.Timestamptz{Time: s.End, Valid: true}, + Start: pgtype.Timestamp{Time: s.Start.ToTime(), Valid: true}, + End: pgtype.Timestamp{Time: s.End.ToTime(), Valid: true}, Current: s.Current, } } @@ -52,8 +51,8 @@ func (s *Season) UpdateParams() sqlc.UpdateSeasonParams { return sqlc.UpdateSeasonParams{ ID: s.ID, Name: s.Name, - Start: pgtype.Timestamptz{Time: s.Start, Valid: true}, - End: pgtype.Timestamptz{Time: s.End, Valid: true}, + End: pgtype.Timestamp{Time: s.End.ToTime(), Valid: true}, + Start: pgtype.Timestamp{Time: s.Start.ToTime(), Valid: true}, Current: s.Current, } } diff --git a/internal/pkg/db/sqlc/models.go b/internal/pkg/db/sqlc/models.go index ac286c0..4083a8a 100644 --- a/internal/pkg/db/sqlc/models.go +++ b/internal/pkg/db/sqlc/models.go @@ -40,8 +40,8 @@ type Scan struct { type Season struct { ID int32 Name string - Start pgtype.Timestamptz - End pgtype.Timestamptz + Start pgtype.Timestamp + End pgtype.Timestamp Current bool } diff --git a/internal/pkg/db/sqlc/season.sql.go b/internal/pkg/db/sqlc/season.sql.go index dec33ce..e40e0d8 100644 --- a/internal/pkg/db/sqlc/season.sql.go +++ b/internal/pkg/db/sqlc/season.sql.go @@ -19,8 +19,8 @@ RETURNING id, name, start, "end", current type CreateSeasonParams struct { Name string - Start pgtype.Timestamptz - End pgtype.Timestamptz + Start pgtype.Timestamp + End pgtype.Timestamp Current bool } @@ -55,6 +55,18 @@ func (q *Queries) DeleteSeason(ctx context.Context, id int32) (int64, error) { return result.RowsAffected(), nil } +const deleteSeasonAll = `-- name: DeleteSeasonAll :execrows +DELETE FROM season +` + +func (q *Queries) DeleteSeasonAll(ctx context.Context) (int64, error) { + result, err := q.db.Exec(ctx, deleteSeasonAll) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + const getAllSeasons = `-- name: GetAllSeasons :many SELECT id, name, start, "end", current @@ -138,8 +150,8 @@ RETURNING id, name, start, "end", current type UpdateSeasonParams struct { Name string - Start pgtype.Timestamptz - End pgtype.Timestamptz + Start pgtype.Timestamp + End pgtype.Timestamp Current bool ID int32 } diff --git a/internal/pkg/event/api.go b/internal/pkg/event/api.go index 3a818bc..2302ef6 100644 --- a/internal/pkg/event/api.go +++ b/internal/pkg/event/api.go @@ -10,11 +10,19 @@ import ( "github.com/gocolly/colly" "github.com/gofiber/fiber/v2" "github.com/zeusWPI/scc/internal/pkg/db/dto" + "github.com/zeusWPI/scc/pkg/config" + "go.uber.org/zap" ) +// TODO: Look at https://github.com/PuerkitoBio/goquery + var layout = "Monday 02 January, 15:04 2006" func (e *Event) getEvents() ([]dto.Event, error) { + zap.S().Info("Events: Getting all events") + + website := config.GetDefaultString("backend.event.website", "https://zeus.gent/events") + var events []dto.Event var errs []error c := colly.NewCollector() @@ -62,7 +70,7 @@ func (e *Event) getEvents() ([]dto.Event, error) { events = append(events, event) }) - err := c.Visit(e.api) + err := c.Visit(website) if err != nil { return nil, err } @@ -73,6 +81,9 @@ func (e *Event) getEvents() ([]dto.Event, error) { } func (e *Event) getPoster(event *dto.Event) error { + zap.S().Info("Events: Getting poster for ", event.Name) + + website := config.GetDefaultString("backend.event.website_poster", "https://git.zeus.gent/ZeusWPI/visueel/raw/branch/master") yearParts := strings.Split(event.AcademicYear, "-") if len(yearParts) != 2 { return fmt.Errorf("Event: Academic year not properly formatted %s", event.AcademicYear) @@ -89,7 +100,7 @@ func (e *Event) getPoster(event *dto.Event) error { year := fmt.Sprintf("20%d-20%d", yearStart, yearEnd) - url := fmt.Sprintf("%s/%s/%s/scc.png", e.apiPoster, year, event.Name) + url := fmt.Sprintf("%s/%s/%s/scc.png", website, year, event.Name) req := fiber.Get(url) status, body, errs := req.Bytes() diff --git a/internal/pkg/event/event.go b/internal/pkg/event/event.go index 8f1d373..d319347 100644 --- a/internal/pkg/event/event.go +++ b/internal/pkg/event/event.go @@ -9,22 +9,16 @@ import ( "github.com/zeusWPI/scc/internal/pkg/db" "github.com/zeusWPI/scc/internal/pkg/db/dto" - "github.com/zeusWPI/scc/pkg/config" ) // Event represents a event instance type Event struct { - db *db.DB - api string - apiPoster string + db *db.DB } // New creates a new event instance func New(db *db.DB) *Event { - api := config.GetDefaultString("event.api", "https://zeus.gent/events") - apiPoster := config.GetDefaultString("event.api_poster", "https://git.zeus.gent/ZeusWPI/visueel/raw/branch/master") - - return &Event{db: db, api: api, apiPoster: apiPoster} + return &Event{db: db} } // Update gets all events from the website of this academic year diff --git a/internal/pkg/gamification/api.go b/internal/pkg/gamification/api.go index b899fb8..280dd1e 100644 --- a/internal/pkg/gamification/api.go +++ b/internal/pkg/gamification/api.go @@ -6,6 +6,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/zeusWPI/scc/internal/pkg/db/dto" + "github.com/zeusWPI/scc/pkg/config" "go.uber.org/zap" ) @@ -19,7 +20,8 @@ type gamificationItem struct { func (g *Gamification) getLeaderboard() ([]dto.Gamification, error) { zap.S().Info("Gamification: Getting leaderboard") - req := fiber.Get(g.api+"/top4").Set("Accept", "application/json") + api := config.GetDefaultString("backend.gamification.api", "https://gamification.zeus.gent") + req := fiber.Get(api+"/top4").Set("Accept", "application/json") res := new([]gamificationItem) status, _, errs := req.Struct(res) @@ -56,6 +58,8 @@ func (g *Gamification) getLeaderboard() ([]dto.Gamification, error) { } func downloadAvatar(gam gamificationItem) (dto.Gamification, error) { + zap.S().Info("Gamification: Getting avatar for ", gam.Name) + req := fiber.Get(gam.AvatarURL) status, body, errs := req.Bytes() if len(errs) != 0 { diff --git a/internal/pkg/gamification/gamification.go b/internal/pkg/gamification/gamification.go index 6b36bef..d6763ac 100644 --- a/internal/pkg/gamification/gamification.go +++ b/internal/pkg/gamification/gamification.go @@ -6,20 +6,16 @@ import ( "errors" "github.com/zeusWPI/scc/internal/pkg/db" - "github.com/zeusWPI/scc/pkg/config" ) // Gamification represents a gamification instance type Gamification struct { - db *db.DB - api string + db *db.DB } // New creates a new gamification instance func New(db *db.DB) *Gamification { - api := config.GetDefaultString("gamification.api", "https://gamification.zeus.gent") - - return &Gamification{db: db, api: api} + return &Gamification{db: db} } // Update gets the current leaderboard from gamification diff --git a/internal/pkg/song/account.go b/internal/pkg/song/account.go index be29ffb..9c779b0 100644 --- a/internal/pkg/song/account.go +++ b/internal/pkg/song/account.go @@ -21,7 +21,7 @@ func (s *Song) refreshToken() error { form := &fiber.Args{} form.Add("grant_type", "client_credentials") - api := config.GetDefaultString("song.spotify_account", "https://accounts.spotify.com/api/token") + api := config.GetDefaultString("backend.song.spotify_api_account", "https://accounts.spotify.com/api/token") req := fiber.Post(api).Form(form).BasicAuth(s.ClientID, s.ClientSecret) res := new(accountResponse) diff --git a/internal/pkg/song/api.go b/internal/pkg/song/api.go index b4eef5c..cf3eb49 100644 --- a/internal/pkg/song/api.go +++ b/internal/pkg/song/api.go @@ -11,7 +11,7 @@ import ( "go.uber.org/zap" ) -var api = config.GetDefaultString("song.spotify_api", "https://api.spotify.com/v1") +var api = config.GetDefaultString("backend.song.spotify_api", "https://api.spotify.com/v1") type trackArtist struct { ID string `json:"id"` @@ -71,6 +71,8 @@ type artistResponse struct { } func (s *Song) getArtist(artist *dto.SongArtist) error { + zap.S().Info("Song: Getting artists info for ", artist.ID) + req := fiber.Get(fmt.Sprintf("%s/%s/%s", api, "artists", artist.SpotifyID)). Set("Authorization", fmt.Sprintf("Bearer %s", s.AccessToken)) @@ -99,6 +101,8 @@ type lyricsResponse struct { } func (s *Song) getLyrics(track *dto.Song) error { + zap.S().Info("Song: Getting lyrics for ", track.Title) + // Get most popular artist if len(track.Artists) == 0 { return fmt.Errorf("Song: No artists for track: %v", track) @@ -117,7 +121,7 @@ func (s *Song) getLyrics(track *dto.Song) error { params.Set("album_name", track.Album) params.Set("duration", fmt.Sprintf("%d", track.DurationMS/1000)) - req := fiber.Get(fmt.Sprintf("%s/get?%s", config.GetDefaultString("song.lrclib_api", "https://lrclib.net/api"), params.Encode())) + req := fiber.Get(fmt.Sprintf("%s/get?%s", config.GetDefaultString("backend.song.lrclib_api", "https://lrclib.net/api"), params.Encode())) res := new(lyricsResponse) status, _, errs := req.Struct(res) diff --git a/internal/pkg/song/song.go b/internal/pkg/song/song.go index ccdd76e..b50841f 100644 --- a/internal/pkg/song/song.go +++ b/internal/pkg/song/song.go @@ -24,8 +24,8 @@ type Song struct { // New creates a new song instance func New(db *db.DB) (*Song, error) { - clientID := config.GetDefaultString("song.spotify_client_id", "") - clientSecret := config.GetDefaultString("song.spotify_client_secret", "") + clientID := config.GetDefaultString("backend.song.spotify_client_id", "") + clientSecret := config.GetDefaultString("backend.song.spotify_client_secret", "") if clientID == "" || clientSecret == "" { return &Song{}, errors.New("Song: Spotify client id or secret not set") diff --git a/internal/pkg/tap/api.go b/internal/pkg/tap/api.go index 522b16e..bfb76e3 100644 --- a/internal/pkg/tap/api.go +++ b/internal/pkg/tap/api.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofiber/fiber/v2" + "github.com/zeusWPI/scc/pkg/config" "go.uber.org/zap" ) @@ -22,7 +23,8 @@ type orderResponse struct { func (t *Tap) getOrders() ([]orderResponseItem, error) { zap.S().Info("Tap: Getting orders") - req := fiber.Get(t.api + "/recent") + api := config.GetDefaultString("backend.tap.api", "https://tap.zeus.gent") + req := fiber.Get(api + "/recent") res := new(orderResponse) status, _, errs := req.Struct(res) diff --git a/internal/pkg/tap/tap.go b/internal/pkg/tap/tap.go index 678d0fa..2988a10 100644 --- a/internal/pkg/tap/tap.go +++ b/internal/pkg/tap/tap.go @@ -18,7 +18,6 @@ import ( // Tap represents a tap instance type Tap struct { db *db.DB - api string beers []string } @@ -38,10 +37,9 @@ var defaultBeers = []string{ // New creates a new tap instance func New(db *db.DB) *Tap { - api := config.GetDefaultString("tap.api", "https://tap.zeus.gent") - beers := config.GetDefaultStringSlice("tap.beers", defaultBeers) + beers := config.GetDefaultStringSlice("backend.tap.beers", defaultBeers) - return &Tap{db: db, api: api, beers: beers} + return &Tap{db: db, beers: beers} } // Update gets all new orders from tap @@ -77,7 +75,7 @@ func (t *Tap) Update() error { for _, order := range orders { _, err := t.db.Queries.CreateTap(context.Background(), sqlc.CreateTapParams{ OrderID: order.OrderID, - OrderCreatedAt: pgtype.Timestamptz{Time: order.OrderCreatedAt}, + OrderCreatedAt: pgtype.Timestamptz{Time: order.OrderCreatedAt, Valid: true}, Name: order.ProductName, Category: order.ProductCategory, }) diff --git a/internal/pkg/zess/api.go b/internal/pkg/zess/api.go index 3b26ff1..9bc8afb 100644 --- a/internal/pkg/zess/api.go +++ b/internal/pkg/zess/api.go @@ -6,13 +6,15 @@ import ( "github.com/gofiber/fiber/v2" "github.com/zeusWPI/scc/internal/pkg/db/dto" + "github.com/zeusWPI/scc/pkg/config" "go.uber.org/zap" ) func (z *Zess) getSeasons() (*[]*dto.Season, error) { zap.S().Info("Zess: Getting seasons") - req := fiber.Get(z.api + "/seasons") + api := config.GetDefaultString("backend.zess.api", "https://zess.zeus.gent") + req := fiber.Get(api + "/seasons") res := new([]*dto.Season) status, _, errs := req.Struct(res) @@ -36,7 +38,8 @@ func (z *Zess) getSeasons() (*[]*dto.Season, error) { func (z *Zess) getScans() (*[]*dto.Scan, error) { zap.S().Info("Zess: Getting scans") - req := fiber.Get(z.api + "/recent_scans") + api := config.GetDefaultString("backend.zess.api", "https://zess.zeus.gent") + req := fiber.Get(api + "/recent_scans") res := new([]*dto.Scan) status, _, errs := req.Struct(res) diff --git a/internal/pkg/zess/zess.go b/internal/pkg/zess/zess.go index c524b2c..9b10f27 100644 --- a/internal/pkg/zess/zess.go +++ b/internal/pkg/zess/zess.go @@ -7,33 +7,27 @@ import ( "slices" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgtype" "github.com/zeusWPI/scc/internal/pkg/db" "github.com/zeusWPI/scc/internal/pkg/db/dto" "github.com/zeusWPI/scc/internal/pkg/db/sqlc" - "github.com/zeusWPI/scc/pkg/config" "github.com/zeusWPI/scc/pkg/util" ) // Zess represents a zess instance type Zess struct { - db *db.DB - api string + db *db.DB } // New creates a new zess instance func New(db *db.DB) *Zess { - api := config.GetDefaultString("zess.api", "https://zess.zeus.gent") - return &Zess{db: db, api: api} + return &Zess{db: db} } // UpdateSeasons updates the seasons func (z *Zess) UpdateSeasons() error { seasons, err := z.db.Queries.GetAllSeasons(context.Background()) - if err != nil { - if err != pgx.ErrNoRows { - return err - } + if err != nil && err != pgx.ErrNoRows { + return err } // Get all seasons from zess @@ -42,41 +36,19 @@ func (z *Zess) UpdateSeasons() error { return err } - equal := slices.CompareFunc(util.SliceMap(seasons, dto.SeasonDTO), *zessSeasons, dto.SeasonCmp) - - // Same seasons - if equal == 0 { + if slices.CompareFunc(util.SliceMap(seasons, dto.SeasonDTO), *zessSeasons, dto.SeasonCmp) == 0 { return nil } - // Update seasons - errs := make([]error, 0) - - for i, season := range *zessSeasons { - if i < len(seasons) { - // Update seasons - seasons[i].ID = season.ID - seasons[i].Name = season.Name - seasons[i].Start = pgtype.Timestamptz{Time: season.Start} - seasons[i].End = pgtype.Timestamptz{Time: season.End} - - _, err := z.db.Queries.UpdateSeason(context.Background(), dto.SeasonDTO(seasons[i]).UpdateParams()) - if err != nil { - errs = append(errs, err) - } - } else { - // Create seasons - _, err := z.db.Queries.CreateSeason(context.Background(), season.CreateParams()) - if err != nil { - errs = append(errs, err) - } - } + // The seasons differ + // Delete all existing and enter the new ones + if _, err := z.db.Queries.DeleteSeasonAll(context.Background()); err != nil { + return err } - // Delete seasons - for i := len(*zessSeasons); i < len(seasons); i++ { - _, err := z.db.Queries.DeleteSeason(context.Background(), seasons[i].ID) - if err != nil { + var errs []error + for _, season := range *zessSeasons { + if _, err := z.db.Queries.CreateSeason(context.Background(), season.CreateParams()); err != nil { errs = append(errs, err) } } diff --git a/pkg/config/config.go b/pkg/config/config.go index 3344fec..6c3e8b0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,8 +10,6 @@ import ( "github.com/spf13/viper" ) -// FIXME: Add mutex for map writes - var mu sync.Mutex func bindEnv(key string) { @@ -20,7 +18,7 @@ func bindEnv(key string) { mu.Lock() defer mu.Unlock() - viper.BindEnv(key, envName) + _ = viper.BindEnv(key, envName) } // Init initializes the configuration diff --git a/pkg/date/date.go b/pkg/date/date.go new file mode 100644 index 0000000..af35c85 --- /dev/null +++ b/pkg/date/date.go @@ -0,0 +1,44 @@ +// Package date makes working with dates without timezones easier +package date + +import ( + "fmt" + "strings" + "time" + + "github.com/go-playground/validator/v10" +) + +// Date represents a date without a timezone +type Date time.Time + +const dateLayout = "2006-01-02" + +// UnmarshalJSON converts bytes to a Date +func (d *Date) UnmarshalJSON(b []byte) error { + str := strings.Trim(string(b), `"`) + if str == "" { + *d = Date(time.Time{}) + return nil + } + + // Parse the date + parsedTime, err := time.Parse(dateLayout, str) + if err != nil { + return fmt.Errorf("failed to parse date: %w", err) + } + *d = Date(parsedTime) + + return nil +} + +// ToTime converts a date to a time object +func (d Date) ToTime() time.Time { + return time.Time(d) +} + +// ValidateDate adds validation support for go-playground/validator for the Date type +func ValidateDate(f1 validator.FieldLevel) bool { + date, ok := f1.Field().Interface().(Date) + return ok && !date.ToTime().IsZero() +} diff --git a/tui/screen/cammie/cammie.go b/tui/screen/cammie/cammie.go index f3d763b..29187db 100644 --- a/tui/screen/cammie/cammie.go +++ b/tui/screen/cammie/cammie.go @@ -39,7 +39,7 @@ func New(db *db.DB) screen.Screen { messages := message.NewModel(db) top := event.NewModel(db) bottom := []view.View{gamification.NewModel(db), tap.NewModel(db), zess.NewModel(db)} - return &Cammie{db: db, messages: messages, bottom: top, top: bottom, indexTop: 2, width: 0, height: 0} + return &Cammie{db: db, messages: messages, bottom: top, top: bottom, indexTop: 0, width: 0, height: 0} } // Init initializes the cammie screen @@ -162,7 +162,7 @@ func (c *Cammie) GetSizeMsg() tea.Msg { } func updateBottomIndex(cammie Cammie) tea.Cmd { - timeout := time.Duration(config.GetDefaultInt("tui.screen.cammie_interval_change_s", 300) * int(time.Second)) + timeout := time.Duration(config.GetDefaultInt("tui.screen.cammie.interval_s", 300) * int(time.Second)) return tea.Tick(timeout, func(_ time.Time) tea.Msg { newIndex := (cammie.indexTop + 1) % len(cammie.top) diff --git a/tui/view/event/event.go b/tui/view/event/event.go index 6dcf550..755ad70 100644 --- a/tui/view/event/event.go +++ b/tui/view/event/event.go @@ -76,7 +76,7 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "event update", View: m, Update: updateEvents, - Interval: config.GetDefaultInt("tui.event.interval_s", 3600), + Interval: config.GetDefaultInt("tui.view.event.interval_s", 3600), }, } } diff --git a/tui/view/gamification/gamification.go b/tui/view/gamification/gamification.go index 15d7282..d47bec6 100644 --- a/tui/view/gamification/gamification.go +++ b/tui/view/gamification/gamification.go @@ -86,7 +86,7 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "gamification leaderboard", View: m, Update: updateLeaderboard, - Interval: config.GetDefaultInt("tui.gamification.interval_s", 3600), + Interval: config.GetDefaultInt("tui.view.gamification.interval_s", 3600), }, } } diff --git a/tui/view/message/message.go b/tui/view/message/message.go index 0ed52df..abe6704 100644 --- a/tui/view/message/message.go +++ b/tui/view/message/message.go @@ -87,7 +87,7 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "cammie messages", View: m, Update: updateMessages, - Interval: config.GetDefaultInt("tui.message.interval_s", 1), + Interval: config.GetDefaultInt("tui.view.message.interval_s", 1), }, } } diff --git a/tui/view/song/song.go b/tui/view/song/song.go index 1a4e33a..c4ab914 100644 --- a/tui/view/song/song.go +++ b/tui/view/song/song.go @@ -57,6 +57,10 @@ type msgTop struct { topArtists []topStat } +type msgHistory struct { + history []string +} + type msgLyrics struct { song dto.Song previous []string @@ -73,13 +77,10 @@ type topStat struct { // NewModel initializes a new song model func NewModel(db *db.DB) view.View { - // Get history, afterwards it gets updated when a new currentSong is detected - history, _ := db.Queries.GetSongHistory(context.Background()) - return &Model{ db: db, current: playing{stopwatch: stopwatch.New(), progress: progress.New()}, - history: history, + history: make([]string, 0, 5), topSongs: make([]topStat, 0, 5), topGenres: make([]topStat, 0, 5), topArtists: make([]topStat, 0, 5), @@ -110,11 +111,6 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { return m, nil case msgPlaying: - m.history = append(m.history, msg.current.song.Title) - if len(m.history) > 5 { - m.history = m.history[1:] - } - m.current = msg.current // New song, start the commands to update the lyrics lyric, ok := m.current.lyrics.Next() @@ -139,6 +135,11 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { m.current.upcoming = lyricsToString(m.current.lyrics.Upcoming(upcomingAmount)) return m, tea.Batch(updateLyrics(m.current, startTime), m.current.stopwatch.Start(time.Since(m.current.song.CreatedAt))) + case msgHistory: + m.history = msg.history + + return m, nil + case msgTop: if msg.topSongs != nil { m.topSongs = msg.topSongs @@ -203,13 +204,19 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "update current song", View: m, Update: updateCurrentSong, - Interval: config.GetDefaultInt("tui.song.interval_current_s", 5), + Interval: config.GetDefaultInt("tui.view.song.interval_current_s", 5), + }, + { + Name: "update history", + View: m, + Update: updateHistory, + Interval: config.GetDefaultInt("tui.view.song.interval_history_s", 5), }, { Name: "top stats", View: m, Update: updateTopStats, - Interval: config.GetDefaultInt("tui.song.interval_top_s", 3600), + Interval: config.GetDefaultInt("tui.view.song.interval_top_s", 3600), }, } } @@ -244,6 +251,17 @@ func updateCurrentSong(view view.View) (tea.Msg, error) { return msgPlaying{current: playing{song: song, lyrics: lyrics.New(song)}}, nil } +func updateHistory(view view.View) (tea.Msg, error) { + m := view.(*Model) + + history, err := m.db.Queries.GetSongHistory(context.Background()) + if err != nil && err != pgx.ErrNoRows { + return nil, err + } + + return msgHistory{history: history}, nil +} + func updateTopStats(view view.View) (tea.Msg, error) { m := view.(*Model) msg := msgTop{} diff --git a/tui/view/tap/tap.go b/tui/view/tap/tap.go index d752685..3f51a39 100644 --- a/tui/view/tap/tap.go +++ b/tui/view/tap/tap.go @@ -117,7 +117,7 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "tap orders", View: m, Update: updateOrders, - Interval: config.GetDefaultInt("tui.tap.interval_s", 60), + Interval: config.GetDefaultInt("tui.view.tap.interval_s", 60), }, } } diff --git a/tui/view/zess/zess.go b/tui/view/zess/zess.go index 2d672b6..53bc632 100644 --- a/tui/view/zess/zess.go +++ b/tui/view/zess/zess.go @@ -120,7 +120,7 @@ func (m *Model) Update(msg tea.Msg) (view.View, tea.Cmd) { m.maxWeekScans = newScan.amount } // Make sure the array doesn't get too big - if len(m.scans) > config.GetDefaultInt("tui.zess.weeks", 10) { + if len(m.scans) > config.GetDefaultInt("tui.view.zess.weeks", 10) { m.scans = m.scans[:1] } } @@ -177,13 +177,13 @@ func (m *Model) GetUpdateDatas() []view.UpdateData { Name: "zess scans", View: m, Update: updateScans, - Interval: config.GetDefaultInt("tui.zess.interval_scan_s", 60), + Interval: config.GetDefaultInt("tui.view.zess.interval_scan_s", 60), }, { Name: "zess season", View: m, Update: updateSeason, - Interval: config.GetDefaultInt("tui.zess.interval_season_s", 3600), + Interval: config.GetDefaultInt("tui.view.zess.interval_season_s", 3600), }, } }