From 119420fddf870e70d1a5d7877623b74173c4e4c8 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Wed, 27 Nov 2024 16:41:59 +0100 Subject: [PATCH] feat: add website events integration --- .gitignore | 3 + config/development.toml | 4 + .../20241127133125_add_events_table.sql | 15 ++ db/queries/event.sql | 28 ++++ go.mod | 17 ++- go.sum | 45 +++++- internal/cmd/event.go | 48 ++++++ internal/cmd/gamification.go | 6 +- internal/cmd/tap.go | 6 +- internal/cmd/zess.go | 12 +- internal/pkg/db/dto/event.go | 42 ++++++ internal/pkg/db/sqlc/event.sql.go | 137 ++++++++++++++++++ internal/pkg/db/sqlc/models.go | 8 + internal/pkg/event/api.go | 71 +++++++++ internal/pkg/event/event.go | 72 +++++++++ 15 files changed, 495 insertions(+), 19 deletions(-) create mode 100644 db/migrations/20241127133125_add_events_table.sql create mode 100644 db/queries/event.sql create mode 100644 internal/cmd/event.go create mode 100644 internal/pkg/db/dto/event.go create mode 100644 internal/pkg/db/sqlc/event.sql.go create mode 100644 internal/pkg/event/api.go create mode 100644 internal/pkg/event/event.go diff --git a/.gitignore b/.gitignore index 52db340..00e13bd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ logs/ # DB *.db + +# UI Test file +ui/screen/test.go diff --git a/config/development.toml b/config/development.toml index ca54d00..6a0b36e 100644 --- a/config/development.toml +++ b/config/development.toml @@ -32,6 +32,10 @@ interval_scan_s = 60 api = "https://gamification.zeus.gent" interval_s = 3600 +[event] +api = "https://zeus.gent/events" +interval_s = 86400 + [buzzer] song = [ "-n", "-f880", "-l100", "-d0", diff --git a/db/migrations/20241127133125_add_events_table.sql b/db/migrations/20241127133125_add_events_table.sql new file mode 100644 index 0000000..a083569 --- /dev/null +++ b/db/migrations/20241127133125_add_events_table.sql @@ -0,0 +1,15 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS event ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + date TIMESTAMP NOT NULL, + academic_year TEXT NOT NULL, + location TEXT NOT NULL +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS event; +-- +goose StatementEnd diff --git a/db/queries/event.sql b/db/queries/event.sql new file mode 100644 index 0000000..e421a05 --- /dev/null +++ b/db/queries/event.sql @@ -0,0 +1,28 @@ +-- CRUD + + +-- name: GetAllEvents :many +SELECT * +FROM event; + +-- name: CreateEvent :one +INSERT INTO event (name, date, academic_year, location) +VALUES (?, ?, ?, ?) +RETURNING *; + +-- name: DeleteEvent :exec +DELETE FROM event +WHERE id = ?; + + +-- Other + + +-- name: GetEventByAcademicYear :many +SELECT * +FROM event +WHERE academic_year = ?; + +-- name: DeleteEventByAcademicYear :exec +DELETE FROM event +WHERE academic_year = ?; diff --git a/go.mod b/go.mod index 20eda78..0a6f16f 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,16 @@ require ( github.com/joho/godotenv v1.5.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/spf13/viper v1.19.0 + ) require ( + github.com/PuerkitoBio/goquery v1.10.0 // indirect github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/antchfx/htmlquery v1.3.3 // indirect + github.com/antchfx/xmlquery v1.4.2 // indirect + github.com/antchfx/xpath v1.3.2 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/bubbles v0.18.0 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect @@ -20,7 +26,11 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/kennygrant/sanitize v1.2.4 // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lrstanley/bubblezone v0.0.0-20240125042004-b7bafc493195 // indirect @@ -33,14 +43,18 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect + github.com/temoto/robotstxt v1.1.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/image v0.11.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/term v0.24.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) @@ -48,6 +62,7 @@ require ( github.com/NimbleMarkets/ntcharts v0.1.2 github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-playground/validator/v10 v10.22.1 + github.com/gocolly/colly v1.2.0 github.com/gofiber/contrib/fiberzap v1.0.2 github.com/gofiber/fiber/v2 v2.52.5 github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/go.sum b/go.sum index 9a589d4..9499d22 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,17 @@ github.com/NimbleMarkets/ntcharts v0.1.2 h1:iW1aiOif/Dm74sQd18opi10RMED5589cVhy9SGp98Tw= github.com/NimbleMarkets/ntcharts v0.1.2/go.mod h1:WcHS7kc8oQctN1543DeV9a+gOrS4DDVfKp1N9RZFUqc= +github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= +github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/antchfx/htmlquery v1.3.3 h1:x6tVzrRhVNfECDaVxnZi1mEGrQg3mjE/rxbH2Pe6dNE= +github.com/antchfx/htmlquery v1.3.3/go.mod h1:WeU3N7/rL6mb6dCwtE30dURBnBieKDC/fR8t6X+cKjU= +github.com/antchfx/xmlquery v1.4.2 h1:MZKd9+wblwxfQ1zd1AdrTsqVaMjMCwow3IqkCSe00KA= +github.com/antchfx/xmlquery v1.4.2/go.mod h1:QXhvf5ldTuGqhd1SHNvvtlhhdQLks4dD0awIVhXIDTA= +github.com/antchfx/xpath v1.3.2 h1:LNjzlsSjinu3bQpw9hWMY9ocB80oLOWuQqFvO6xt51U= +github.com/antchfx/xpath v1.3.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= @@ -38,10 +48,21 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI= +github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= github.com/gofiber/contrib/fiberzap v1.0.2 h1:EQwhggtszVfIdBeXxN9Xrmld71es34Ufs+ef8VMqZxc= github.com/gofiber/contrib/fiberzap v1.0.2/go.mod h1:jGO8BHU4gRI9U0JtM6zj2CIhYfgVmW5JxziN8NTgVwE= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= @@ -50,6 +71,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -104,6 +127,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -118,6 +143,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -125,6 +151,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg= +github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= @@ -153,8 +181,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -169,17 +199,21 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= @@ -188,6 +222,13 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/cmd/event.go b/internal/cmd/event.go new file mode 100644 index 0000000..ff392a6 --- /dev/null +++ b/internal/cmd/event.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "time" + + "github.com/zeusWPI/scc/internal/pkg/db" + "github.com/zeusWPI/scc/internal/pkg/event" + "github.com/zeusWPI/scc/pkg/config" + "go.uber.org/zap" +) + +// Event starts the event instance +func Event(db *db.DB) (*event.Event, chan bool) { + ev := event.New(db) + done := make(chan bool) + + go eventPeriodicUpdate(ev, done) + + return ev, done +} + +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") + + ticker := time.NewTimer(time.Duration(interval) * time.Second) + defer ticker.Stop() + + // Run immediatly once + zap.S().Info("Event: Updating events") + if err := ev.Update(); err != nil { + zap.S().Error("Event: Error updating events\n", err) + } + + for { + select { + case <-done: + zap.S().Info("Event: Stopping periodic leaderboard update") + return + case <-ticker.C: + // Update leaderboard + zap.S().Info("Event: Updating events") + if err := ev.Update(); err != nil { + zap.S().Error("Event: Error updating events\n", err) + } + } + } +} diff --git a/internal/cmd/gamification.go b/internal/cmd/gamification.go index 550f857..aae1918 100644 --- a/internal/cmd/gamification.go +++ b/internal/cmd/gamification.go @@ -28,8 +28,7 @@ func gamificationPeriodicUpdate(gam *gamification.Gamification, done chan bool) // Run immediatly once zap.S().Info("Gamification: Updating leaderboard") - err := gam.Update() - if err != nil { + if err := gam.Update(); err != nil { zap.S().Error("gamification: Error updating leaderboard\n", err) } @@ -41,8 +40,7 @@ func gamificationPeriodicUpdate(gam *gamification.Gamification, done chan bool) case <-ticker.C: // Update leaderboard zap.S().Info("Gamification: Updating leaderboard") - err := gam.Update() - if err != nil { + if err := gam.Update(); err != nil { zap.S().Error("gamification: Error updating leaderboard\n", err) } } diff --git a/internal/cmd/tap.go b/internal/cmd/tap.go index 39a18d6..a2fcc8f 100644 --- a/internal/cmd/tap.go +++ b/internal/cmd/tap.go @@ -28,8 +28,7 @@ func tapPeriodicUpdate(tap *tap.Tap, done chan bool) { // Run immediatly once zap.S().Info("Tap: Updating tap") - err := tap.Update() - if err != nil { + if err := tap.Update(); err != nil { zap.S().Error("Tap: Error updating tap\n", err) } @@ -41,8 +40,7 @@ func tapPeriodicUpdate(tap *tap.Tap, done chan bool) { case <-ticker.C: // Update tap zap.S().Info("Tap: Updating tap") - err := tap.Update() - if err != nil { + if err := tap.Update(); err != nil { zap.S().Error("Tap: Error updating tap\n", err) } } diff --git a/internal/cmd/zess.go b/internal/cmd/zess.go index f80a529..c40ab34 100644 --- a/internal/cmd/zess.go +++ b/internal/cmd/zess.go @@ -30,8 +30,7 @@ func zessPeriodicSeasonUpdate(zess *zess.Zess, done chan bool) { // Run immediatly once zap.S().Info("Zess: Updating seasons") - err := zess.UpdateSeasons() - if err != nil { + if err := zess.UpdateSeasons(); err != nil { zap.S().Error("Zess: Error updating seasons\n", err) } @@ -43,8 +42,7 @@ func zessPeriodicSeasonUpdate(zess *zess.Zess, done chan bool) { case <-ticker.C: // Update seasons zap.S().Info("Zess: Updating seasons") - err := zess.UpdateSeasons() - if err != nil { + if err := zess.UpdateSeasons(); err != nil { zap.S().Error("Zess: Error updating seasons\n", err) } } @@ -60,8 +58,7 @@ func zessPeriodicScanUpdate(zess *zess.Zess, done chan bool) { // Run immediatly once zap.S().Info("Zess: Updating scans") - err := zess.UpdateScans() - if err != nil { + if err := zess.UpdateScans(); err != nil { zap.S().Error("Zess: Error updating scans\n", err) } @@ -73,8 +70,7 @@ func zessPeriodicScanUpdate(zess *zess.Zess, done chan bool) { case <-ticker.C: // Update scans zap.S().Info("Zess: Updating scans") - err := zess.UpdateScans() - if err != nil { + if err := zess.UpdateScans(); err != nil { zap.S().Error("Zess: Error updating scans\n", err) } } diff --git a/internal/pkg/db/dto/event.go b/internal/pkg/db/dto/event.go new file mode 100644 index 0000000..838d833 --- /dev/null +++ b/internal/pkg/db/dto/event.go @@ -0,0 +1,42 @@ +package dto + +import ( + "time" + + "github.com/zeusWPI/scc/internal/pkg/db/sqlc" +) + +// Event represents the DTO object for event +type Event struct { + ID int64 + Name string + Date time.Time + AcademicYear string + Location string +} + +// EventDTO converts a sqlc Event object to a DTO Event +func EventDTO(e sqlc.Event) *Event { + return &Event{ + ID: e.ID, + Name: e.Name, + Date: e.Date, + AcademicYear: e.AcademicYear, + Location: e.Location, + } +} + +// Equal compares 2 events +func (e *Event) Equal(e2 Event) bool { + return e.Name == e2.Name && e.Date.Equal(e2.Date) && e.AcademicYear == e2.AcademicYear && e.Location == e2.Location +} + +// CreateParams converts a Event DTO to a sqlc CreateEventParams object +func (e *Event) CreateParams() sqlc.CreateEventParams { + return sqlc.CreateEventParams{ + Name: e.Name, + Date: e.Date, + AcademicYear: e.AcademicYear, + Location: e.Location, + } +} diff --git a/internal/pkg/db/sqlc/event.sql.go b/internal/pkg/db/sqlc/event.sql.go new file mode 100644 index 0000000..4759542 --- /dev/null +++ b/internal/pkg/db/sqlc/event.sql.go @@ -0,0 +1,137 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: event.sql + +package sqlc + +import ( + "context" + "time" +) + +const createEvent = `-- name: CreateEvent :one +INSERT INTO event (name, date, academic_year, location) +VALUES (?, ?, ?, ?) +RETURNING id, name, date, academic_year, location +` + +type CreateEventParams struct { + Name string + Date time.Time + AcademicYear string + Location string +} + +func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event, error) { + row := q.db.QueryRowContext(ctx, createEvent, + arg.Name, + arg.Date, + arg.AcademicYear, + arg.Location, + ) + var i Event + err := row.Scan( + &i.ID, + &i.Name, + &i.Date, + &i.AcademicYear, + &i.Location, + ) + return i, err +} + +const deleteEvent = `-- name: DeleteEvent :exec +DELETE FROM event +WHERE id = ? +` + +func (q *Queries) DeleteEvent(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteEvent, id) + return err +} + +const deleteEventByAcademicYear = `-- name: DeleteEventByAcademicYear :exec +DELETE FROM event +WHERE academic_year = ? +` + +func (q *Queries) DeleteEventByAcademicYear(ctx context.Context, academicYear string) error { + _, err := q.db.ExecContext(ctx, deleteEventByAcademicYear, academicYear) + return err +} + +const getAllEvents = `-- name: GetAllEvents :many + + +SELECT id, name, date, academic_year, location +FROM event +` + +// CRUD +func (q *Queries) GetAllEvents(ctx context.Context) ([]Event, error) { + rows, err := q.db.QueryContext(ctx, getAllEvents) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Event + for rows.Next() { + var i Event + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Date, + &i.AcademicYear, + &i.Location, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getEventByAcademicYear = `-- name: GetEventByAcademicYear :many + + +SELECT id, name, date, academic_year, location +FROM event +WHERE academic_year = ? +` + +// Other +func (q *Queries) GetEventByAcademicYear(ctx context.Context, academicYear string) ([]Event, error) { + rows, err := q.db.QueryContext(ctx, getEventByAcademicYear, academicYear) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Event + for rows.Next() { + var i Event + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Date, + &i.AcademicYear, + &i.Location, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/pkg/db/sqlc/models.go b/internal/pkg/db/sqlc/models.go index a2d2dfc..ed03606 100644 --- a/internal/pkg/db/sqlc/models.go +++ b/internal/pkg/db/sqlc/models.go @@ -8,6 +8,14 @@ import ( "time" ) +type Event struct { + ID int64 + Name string + Date time.Time + AcademicYear string + Location string +} + type Gamification struct { ID int64 Name string diff --git a/internal/pkg/event/api.go b/internal/pkg/event/api.go new file mode 100644 index 0000000..37a5358 --- /dev/null +++ b/internal/pkg/event/api.go @@ -0,0 +1,71 @@ +package event + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/gocolly/colly" + "github.com/zeusWPI/scc/internal/pkg/db/dto" +) + +var layout = "Monday 02 January, 15:04 2006" + +func (e *Event) getEvents() ([]dto.Event, error) { + var events []dto.Event + var errs []error + c := colly.NewCollector() + + c.OnHTML(".event-tile", func(el *colly.HTMLElement) { + event := dto.Event{} + + // Name + event.Name = el.ChildText(".is-size-4-mobile") + + // Date & Location + dateLoc := el.DOM.Find(".event-time-loc").Contents() + dateLocStr := strings.Split(dateLoc.Text(), " ") + + if len(dateLocStr) != 2 { + errs = append(errs, fmt.Errorf("Event: Unable to scrape date and location %s", dateLocStr)) + return + } + + // Location + event.Location = strings.TrimSpace(dateLocStr[1]) + + // Date + date := strings.TrimSpace(dateLocStr[0]) + + yearString := el.Attr("href") + yearParts := strings.Split(yearString, "/") + if len(yearParts) == 5 { + rangeParts := strings.Split(yearParts[2], "-") + if len(rangeParts) == 2 { + dateWithYear := fmt.Sprintf("%s 20%s", date, rangeParts[0]) + parsedDate, err := time.Parse(layout, dateWithYear) + if err == nil { + event.AcademicYear = yearParts[2] + event.Date = parsedDate + } + } + } + // Check for error + if event.Date.IsZero() { + errs = append(errs, fmt.Errorf("Event: Unable to parse date %s %s", date, yearString)) + return + } + + events = append(events, event) + }) + + err := c.Visit(e.api) + if err != nil { + return nil, err + } + + c.Wait() + + return events, errors.Join(errs...) +} diff --git a/internal/pkg/event/event.go b/internal/pkg/event/event.go new file mode 100644 index 0000000..18140f3 --- /dev/null +++ b/internal/pkg/event/event.go @@ -0,0 +1,72 @@ +// Package event provides all logic regarding the events of the website +package event + +import ( + "context" + "errors" + "slices" + + "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 +} + +// New creates a new event instance +func New(db *db.DB) *Event { + api := config.GetDefaultString("event.api", "https://zeus.gent/events") + + return &Event{db: db, api: api} +} + +// Update gets all events from the website of this academic year +func (e *Event) Update() error { + events, err := e.getEvents() + if err != nil { + return err + } + if len(events) == 0 { + return nil + } + + eventsDB, err := e.db.Queries.GetEventByAcademicYear(context.Background(), events[0].AcademicYear) + if err != nil { + return err + } + + equal := false + if len(events) == len(eventsDB) { + for _, event := range eventsDB { + found := slices.ContainsFunc(events, func(ev dto.Event) bool { return ev.Equal(*dto.EventDTO(event)) }) + if !found { + break + } + } + equal = true + } + + // Both are equal, nothing to be done + if equal { + return nil + } + + // They differ, remove the old ones and insert the new once + err = e.db.Queries.DeleteEventByAcademicYear(context.Background(), events[0].AcademicYear) + if err != nil { + return err + } + var errs []error + for _, event := range events { + _, err = e.db.Queries.CreateEvent(context.Background(), event.CreateParams()) + if err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) +}