mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-08-03 02:09:15 +02:00
Merge branch 'main' into 104-exams-in-calendar
# Conflicts: # backend/service/addSchedule.go
This commit is contained in:
26
README.md
26
README.md
@@ -59,6 +59,32 @@ Stay for this time on the page and wait for the response.
|
|||||||
http://127.0.0.1/api/fetch/events
|
http://127.0.0.1/api/fetch/events
|
||||||
|
|
||||||
|
|
||||||
|
After you fetched the events you can optional fetch sport events.
|
||||||
|
This will scrape the sport events from the HTWK website and store them in the database.
|
||||||
|
This will take a few seconds (2s-10s).
|
||||||
|
|
||||||
|
http://127.0.0.1/api/fetch/sports
|
||||||
|
|
||||||
|
|
||||||
### View/Filter/Search in Admin UI
|
### View/Filter/Search in Admin UI
|
||||||
|
|
||||||
If you want some easy first api endpoints and data views, you can use the Admin UI.
|
If you want some easy first api endpoints and data views, you can use the Admin UI.
|
||||||
|
|
||||||
|
|
||||||
|
### Schedules
|
||||||
|
|
||||||
|
In our project we used schedules to fetch data from HTWK and store it in the database.
|
||||||
|
So they get periodically executed and keep the database up to date.
|
||||||
|
|
||||||
|
You can find the schedules in the following directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
service/addSchedule.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently, there are 3 schedules:
|
||||||
|
|
||||||
|
- FetchGroupsSchedule
|
||||||
|
- FetchEventsSchedule
|
||||||
|
- FetchSportsSchedule
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ module htwkalender
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.3.1
|
||||||
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0
|
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0
|
||||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||||
@@ -13,6 +14,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||||
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.46.3 // indirect
|
github.com/aws/aws-sdk-go v1.46.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
|
||||||
|
@@ -14,6 +14,10 @@ github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1r
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
|
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
@@ -239,8 +243,10 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -258,25 +264,30 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
@@ -2,6 +2,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/tools/types"
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
@@ -13,21 +14,30 @@ func (m Events) Contains(event Event) bool {
|
|||||||
return slices.Contains(m, event)
|
return slices.Contains(m, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AnonymizedEventDTO struct {
|
||||||
|
Day string `db:"Day" json:"day"`
|
||||||
|
Week string `db:"Week" json:"week"`
|
||||||
|
Start types.DateTime `db:"start" json:"start"`
|
||||||
|
End types.DateTime `db:"end" json:"end"`
|
||||||
|
Rooms string `db:"Rooms" json:"rooms"`
|
||||||
|
Free bool `json:"free"`
|
||||||
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
UUID string `db:"uuid" json:"uuid"`
|
UUID string `db:"uuid" json:"uuid"`
|
||||||
Day string `db:"Day" json:"day"`
|
Day string `db:"Day" json:"day"`
|
||||||
Week string `db:"Week" json:"week"`
|
Week string `db:"Week" json:"week"`
|
||||||
Start types.DateTime `db:"start" json:"start"`
|
Start types.DateTime `db:"start" json:"start"`
|
||||||
End types.DateTime `db:"end" json:"end"`
|
End types.DateTime `db:"end" json:"end"`
|
||||||
Name string `db:"Name" json:"name"`
|
Name string `db:"Name" json:"name"`
|
||||||
EventType string `db:"EventType" json:"eventType"`
|
EventType string `db:"EventType" json:"eventType"`
|
||||||
Compulsory string `db:"Compulsory" json:"compulsory"`
|
Compulsory string `db:"Compulsory" json:"compulsory"`
|
||||||
Prof string `db:"Prof" json:"prof"`
|
Prof string `db:"Prof" json:"prof"`
|
||||||
Rooms string `db:"Rooms" json:"rooms"`
|
Rooms string `db:"Rooms" json:"rooms"`
|
||||||
Notes string `db:"Notes" json:"notes"`
|
Notes string `db:"Notes" json:"notes"`
|
||||||
BookedAt string `db:"BookedAt" json:"bookedAt"`
|
BookedAt string `db:"BookedAt" json:"bookedAt"`
|
||||||
Course string `db:"course" json:"course"`
|
Course string `db:"course" json:"course"`
|
||||||
Semester string `db:"semester" json:"semester"`
|
Semester string `db:"semester" json:"semester"`
|
||||||
models.BaseModel
|
models.BaseModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,3 +62,15 @@ func (m *Event) SetCourse(course string) Event {
|
|||||||
m.Course = course
|
m.Course = course
|
||||||
return *m
|
return *m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates an AnonymizedEventDTO from an Event hiding all sensitive data
|
||||||
|
func (m *Event) AnonymizeEvent() AnonymizedEventDTO {
|
||||||
|
return AnonymizedEventDTO{
|
||||||
|
Day: m.Day,
|
||||||
|
Week: m.Week,
|
||||||
|
Start: m.Start,
|
||||||
|
End: m.End,
|
||||||
|
Rooms: m.Rooms,
|
||||||
|
Free: strings.Contains(strings.ToLower(m.Name), "zur freien verfügung"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
@@ -126,3 +127,73 @@ func TestEvent_Equals(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvent_AnonymizeEvent(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
UUID string
|
||||||
|
Day string
|
||||||
|
Week string
|
||||||
|
Start types.DateTime
|
||||||
|
End types.DateTime
|
||||||
|
Name string
|
||||||
|
EventType string
|
||||||
|
Compulsory string
|
||||||
|
Prof string
|
||||||
|
Rooms string
|
||||||
|
Notes string
|
||||||
|
BookedAt string
|
||||||
|
Course string
|
||||||
|
Semester string
|
||||||
|
BaseModel models.BaseModel
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want AnonymizedEventDTO
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty event",
|
||||||
|
fields: fields{},
|
||||||
|
want: AnonymizedEventDTO{Day: "", Week: "", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "", Free: false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one event",
|
||||||
|
fields: fields{Name: "Event", Day: "test", Week: "test", Rooms: "test"},
|
||||||
|
want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one event with free",
|
||||||
|
fields: fields{Name: "Räume zur freien Verfügung", Day: "test", Week: "test", Rooms: "test", Course: "test"},
|
||||||
|
want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "another free event",
|
||||||
|
fields: fields{Name: "Zur freien Verfügung", Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Course: "42INM-3"},
|
||||||
|
want: AnonymizedEventDTO{Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Free: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := &Event{
|
||||||
|
UUID: tt.fields.UUID,
|
||||||
|
Day: tt.fields.Day,
|
||||||
|
Week: tt.fields.Week,
|
||||||
|
Start: tt.fields.Start,
|
||||||
|
End: tt.fields.End,
|
||||||
|
Name: tt.fields.Name,
|
||||||
|
EventType: tt.fields.EventType,
|
||||||
|
Compulsory: tt.fields.Compulsory,
|
||||||
|
Prof: tt.fields.Prof,
|
||||||
|
Rooms: tt.fields.Rooms,
|
||||||
|
Notes: tt.fields.Notes,
|
||||||
|
BookedAt: tt.fields.BookedAt,
|
||||||
|
Course: tt.fields.Course,
|
||||||
|
Semester: tt.fields.Semester,
|
||||||
|
BaseModel: tt.fields.BaseModel,
|
||||||
|
}
|
||||||
|
if got := m.AnonymizeEvent(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Event.AnonymizeEvent() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
56
backend/model/sportFetcherModel.go
Normal file
56
backend/model/sportFetcherModel.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// MODELS
|
||||||
|
|
||||||
|
// SportEntry represents the overall event details.
|
||||||
|
type SportEntry struct {
|
||||||
|
Title string
|
||||||
|
Details EventDetails
|
||||||
|
AdditionalNote string
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventDetails represents detailed information about the event.
|
||||||
|
type EventDetails struct {
|
||||||
|
DateRange DateRange
|
||||||
|
Cycle string
|
||||||
|
Gender string
|
||||||
|
CourseLead CourseLead
|
||||||
|
Location Location
|
||||||
|
Participants Participants
|
||||||
|
Cost string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateRange represents a start and end date.
|
||||||
|
type DateRange struct {
|
||||||
|
Start time.Time
|
||||||
|
End time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// CourseLead represents a person with a name and a contact link.
|
||||||
|
type CourseLead struct {
|
||||||
|
Name string
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location represents the location of the event.
|
||||||
|
type Location struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Participants represents the participants' details.
|
||||||
|
type Participants struct {
|
||||||
|
Bookings int
|
||||||
|
TotalPlaces int
|
||||||
|
WaitList int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SportDayStartEnd struct {
|
||||||
|
Start time.Time
|
||||||
|
End time.Time
|
||||||
|
Day time.Weekday
|
||||||
|
}
|
@@ -8,7 +8,7 @@ servers:
|
|||||||
- url: http://localhost:8090
|
- url: http://localhost:8090
|
||||||
description: Local server
|
description: Local server
|
||||||
paths:
|
paths:
|
||||||
/api/fetchPlans:
|
/api/fetch/events:
|
||||||
get:
|
get:
|
||||||
summary: Fetch Seminar Plans
|
summary: Fetch Seminar Plans
|
||||||
security:
|
security:
|
||||||
@@ -16,7 +16,7 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Successful response
|
description: Successful response
|
||||||
/api/fetchGroups:
|
/api/fetch/groups:
|
||||||
get:
|
get:
|
||||||
summary: Fetch Seminar Groups
|
summary: Fetch Seminar Groups
|
||||||
security:
|
security:
|
||||||
@@ -24,6 +24,14 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Successful response
|
description: Successful response
|
||||||
|
/api/fetch/sports:
|
||||||
|
get:
|
||||||
|
summary: Fetch Sport Events from HTWK Leipzig
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful response
|
||||||
/api/modules:
|
/api/modules:
|
||||||
delete:
|
delete:
|
||||||
summary: Delete Module
|
summary: Delete Module
|
||||||
|
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"htwkalender/service/events"
|
"htwkalender/service/events"
|
||||||
|
"htwkalender/service/fetch/sport"
|
||||||
v1 "htwkalender/service/fetch/v1"
|
v1 "htwkalender/service/fetch/v1"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
"htwkalender/service/ical"
|
"htwkalender/service/ical"
|
||||||
@@ -54,6 +55,26 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/api/fetch/sports",
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
|
||||||
|
sportEvents := sport.FetchAndUpdateSportEvents(app)
|
||||||
|
return c.JSON(200, sportEvents)
|
||||||
|
},
|
||||||
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
apis.ActivityLogger(app),
|
||||||
|
apis.RequireAdminAuth(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
_, err := e.Router.AddRoute(echo.Route{
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
Method: http.MethodDelete,
|
Method: http.MethodDelete,
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"htwkalender/service/course"
|
"htwkalender/service/course"
|
||||||
"htwkalender/service/events"
|
"htwkalender/service/events"
|
||||||
"htwkalender/service/feed"
|
"htwkalender/service/feed"
|
||||||
|
"htwkalender/service/fetch/sport"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
"htwkalender/service/functions/time"
|
"htwkalender/service/functions/time"
|
||||||
"log"
|
"log"
|
||||||
@@ -31,6 +32,12 @@ func AddSchedules(app *pocketbase.PocketBase) {
|
|||||||
feed.ClearFeeds(app.Dao(), 6, time.RealClock{})
|
feed.ClearFeeds(app.Dao(), 6, time.RealClock{})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Every sunday at 2am fetch all sport events (5 segments - minute, hour, day, month, weekday) "0 2 * * 0"
|
||||||
|
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
|
||||||
|
sport.FetchAndUpdateSportEvents(app)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
//delete all events and then fetch all events from remote this should be done every day at 4am
|
//delete all events and then fetch all events from remote this should be done every day at 4am
|
||||||
scheduler.MustAdd("fetchEvents", "0 4 * * *", func() {
|
scheduler.MustAdd("fetchEvents", "0 4 * * *", func() {
|
||||||
err := events.DeleteAllEvents(app)
|
err := events.DeleteAllEvents(app)
|
||||||
|
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
@@ -251,3 +252,15 @@ func FindAllEventsByModule(app *pocketbase.PocketBase, module model.Module) (mod
|
|||||||
|
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAllModulesByNameAndDateRange(app *pocketbase.PocketBase, name string, startDate time.Time, endDate time.Time) (model.Events, error) {
|
||||||
|
var events model.Events
|
||||||
|
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND Start >= {:startDate} AND End <= {:endDate}", dbx.Params{"name": name, "startDate": startDate, "endDate": endDate})).All(&events)
|
||||||
|
if err != nil {
|
||||||
|
print("Error while getting events from database: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
495
backend/service/fetch/sport/sportFetcher.go
Normal file
495
backend/service/fetch/sport/sportFetcher.go
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
package sport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pocketbase/pocketbase"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
|
"htwkalender/model"
|
||||||
|
"htwkalender/service/db"
|
||||||
|
"htwkalender/service/functions"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @TODO: add tests
|
||||||
|
// @TODO: make it like a cron job to fetch the sport courses once a week
|
||||||
|
func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event {
|
||||||
|
|
||||||
|
var sportCourseLinks = fetchAllAvailableSportCourses()
|
||||||
|
sportEntries := fetchHTWKSportCourses(sportCourseLinks)
|
||||||
|
events := formatEntriesToEvents(sportEntries)
|
||||||
|
|
||||||
|
var earliestDate time.Time
|
||||||
|
var latestDate time.Time
|
||||||
|
|
||||||
|
// find earliest and latest date in events
|
||||||
|
for _, event := range events {
|
||||||
|
if event.Start.Time().Before(earliestDate) {
|
||||||
|
earliestDate = event.Start.Time()
|
||||||
|
}
|
||||||
|
if event.End.Time().After(latestDate) {
|
||||||
|
latestDate = event.End.Time()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all events from database where name = Feiertage und lehrveranstaltungsfreie Tage
|
||||||
|
holidays, err := db.GetAllModulesByNameAndDateRange(app, "Feiertage und lehrveranstaltungsfreie Tage", earliestDate, latestDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all events that have same year, month and day as items in holidays
|
||||||
|
for _, holiday := range holidays {
|
||||||
|
for i, event := range events {
|
||||||
|
if event.Start.Time().Year() == holiday.Start.Time().Year() &&
|
||||||
|
event.Start.Time().Month() == holiday.Start.Time().Month() &&
|
||||||
|
event.Start.Time().Day() == holiday.Start.Time().Day() {
|
||||||
|
events = append(events[:i], events[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.DeleteAllEventsForCourse(app, "Sport", functions.GetCurrentSemesterString())
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save events to database
|
||||||
|
savedEvents, err := db.SaveEvents(events, app)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return savedEvents
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatEntriesToEvents(entries []model.SportEntry) []model.Event {
|
||||||
|
|
||||||
|
var events []model.Event
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
eventStarts, eventEnds := getWeekEvents(entry.Details.DateRange.Start, entry.Details.DateRange.End, entry.Details.Cycle)
|
||||||
|
for j := range eventStarts {
|
||||||
|
|
||||||
|
start, _ := types.ParseDateTime(eventStarts[j].In(time.UTC))
|
||||||
|
end, _ := types.ParseDateTime(eventEnds[j].In(time.UTC))
|
||||||
|
|
||||||
|
var event = model.Event{
|
||||||
|
UUID: uuid.NewSHA1(uuid.NameSpaceDNS, []byte(entry.Title+entry.ID+entry.Details.Type)).String(),
|
||||||
|
Day: toGermanWeekdayString(entry.Details.DateRange.Start.Weekday()),
|
||||||
|
Week: strconv.Itoa(23),
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
Name: entry.Title + " " + entry.Details.Type + " (" + entry.ID + ")",
|
||||||
|
EventType: entry.Details.Type,
|
||||||
|
Prof: entry.Details.CourseLead.Name,
|
||||||
|
Rooms: entry.Details.Location.Name,
|
||||||
|
Notes: entry.AdditionalNote,
|
||||||
|
BookedAt: "",
|
||||||
|
Course: "Sport",
|
||||||
|
Semester: checkSemester(entry.Details.DateRange.Start),
|
||||||
|
}
|
||||||
|
events = append(events, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDayInt(weekDay string) int {
|
||||||
|
var weekDayInt int
|
||||||
|
switch weekDay {
|
||||||
|
case "Mo":
|
||||||
|
weekDayInt = 1
|
||||||
|
case "Di":
|
||||||
|
weekDayInt = 2
|
||||||
|
case "Mi":
|
||||||
|
weekDayInt = 3
|
||||||
|
case "Do":
|
||||||
|
weekDayInt = 4
|
||||||
|
case "Fr":
|
||||||
|
weekDayInt = 5
|
||||||
|
case "Sa":
|
||||||
|
weekDayInt = 6
|
||||||
|
case "So":
|
||||||
|
weekDayInt = 0
|
||||||
|
}
|
||||||
|
return weekDayInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func toGermanWeekdayString(weekday time.Weekday) string {
|
||||||
|
switch weekday {
|
||||||
|
case time.Monday:
|
||||||
|
return "Montag"
|
||||||
|
case time.Tuesday:
|
||||||
|
return "Dienstag"
|
||||||
|
case time.Wednesday:
|
||||||
|
return "Mittwoch"
|
||||||
|
case time.Thursday:
|
||||||
|
return "Donnerstag"
|
||||||
|
case time.Friday:
|
||||||
|
return "Freitag"
|
||||||
|
case time.Saturday:
|
||||||
|
return "Samstag"
|
||||||
|
case time.Sunday:
|
||||||
|
return "Sonntag"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractStartAndEndTime(cycle string) (int, int, int, int) {
|
||||||
|
timeRegExp, _ := regexp.Compile("[0-9]{2}:[0-9]{2}")
|
||||||
|
times := timeRegExp.FindAllString(cycle, 2)
|
||||||
|
startHour, _ := strconv.Atoi(times[0][0:2])
|
||||||
|
startMinute, _ := strconv.Atoi(times[0][3:5])
|
||||||
|
|
||||||
|
endHour, _ := strconv.Atoi(times[1][0:2])
|
||||||
|
endMinute, _ := strconv.Atoi(times[1][3:5])
|
||||||
|
return startHour, startMinute, endHour, endMinute
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, []time.Time) {
|
||||||
|
|
||||||
|
var weekEvents []model.SportDayStartEnd
|
||||||
|
|
||||||
|
// split by regexp to get the cycle parts
|
||||||
|
var cycleParts []string
|
||||||
|
cycleParts = splitByCommaWithTime(cycle)
|
||||||
|
|
||||||
|
for _, cyclePart := range cycleParts {
|
||||||
|
|
||||||
|
//cut string at the first integer/number
|
||||||
|
cyclePartWithDaysOnly := cyclePart[0:strings.IndexFunc(cyclePart, func(r rune) bool { return r >= '0' && r <= '9' })]
|
||||||
|
|
||||||
|
// check if cycle has multiple days by checking if it has a plus sign
|
||||||
|
if strings.Contains(cyclePartWithDaysOnly, "+") {
|
||||||
|
// find all days in cycle part by regexp
|
||||||
|
dayRegExp, _ := regexp.Compile("[A-Z][a-z]")
|
||||||
|
days := dayRegExp.FindAllString(cyclePart, -1)
|
||||||
|
startHour, startMinute, endHour, endMinute := extractStartAndEndTime(cyclePart)
|
||||||
|
|
||||||
|
// creating a SportDayStartEnd for each day in the cycle
|
||||||
|
for _, day := range days {
|
||||||
|
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||||
|
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
||||||
|
End: time.Date(end.Year(), end.Month(), end.Day(), endHour, endMinute, 0, 0, end.Location()),
|
||||||
|
Day: time.Weekday(getDayInt(day)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if cycle has multiple days by checking if it has a minus sign
|
||||||
|
if strings.Contains(cyclePartWithDaysOnly, "-") {
|
||||||
|
// find all days in cycle part by regexp
|
||||||
|
dayRegExp, _ := regexp.Compile("[A-Z][a-z]")
|
||||||
|
days := dayRegExp.FindAllString(cyclePart, 2)
|
||||||
|
startHour, startMinute, endHour, endMinute := extractStartAndEndTime(cyclePart)
|
||||||
|
|
||||||
|
//create a int array with all days from start to end day
|
||||||
|
var daysBetween []int
|
||||||
|
for i := getDayInt(days[0]); i <= getDayInt(days[1]); i++ {
|
||||||
|
daysBetween = append(daysBetween, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating a SportDayStartEnd for each day in the cycle
|
||||||
|
for _, day := range daysBetween {
|
||||||
|
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||||
|
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
||||||
|
End: time.Date(end.Year(), end.Month(), end.Day(), endHour, endMinute, 0, 0, end.Location()),
|
||||||
|
Day: time.Weekday(day),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if cycle has only one day
|
||||||
|
if !strings.Contains(cyclePartWithDaysOnly, "-") && !strings.Contains(cyclePartWithDaysOnly, "+") {
|
||||||
|
// find all days in cycle part by regexp
|
||||||
|
dayRegExp, _ := regexp.Compile("[A-Z][a-z]")
|
||||||
|
days := dayRegExp.FindAllString(cyclePart, -1)
|
||||||
|
startHour, startMinute, endHour, endMinute := extractStartAndEndTime(cyclePart)
|
||||||
|
|
||||||
|
// creating a SportDayStartEnd for each day in the cycle
|
||||||
|
for _, day := range days {
|
||||||
|
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||||
|
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
||||||
|
End: time.Date(end.Year(), end.Month(), end.Day(), endHour, endMinute, 0, 0, end.Location()),
|
||||||
|
Day: time.Weekday(getDayInt(day)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startDatesList []time.Time
|
||||||
|
var endDatesList []time.Time
|
||||||
|
|
||||||
|
for _, weekEvent := range weekEvents {
|
||||||
|
startDates, endDates := createEventListFromStartToEndMatchingDay(weekEvent)
|
||||||
|
startDatesList = append(startDatesList, startDates...)
|
||||||
|
endDatesList = append(endDatesList, endDates...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return startDatesList, endDatesList
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEventListFromStartToEndMatchingDay(weekEvent model.SportDayStartEnd) ([]time.Time, []time.Time) {
|
||||||
|
var startDates []time.Time
|
||||||
|
var endDates []time.Time
|
||||||
|
for d := weekEvent.Start; d.Before(weekEvent.End); d = d.AddDate(0, 0, 1) {
|
||||||
|
if d.Weekday() == weekEvent.Day {
|
||||||
|
startDates = append(startDates, time.Date(d.Year(), d.Month(), d.Day(), weekEvent.Start.Hour(), weekEvent.Start.Minute(), 0, 0, d.Location()))
|
||||||
|
endDates = append(endDates, time.Date(d.Year(), d.Month(), d.Day(), weekEvent.End.Hour(), weekEvent.End.Minute(), 0, 0, d.Location()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return startDates, endDates
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitByCommaWithTime(input string) []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
// Split by comma
|
||||||
|
parts := strings.Split(input, ", ")
|
||||||
|
|
||||||
|
// Regular expression to match a day with time
|
||||||
|
regex := regexp.MustCompile(`([A-Za-z]{2,}(\+[A-Za-z]{2,})* \d{2}:\d{2}-\d{2}:\d{2})`)
|
||||||
|
|
||||||
|
// Iterate over parts and combine when necessary
|
||||||
|
var currentPart string
|
||||||
|
for _, part := range parts {
|
||||||
|
if regex.MatchString(part) {
|
||||||
|
if currentPart != "" {
|
||||||
|
currentPart += ", " + part
|
||||||
|
result = append(result, currentPart)
|
||||||
|
currentPart = ""
|
||||||
|
} else {
|
||||||
|
result = append(result, part)
|
||||||
|
}
|
||||||
|
// If the part contains a day with time, start a new currentPart
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If there's no currentPart, start a new one
|
||||||
|
if currentPart != "" {
|
||||||
|
currentPart += ", " + part
|
||||||
|
} else {
|
||||||
|
currentPart = part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last currentPart to the result
|
||||||
|
if currentPart != "" {
|
||||||
|
result = append(result, currentPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if ws or ss
|
||||||
|
func checkSemester(date time.Time) string {
|
||||||
|
if date.Month() >= 4 && date.Month() <= 9 {
|
||||||
|
return "ss"
|
||||||
|
} else {
|
||||||
|
return "ws"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the main page where all sport courses are listed and extract all links to the sport courses
|
||||||
|
func fetchAllAvailableSportCourses() []string {
|
||||||
|
var url = "https://sport.htwk-leipzig.de/sportangebote"
|
||||||
|
|
||||||
|
var doc, err = htmlRequest(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// link list of all sport courses
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
// find all links to sport courses with regex https://sport.htwk-leipzig.de/sportangebote/detail/sport/ + [0-9]{1,4}
|
||||||
|
doc.Find("a[href]").Each(func(i int, s *goquery.Selection) {
|
||||||
|
link, _ := s.Attr("href")
|
||||||
|
if strings.HasPrefix(link, "/sportangebote/detail/sport/") {
|
||||||
|
links = append(links, link)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchAllHTWKSportCourses fetches all sport courses from the given links.
|
||||||
|
// to speed up the process, it uses multithreading.
|
||||||
|
|
||||||
|
func fetchHTWKSportCourses(links []string) []model.SportEntry {
|
||||||
|
|
||||||
|
//multithreaded webpage requests to speed up the process
|
||||||
|
|
||||||
|
var maxThreads = 10
|
||||||
|
var htmlPageArray = make([]*goquery.Document, len(links))
|
||||||
|
var hostUrl = "https://sport.htwk-leipzig.de"
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(maxThreads)
|
||||||
|
for i := 0; i < maxThreads; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
for j := i; j < len(links); j += maxThreads {
|
||||||
|
doc, err := htmlRequest(hostUrl + links[j])
|
||||||
|
if err == nil {
|
||||||
|
htmlPageArray[j] = doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
var events []model.SportEntry
|
||||||
|
|
||||||
|
for _, doc := range htmlPageArray {
|
||||||
|
if doc != nil {
|
||||||
|
event, err := fetchHtwkSportCourse(doc)
|
||||||
|
if err == nil {
|
||||||
|
events = append(events, event...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func htmlRequest(url string) (*goquery.Document, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchHtwkSportCourse fetches the sport course from the given url and id.
|
||||||
|
// If the sport course does not exist, it will return an error.
|
||||||
|
// If the sport course exists, it will return the sport course.
|
||||||
|
// goquery is used to parse the html. The html structure is not very consistent, so it is hard to parse.
|
||||||
|
// May be improved in the future.
|
||||||
|
func fetchHtwkSportCourse(doc *goquery.Document) ([]model.SportEntry, error) {
|
||||||
|
var events []model.SportEntry
|
||||||
|
germanTime, _ := time.LoadLocation("Europe/Berlin")
|
||||||
|
|
||||||
|
if doc.Find("h1").Text() == "Aktuelle Sportangebote" {
|
||||||
|
return nil, errors.New("not a sport course page")
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Find(".eventHead").Each(func(i int, s *goquery.Selection) {
|
||||||
|
var event model.SportEntry
|
||||||
|
var details model.EventDetails
|
||||||
|
|
||||||
|
fullTitle := strings.TrimSpace(s.Find("h3").Text())
|
||||||
|
titleParts := strings.Split(fullTitle, "-")
|
||||||
|
if len(titleParts) > 0 {
|
||||||
|
event.Title = strings.TrimSpace(titleParts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(titleParts) > 2 {
|
||||||
|
details.Type = strings.TrimSpace(titleParts[len(titleParts)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
event.ID = parseEventID(fullTitle)
|
||||||
|
|
||||||
|
s.NextFiltered("table.eventDetails").Find("tr").Each(func(i int, s *goquery.Selection) {
|
||||||
|
key := strings.TrimSpace(s.Find("td").First().Text())
|
||||||
|
value := strings.TrimSpace(s.Find("td").Last().Text())
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "Zeitraum":
|
||||||
|
dates := strings.Split(value, "-")
|
||||||
|
if len(dates) == 2 {
|
||||||
|
startDate, _ := time.ParseInLocation("02.01.2006", strings.TrimSpace(dates[0]), germanTime)
|
||||||
|
endDate, _ := time.ParseInLocation("02.01.2006", strings.TrimSpace(dates[1]), germanTime)
|
||||||
|
details.DateRange = model.DateRange{Start: startDate, End: endDate}
|
||||||
|
}
|
||||||
|
case "Zyklus":
|
||||||
|
details.Cycle = value
|
||||||
|
case "Geschlecht":
|
||||||
|
details.Gender = value
|
||||||
|
case "Leiter":
|
||||||
|
leaderName := strings.TrimSpace(s.Find("td a").Text())
|
||||||
|
leadersSlice := strings.Split(leaderName, "\n")
|
||||||
|
for i, leader := range leadersSlice {
|
||||||
|
leadersSlice[i] = strings.TrimSpace(leader)
|
||||||
|
}
|
||||||
|
formattedLeaders := strings.Join(leadersSlice, ", ")
|
||||||
|
leaderLink, _ := s.Find("td a").Attr("href")
|
||||||
|
details.CourseLead = model.CourseLead{Name: formattedLeaders, Link: leaderLink}
|
||||||
|
case "Ort":
|
||||||
|
locationDetails := strings.Split(value, "(")
|
||||||
|
if len(locationDetails) == 2 {
|
||||||
|
details.Location = model.Location{
|
||||||
|
Name: strings.TrimSpace(locationDetails[0]),
|
||||||
|
Address: strings.TrimRight(strings.TrimSpace(locationDetails[1]), ")"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Teilnehmer":
|
||||||
|
parts := strings.Split(value, "/")
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
bookings, _ := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||||
|
totalPlaces, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||||
|
waitList, _ := strconv.Atoi(strings.TrimSpace(parts[2]))
|
||||||
|
details.Participants = model.Participants{Bookings: bookings, TotalPlaces: totalPlaces, WaitList: waitList}
|
||||||
|
}
|
||||||
|
case "Kosten":
|
||||||
|
details.Cost = value // makes no sense since you need to be logged in to see the price
|
||||||
|
case "Hinweis":
|
||||||
|
var allNotes []string
|
||||||
|
|
||||||
|
s.Find("td").Last().Contents().Each(func(i int, s *goquery.Selection) {
|
||||||
|
if s.Is("h4.eventAdvice") || goquery.NodeName(s) == "#text" {
|
||||||
|
note := strings.TrimSpace(s.Text())
|
||||||
|
if note != "" {
|
||||||
|
allNotes = append(allNotes, note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
event.AdditionalNote = strings.Join(allNotes, " ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
event.Details = details
|
||||||
|
events = append(events, event)
|
||||||
|
})
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEventID from fulltitle
|
||||||
|
// the event id is a number in the fulltitle thats not a time like HH:MM and shoudl be found after Nr. or Nr:
|
||||||
|
func parseEventID(fulltitle string) string {
|
||||||
|
var eventID string
|
||||||
|
var numberRegExp = regexp.MustCompile("[0-9]{1,4}")
|
||||||
|
var fulltitleParts = strings.Split(fulltitle, " ")
|
||||||
|
for i, part := range fulltitleParts {
|
||||||
|
if part == "Nr." || part == "Nr:" {
|
||||||
|
eventID = fulltitleParts[i+1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventID == "" {
|
||||||
|
eventID = numberRegExp.FindString(fulltitle)
|
||||||
|
}
|
||||||
|
return eventID
|
||||||
|
|
||||||
|
}
|
40
backend/service/fetch/sport/sportFetcher_test.go
Normal file
40
backend/service/fetch/sport/sportFetcher_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package sport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_splitByCommaWithTime(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
input string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"one string", args{"one"}, []string{"one"}},
|
||||||
|
{"two strings", args{"one,two"}, []string{"one,two"}},
|
||||||
|
{"three strings", args{"one,two,three"}, []string{"one,two,three"}},
|
||||||
|
// e.g. "Mo 18:00-20:00, Di 18:00-20:00" -> ["Mo 18:00-20:00", "Di 18:00-20:00"]
|
||||||
|
// e.g. "Mo 18:00-20:00, Di 18:00-20:00, Mi 18:00-20:00" -> ["Mo 18:00-20:00", "Di 18:00-20:00", "Mi 18:00-20:00"]
|
||||||
|
// e.g. "Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00" -> ["Mo, Mi, Fr 18:00-20:00", "Sa 20:00-21:00"]
|
||||||
|
// e.g. "Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00" -> ["Mo, Mi, Fr 18:00-20:00", "Sa 20:00-21:00", "So 20:00-21:00"]
|
||||||
|
// e.g. "Mo+Mi+Fr 18:00-20:00, Sa 20:00-21:00" -> ["Mo+Mi+Fr 18:00-20:00", "Sa 20:00-21:00"]
|
||||||
|
// e.g. "Mo+Mi 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00" -> ["Mo+Mi 18:00-20:00", "Sa 20:00-21:00", "So 20:00-21:00"]
|
||||||
|
{"Mo 18:00-20:00, Di 18:00-20:00", args{"Mo 18:00-20:00, Di 18:00-20:00"}, []string{"Mo 18:00-20:00", "Di 18:00-20:00"}},
|
||||||
|
{"Mo 18:00-20:00, Di 18:00-20:00, Mi 18:00-20:00", args{"Mo 18:00-20:00, Di 18:00-20:00, Mi 18:00-20:00"}, []string{"Mo 18:00-20:00", "Di 18:00-20:00", "Mi 18:00-20:00"}},
|
||||||
|
{"Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00", args{"Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00"}, []string{"Mo, Mi, Fr 18:00-20:00", "Sa 20:00-21:00"}},
|
||||||
|
{"Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00", args{"Mo, Mi, Fr 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00"}, []string{"Mo, Mi, Fr 18:00-20:00", "Sa 20:00-21:00", "So 20:00-21:00"}},
|
||||||
|
{"Mo+Mi+Fr 18:00-20:00, Sa 20:00-21:00", args{"Mo+Mi+Fr 18:00-20:00, Sa 20:00-21:00"}, []string{"Mo+Mi+Fr 18:00-20:00", "Sa 20:00-21:00"}},
|
||||||
|
{"Mo+Mi 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00", args{"Mo+Mi 18:00-20:00, Sa 20:00-21:00, So 20:00-21:00"}, []string{"Mo+Mi 18:00-20:00", "Sa 20:00-21:00", "So 20:00-21:00"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := splitByCommaWithTime(tt.args.input); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("splitByCommaWithTime() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
14
backend/service/functions/semester.go
Normal file
14
backend/service/functions/semester.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// GetCurrentSemesterString returns the current semester as string
|
||||||
|
// if current month is between 10 and 03 -> winter semester "ws"
|
||||||
|
func GetCurrentSemesterString() string {
|
||||||
|
|
||||||
|
if time.Now().Month() >= 10 || time.Now().Month() <= 3 {
|
||||||
|
return "ws"
|
||||||
|
} else {
|
||||||
|
return "ss"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,12 @@
|
|||||||
package room
|
package room
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v5"
|
"htwkalender/model"
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"github.com/pocketbase/pocketbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRooms(c echo.Context, app *pocketbase.PocketBase) error {
|
func GetRooms(c echo.Context, app *pocketbase.PocketBase) error {
|
||||||
@@ -14,10 +16,19 @@ func GetRooms(c echo.Context, app *pocketbase.PocketBase) error {
|
|||||||
|
|
||||||
func GetRoomScheduleForDay(c echo.Context, app *pocketbase.PocketBase, room string, date string) error {
|
func GetRoomScheduleForDay(c echo.Context, app *pocketbase.PocketBase, room string, date string) error {
|
||||||
events := db.GetRoomScheduleForDay(app, room, date)
|
events := db.GetRoomScheduleForDay(app, room, date)
|
||||||
return c.JSON(http.StatusOK, events)
|
return c.JSON(http.StatusOK, anonymizeRooms(events))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRoomSchedule(c echo.Context, app *pocketbase.PocketBase, room string, from string, to string) error {
|
func GetRoomSchedule(c echo.Context, app *pocketbase.PocketBase, room string, from string, to string) error {
|
||||||
events := db.GetRoomSchedule(app, room, from, to)
|
events := db.GetRoomSchedule(app, room, from, to)
|
||||||
return c.JSON(http.StatusOK, events)
|
return c.JSON(http.StatusOK, anonymizeRooms(events))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform the events to anonymized events throwing away all unnecessary information
|
||||||
|
func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO {
|
||||||
|
var anonymizedEvents = []model.AnonymizedEventDTO{}
|
||||||
|
for _, event := range events {
|
||||||
|
anonymizedEvents = append(anonymizedEvents, event.AnonymizeEvent())
|
||||||
|
}
|
||||||
|
return anonymizedEvents
|
||||||
}
|
}
|
||||||
|
125
backend/service/room/roomService_test.go
Normal file
125
backend/service/room/roomService_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package room
|
||||||
|
|
||||||
|
import (
|
||||||
|
"htwkalender/model"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_anonymizeRooms(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
events []model.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []model.AnonymizedEventDTO
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "anonymize single event",
|
||||||
|
args: args{
|
||||||
|
events: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Secret",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Secret",
|
||||||
|
Rooms: "Room",
|
||||||
|
Notes: "Secret",
|
||||||
|
BookedAt: "Secret",
|
||||||
|
Course: "42INM-3",
|
||||||
|
Semester: "ws",
|
||||||
|
Compulsory: "p",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []model.AnonymizedEventDTO{
|
||||||
|
{
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Rooms: "Room",
|
||||||
|
Free: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anonymize empty list",
|
||||||
|
args: args{
|
||||||
|
events: []model.Event{},
|
||||||
|
},
|
||||||
|
want: []model.AnonymizedEventDTO{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anonymize multiple events",
|
||||||
|
args: args{
|
||||||
|
events: []model.Event{
|
||||||
|
{
|
||||||
|
UUID: "testUUID1",
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "51",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Incognito",
|
||||||
|
EventType: "V",
|
||||||
|
Prof: "Prof. Dr. Incognito",
|
||||||
|
Rooms: "Room",
|
||||||
|
Notes: "Incognito",
|
||||||
|
BookedAt: "Incognito",
|
||||||
|
Course: "69INM-2",
|
||||||
|
Semester: "sose",
|
||||||
|
Compulsory: "p",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UUID: "testUUID2",
|
||||||
|
Day: "Dienstag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Name: "Private",
|
||||||
|
EventType: "S",
|
||||||
|
Prof: "Prof.In. Dr.-Ing. Private",
|
||||||
|
Rooms: "Room",
|
||||||
|
Notes: "Private",
|
||||||
|
BookedAt: "Private",
|
||||||
|
Course: "42MIM-3",
|
||||||
|
Semester: "ws",
|
||||||
|
Compulsory: "w",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []model.AnonymizedEventDTO{
|
||||||
|
{
|
||||||
|
Day: "Montag",
|
||||||
|
Week: "51",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Rooms: "Room",
|
||||||
|
Free: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Day: "Dienstag",
|
||||||
|
Week: "52",
|
||||||
|
Start: types.DateTime{},
|
||||||
|
End: types.DateTime{},
|
||||||
|
Rooms: "Room",
|
||||||
|
Free: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := anonymizeRooms(tt.args.events); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("anonymizeRooms() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -34,7 +34,7 @@
|
|||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.5",
|
"vite": "^4.4.12",
|
||||||
"vue-tsc": "^1.8.5"
|
"vue-tsc": "^1.8.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3588,9 +3588,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.4.9",
|
"version": "4.4.12",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.12.tgz",
|
||||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
"integrity": "sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
|
@@ -37,7 +37,7 @@
|
|||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.5",
|
"vite": "^4.4.12",
|
||||||
"vue-tsc": "^1.8.5"
|
"vue-tsc": "^1.8.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Event } from "../model/event.ts";
|
import { AnonymizedEventDTO } from "../model/event.ts";
|
||||||
|
|
||||||
export async function fetchRoom(): Promise<string[]> {
|
export async function fetchRoom(): Promise<string[]> {
|
||||||
const rooms: string[] = [];
|
const rooms: string[] = [];
|
||||||
@@ -16,8 +16,8 @@ export async function fetchEventsByRoomAndDuration(
|
|||||||
room: string,
|
room: string,
|
||||||
from_date: string,
|
from_date: string,
|
||||||
to_date: string,
|
to_date: string,
|
||||||
): Promise<Event[]> {
|
): Promise<AnonymizedEventDTO[]> {
|
||||||
const events: Event[] = [];
|
const events: AnonymizedEventDTO[] = [];
|
||||||
await fetch(
|
await fetch(
|
||||||
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,
|
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,
|
||||||
)
|
)
|
||||||
@@ -27,7 +27,7 @@ export async function fetchEventsByRoomAndDuration(
|
|||||||
})
|
})
|
||||||
.then((eventsResponse) => {
|
.then((eventsResponse) => {
|
||||||
console.log("Response:", eventsResponse);
|
console.log("Response:", eventsResponse);
|
||||||
eventsResponse.forEach((event: Event) => events.push(event));
|
eventsResponse.forEach((event: AnonymizedEventDTO) => events.push(event));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Error fetching events: ", error);
|
console.log("Error fetching events: ", error);
|
||||||
|
@@ -52,7 +52,7 @@ async function getOccupation() {
|
|||||||
id: index,
|
id: index,
|
||||||
start: event.start.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
start: event.start.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
||||||
end: event.end.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
end: event.end.replace(/\s\+\d{4}\s\w+$/, "").replace(" ", "T"),
|
||||||
showFree: event.name.toLowerCase().includes("zur freien verfügung"),
|
showFree: event.free
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -14,3 +14,14 @@ export class Event {
|
|||||||
public week: string,
|
public week: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AnonymizedEventDTO {
|
||||||
|
constructor(
|
||||||
|
public day: string,
|
||||||
|
public week: string,
|
||||||
|
public start: string,
|
||||||
|
public end: string,
|
||||||
|
public rooms: string,
|
||||||
|
public free: boolean
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import Faq from "../components/FaqPage.vue";
|
|
||||||
import AdditionalModules from "../view/AdditionalModules.vue";
|
const Faq = () => import("../components/FaqPage.vue");
|
||||||
import CalendarLink from "../components/CalendarLink.vue";
|
const AdditionalModules = () => import("../view/AdditionalModules.vue");
|
||||||
import Imprint from "../view/ImprintPage.vue";
|
const CalendarLink = () => import("../components/CalendarLink.vue");
|
||||||
import PrivacyPolicy from "../view/PrivacyPolicy.vue";
|
const Imprint = () => import("../view/ImprintPage.vue");
|
||||||
import RenameModules from "../components/RenameModules.vue";
|
const PrivacyPolicy = () => import("../view/PrivacyPolicy.vue");
|
||||||
import RoomFinder from "../view/RoomFinder.vue";
|
const RenameModules = () => import("../components/RenameModules.vue");
|
||||||
import EditCalendarView from "../view/EditCalendarView.vue";
|
const RoomFinder = () => import("../view/RoomFinder.vue");
|
||||||
import EditAdditionalModules from "../components/editCalendar/EditAdditionalModules.vue";
|
const EditCalendarView = () => import("../view/EditCalendarView.vue");
|
||||||
import EditModules from "../components/editCalendar/EditModules.vue";
|
const EditAdditionalModules = () => import("../components/editCalendar/EditAdditionalModules.vue");
|
||||||
import CourseSelection from "../view/CourseSelection.vue";
|
const EditModules = () => import("../components/editCalendar/EditModules.vue");
|
||||||
|
const CourseSelection = () => import("../view/CourseSelection.vue");
|
||||||
|
|
||||||
import i18n from "../i18n";
|
import i18n from "../i18n";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
Reference in New Issue
Block a user