mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 17:48:51 +02:00
add first ical implementation
This commit is contained in:
40
addRoute.go
40
addRoute.go
@ -6,6 +6,8 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/apis"
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"htwk-planner/service"
|
"htwk-planner/service"
|
||||||
|
"htwk-planner/service/fetch"
|
||||||
|
"htwk-planner/service/ical"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@ -22,7 +24,7 @@ func addRoutes(app *pocketbase.PocketBase) {
|
|||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/api/fetchPlans",
|
Path: "/api/fetchPlans",
|
||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
return service.FetchHTWK(c, app)
|
return fetch.FetchHTWK(c, app)
|
||||||
},
|
},
|
||||||
Middlewares: []echo.MiddlewareFunc{
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
apis.ActivityLogger(app),
|
apis.ActivityLogger(app),
|
||||||
@ -39,7 +41,7 @@ func addRoutes(app *pocketbase.PocketBase) {
|
|||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/api/fetchGroups",
|
Path: "/api/fetchGroups",
|
||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
return service.FetchSeminarGroups(c, app)
|
return fetch.SeminarGroups(c, app)
|
||||||
},
|
},
|
||||||
Middlewares: []echo.MiddlewareFunc{
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
apis.ActivityLogger(app),
|
apis.ActivityLogger(app),
|
||||||
@ -68,4 +70,38 @@ 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/feedURL",
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
return ical.FeedURL(c, app)
|
||||||
|
},
|
||||||
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
apis.ActivityLogger(app),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/api/feed",
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
return ical.Feed(c, app)
|
||||||
|
},
|
||||||
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
apis.ActivityLogger(app),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module htwk-planner
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
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
|
||||||
github.com/pocketbase/pocketbase v0.17.5
|
github.com/pocketbase/pocketbase v0.17.5
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.14.0
|
||||||
|
7
go.sum
7
go.sum
@ -81,6 +81,7 @@ github.com/ganigeorgiev/fexpr v0.3.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO
|
|||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
@ -125,6 +126,7 @@ github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1
|
|||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@ -133,12 +135,16 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
|
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||||
|
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0 h1:p+k2RozdR141dIkAbOuZafkZjrcjT/YvwYYH7qCSG+c=
|
||||||
|
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0/go.mod h1:YHaw6sOIeFRob8Y9q/blEAMfVcLpeE9+vdhrwyEMxoI=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4=
|
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4=
|
||||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
|
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
@ -146,6 +152,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
|
@ -7,10 +7,10 @@ type SeminarGroup struct {
|
|||||||
Course string
|
Course string
|
||||||
Faculty string
|
Faculty string
|
||||||
FacultyId string
|
FacultyId string
|
||||||
Events []Events
|
Events []Event
|
||||||
}
|
}
|
||||||
|
|
||||||
type Events struct {
|
type Event struct {
|
||||||
Day string
|
Day string
|
||||||
Week string
|
Week string
|
||||||
Start string
|
Start string
|
||||||
@ -21,4 +21,5 @@ type Events struct {
|
|||||||
Rooms string
|
Rooms string
|
||||||
Notes string
|
Notes string
|
||||||
BookedAt string
|
BookedAt string
|
||||||
|
Semester string
|
||||||
}
|
}
|
||||||
|
@ -281,6 +281,18 @@
|
|||||||
"max": null,
|
"max": null,
|
||||||
"pattern": ""
|
"pattern": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vlbpm9fz",
|
||||||
|
"name": "semester",
|
||||||
|
"type": "text",
|
||||||
|
"system": false,
|
||||||
|
"required": false,
|
||||||
|
"options": {
|
||||||
|
"min": null,
|
||||||
|
"max": null,
|
||||||
|
"pattern": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"indexes": [
|
"indexes": [
|
||||||
|
47
service/date/dateFormat.go
Normal file
47
service/date/dateFormat.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package date
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func GetDateFromWeekNumber(year int, weekNumber int, dayName string) (time.Time, error) {
|
||||||
|
// Create a time.Date for the first day of the year
|
||||||
|
firstDayOfYear := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
// Calculate the number of days to add to reach the desired week
|
||||||
|
daysToAdd := time.Duration((weekNumber-1)*7) * 24 * time.Hour
|
||||||
|
|
||||||
|
// Find the starting day of the week (e.g., Monday)
|
||||||
|
startingDayOfWeek := firstDayOfYear
|
||||||
|
|
||||||
|
// check if the first day of the year is friday or saturday or sunday
|
||||||
|
|
||||||
|
if startingDayOfWeek.Weekday() == time.Friday || startingDayOfWeek.Weekday() == time.Saturday || startingDayOfWeek.Weekday() == time.Sunday {
|
||||||
|
for startingDayOfWeek.Weekday() != time.Monday {
|
||||||
|
startingDayOfWeek = startingDayOfWeek.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for startingDayOfWeek.Weekday() != time.Monday {
|
||||||
|
startingDayOfWeek = startingDayOfWeek.Add(-24 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the desired date by adding daysToAdd and adjusting for the day name
|
||||||
|
desiredDate := startingDayOfWeek.Add(daysToAdd)
|
||||||
|
|
||||||
|
// Find the day of the week
|
||||||
|
dayOfWeek := map[string]time.Weekday{
|
||||||
|
"Montag": time.Monday,
|
||||||
|
"Dienstag": time.Tuesday,
|
||||||
|
"Mittwoch": time.Wednesday,
|
||||||
|
"Donnerstag": time.Thursday,
|
||||||
|
"Freitag": time.Friday,
|
||||||
|
"Samstag": time.Saturday,
|
||||||
|
"Sonntag": time.Sunday,
|
||||||
|
}[dayName]
|
||||||
|
|
||||||
|
// Adjust to the desired day of the week
|
||||||
|
for desiredDate.Weekday() != dayOfWeek {
|
||||||
|
desiredDate = desiredDate.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
return desiredDate, nil
|
||||||
|
}
|
64
service/date/dateFormat_test.go
Normal file
64
service/date/dateFormat_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getDateFromWeekNumber(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
year int
|
||||||
|
weekNumber int
|
||||||
|
dayName string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want time.Time
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test 1",
|
||||||
|
args: args{
|
||||||
|
year: 2021,
|
||||||
|
weekNumber: 1,
|
||||||
|
dayName: "Montag",
|
||||||
|
},
|
||||||
|
want: time.Date(2021, 1, 4, 0, 0, 0, 0, time.UTC),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 2",
|
||||||
|
args: args{
|
||||||
|
year: 2023,
|
||||||
|
weekNumber: 57,
|
||||||
|
dayName: "Montag",
|
||||||
|
},
|
||||||
|
want: time.Date(2024, 1, 29, 0, 0, 0, 0, time.UTC),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 3",
|
||||||
|
args: args{
|
||||||
|
year: 2023,
|
||||||
|
weekNumber: 1,
|
||||||
|
dayName: "Montag",
|
||||||
|
},
|
||||||
|
want: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := GetDateFromWeekNumber(tt.args.year, tt.args.weekNumber, tt.args.dayName)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("getDateFromWeekNumber() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getDateFromWeekNumber() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/jordic/goics"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"htwk-planner/model"
|
"htwk-planner/model"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) error {
|
func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) error {
|
||||||
@ -24,6 +27,7 @@ func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection
|
|||||||
record.Set("Notes", event.Notes)
|
record.Set("Notes", event.Notes)
|
||||||
record.Set("BookedAt", event.BookedAt)
|
record.Set("BookedAt", event.BookedAt)
|
||||||
record.Set("course", seminarGroup.Course)
|
record.Set("course", seminarGroup.Course)
|
||||||
|
record.Set("semester", event.Semester)
|
||||||
err = app.Dao().SaveRecord(record)
|
err = app.Dao().SaveRecord(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error while saving record: ", err.Error())
|
println("Error while saving record: ", err.Error())
|
||||||
@ -42,7 +46,7 @@ func contains(s []string, e string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRooms function to get all rooms from database that are stored as a string in the Events struct
|
// GetRooms function to get all rooms from database that are stored as a string in the Event struct
|
||||||
func GetRooms(app *pocketbase.PocketBase) []string {
|
func GetRooms(app *pocketbase.PocketBase) []string {
|
||||||
|
|
||||||
var events []struct {
|
var events []struct {
|
||||||
@ -70,3 +74,39 @@ func GetRooms(app *pocketbase.PocketBase) []string {
|
|||||||
}
|
}
|
||||||
return roomArray
|
return roomArray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Events []*model.Event
|
||||||
|
|
||||||
|
// EmitICal implements the interface for goics
|
||||||
|
func (e Events) EmitICal() goics.Componenter {
|
||||||
|
c := goics.NewComponent()
|
||||||
|
c.SetType("VCALENDAR")
|
||||||
|
c.AddProperty("CALSCAL", "GREGORIAN")
|
||||||
|
for _, event := range e {
|
||||||
|
s := goics.NewComponent()
|
||||||
|
s.SetType("VEVENT")
|
||||||
|
timeEnd, _ := time.Parse("2024-01-07 07:00:00 +0000 UTC", event.End)
|
||||||
|
timeStart, _ := time.Parse("2024-01-07 07:00:00 +0000 UTC", event.Start)
|
||||||
|
k, v := goics.FormatDateTimeField("DTEND", timeEnd)
|
||||||
|
s.AddProperty(k, v)
|
||||||
|
k, v = goics.FormatDateTimeField("DTSTART", timeStart)
|
||||||
|
s.AddProperty(k, v)
|
||||||
|
s.AddProperty("SUMMARY", event.Name)
|
||||||
|
s.AddProperty("DESCRIPTION", event.Notes)
|
||||||
|
s.AddProperty("LOCATION", event.Rooms)
|
||||||
|
c.AddComponent(s)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlanForCourseAndSemester gets all events for specific course and semester
|
||||||
|
func GetPlanForCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) Events {
|
||||||
|
var events Events
|
||||||
|
// get all events from event records in the events collection
|
||||||
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).All(&events)
|
||||||
|
if err != nil {
|
||||||
|
print("Error while getting events from database: ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package service
|
package fetch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@ -40,7 +40,7 @@ func getSeminarHTML() (string, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
func SeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
||||||
|
|
||||||
result, _ := getSeminarHTML()
|
result, _ := getSeminarHTML()
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
|||||||
|
|
||||||
dbError = db.SaveGroups(groups, collection, app)
|
dbError = db.SaveGroups(groups, collection, app)
|
||||||
if dbError != nil {
|
if dbError != nil {
|
||||||
return apis.NewApiError(400, "Could not save Events into database", dbError)
|
return apis.NewApiError(400, "Could not save Event into database", dbError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, groups)
|
return c.JSON(http.StatusOK, groups)
|
||||||
@ -64,7 +64,6 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
|||||||
func parseSeminarGroups(result string) []model.SeminarGroup {
|
func parseSeminarGroups(result string) []model.SeminarGroup {
|
||||||
|
|
||||||
var studium model.Studium
|
var studium model.Studium
|
||||||
|
|
||||||
err := xml.Unmarshal([]byte(result), &studium)
|
err := xml.Unmarshal([]byte(result), &studium)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
@ -1,4 +1,4 @@
|
|||||||
package service
|
package fetch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -8,11 +8,14 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"htwk-planner/model"
|
"htwk-planner/model"
|
||||||
|
"htwk-planner/service/date"
|
||||||
"htwk-planner/service/db"
|
"htwk-planner/service/db"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FetchHTWK(c echo.Context, app *pocketbase.PocketBase) error {
|
func FetchHTWK(c echo.Context, app *pocketbase.PocketBase) error {
|
||||||
@ -44,7 +47,7 @@ func FetchHTWK(c echo.Context, app *pocketbase.PocketBase) error {
|
|||||||
|
|
||||||
dbError = db.SaveEvents(seminarGroups, collection, app)
|
dbError = db.SaveEvents(seminarGroups, collection, app)
|
||||||
if dbError != nil {
|
if dbError != nil {
|
||||||
return apis.NewApiError(400, "Could not save Events into database", dbError)
|
return apis.NewApiError(400, "Could not save Event into database", dbError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, seminarGroups)
|
return c.JSON(http.StatusOK, seminarGroups)
|
||||||
@ -73,24 +76,87 @@ func parseSeminarGroup(result string) model.SeminarGroup {
|
|||||||
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
|
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
|
||||||
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
||||||
events := splitEventsBySingleWeek(splitEventsByWeekVal)
|
events := splitEventsBySingleWeek(splitEventsByWeekVal)
|
||||||
|
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
|
||||||
|
semester, year := extractSemesterAndYear(semesterString)
|
||||||
|
events = convertWeeksToDates(events, semester, year)
|
||||||
var seminarGroup = model.SeminarGroup{
|
var seminarGroup = model.SeminarGroup{
|
||||||
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
|
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
|
||||||
Course: findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data,
|
Course: findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data,
|
||||||
Events: events,
|
Events: events,
|
||||||
}
|
}
|
||||||
|
|
||||||
return seminarGroup
|
return seminarGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func toEvents(tables [][]*html.Node, days []string) []model.Events {
|
func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event {
|
||||||
var events []model.Events
|
var newEvents []model.Event
|
||||||
|
eventYear, _ := strconv.Atoi(year)
|
||||||
|
|
||||||
|
// for each event we need to calculate the start and end date based on the week and the year
|
||||||
|
for _, event := range events {
|
||||||
|
eventWeek, _ := strconv.Atoi(event.Week)
|
||||||
|
eventDay, _ := date.GetDateFromWeekNumber(eventYear, eventWeek, event.Day)
|
||||||
|
start := addTimeToDate(eventDay, event.Start)
|
||||||
|
end := addTimeToDate(eventDay, event.End)
|
||||||
|
newEvent := event
|
||||||
|
newEvent.Start = start.String()
|
||||||
|
newEvent.End = end.String()
|
||||||
|
newEvent.Semester = semester
|
||||||
|
newEvents = append(newEvents, newEvent)
|
||||||
|
}
|
||||||
|
return newEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTimeToDate(date time.Time, timeString string) time.Time {
|
||||||
|
//convert time string to time
|
||||||
|
timeParts := strings.Split(timeString, ":")
|
||||||
|
hour, _ := strconv.Atoi(timeParts[0])
|
||||||
|
minute, _ := strconv.Atoi(timeParts[1])
|
||||||
|
|
||||||
|
return time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractSemesterAndYear(semesterString string) (string, string) {
|
||||||
|
winterPattern := "Wintersemester"
|
||||||
|
summerPattern := "Sommersemester"
|
||||||
|
|
||||||
|
winterMatch := strings.Contains(semesterString, winterPattern)
|
||||||
|
summerMatch := strings.Contains(semesterString, summerPattern)
|
||||||
|
|
||||||
|
semester := ""
|
||||||
|
semesterShortcut := ""
|
||||||
|
|
||||||
|
if winterMatch {
|
||||||
|
semester = "Wintersemester"
|
||||||
|
semesterShortcut = "ws"
|
||||||
|
} else if summerMatch {
|
||||||
|
semester = "Sommersemester"
|
||||||
|
semesterShortcut = "ss"
|
||||||
|
} else {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
yearPattern := `\d{4}`
|
||||||
|
combinedPattern := semester + `\s` + yearPattern
|
||||||
|
re := regexp.MustCompile(combinedPattern)
|
||||||
|
match := re.FindString(semesterString)
|
||||||
|
year := ""
|
||||||
|
|
||||||
|
if match != "" {
|
||||||
|
reYear := regexp.MustCompile(yearPattern)
|
||||||
|
year = reYear.FindString(match)
|
||||||
|
}
|
||||||
|
return semesterShortcut, year
|
||||||
|
}
|
||||||
|
|
||||||
|
func toEvents(tables [][]*html.Node, days []string) []model.Event {
|
||||||
|
var events []model.Event
|
||||||
|
|
||||||
for table := range tables {
|
for table := range tables {
|
||||||
for row := range tables[table] {
|
for row := range tables[table] {
|
||||||
|
|
||||||
tableData := findTableData(tables[table][row])
|
tableData := findTableData(tables[table][row])
|
||||||
if len(tableData) > 0 {
|
if len(tableData) > 0 {
|
||||||
events = append(events, model.Events{
|
events = append(events, model.Event{
|
||||||
Day: days[table],
|
Day: days[table],
|
||||||
Week: getTextContent(tableData[0]),
|
Week: getTextContent(tableData[0]),
|
||||||
Start: getTextContent(tableData[1]),
|
Start: getTextContent(tableData[1]),
|
||||||
@ -110,8 +176,8 @@ func toEvents(tables [][]*html.Node, days []string) []model.Events {
|
|||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitEventsByWeek(events []model.Events) []model.Events {
|
func splitEventsByWeek(events []model.Event) []model.Event {
|
||||||
var newEvents []model.Events
|
var newEvents []model.Event
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
weeks := strings.Split(event.Week, ",")
|
weeks := strings.Split(event.Week, ",")
|
||||||
@ -124,8 +190,8 @@ func splitEventsByWeek(events []model.Events) []model.Events {
|
|||||||
return newEvents
|
return newEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitEventsBySingleWeek(events []model.Events) []model.Events {
|
func splitEventsBySingleWeek(events []model.Event) []model.Event {
|
||||||
var newEvents []model.Events
|
var newEvents []model.Event
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
if strings.Contains(event.Week, "-") {
|
if strings.Contains(event.Week, "-") {
|
52
service/fetch/fetchService_test.go
Normal file
52
service/fetch/fetchService_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package fetch
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_extractSemesterAndYear(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
semesterString string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
want1 string
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
{
|
||||||
|
name: "Test 1",
|
||||||
|
args: args{
|
||||||
|
semesterString: "Wintersemester 2023/24 (Planungszeitraum 01.09.2023 bis 03.03.2024)",
|
||||||
|
},
|
||||||
|
want: "ws",
|
||||||
|
want1: "2023",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 2",
|
||||||
|
args: args{
|
||||||
|
semesterString: "Sommersemester 2023 (Planungszeitraum 06.03. bis 31.08.2023)",
|
||||||
|
},
|
||||||
|
want: "ss",
|
||||||
|
want1: "2023",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test 3",
|
||||||
|
args: args{
|
||||||
|
semesterString: "Sommersemester 2010 (Planungszeitraum 06.03. bis 31.08.2023)",
|
||||||
|
},
|
||||||
|
want: "ss",
|
||||||
|
want1: "2010",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1 := extractSemesterAndYear(tt.args.semesterString)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("extractSemesterAndYear() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
if got1 != tt.want1 {
|
||||||
|
t.Errorf("extractSemesterAndYear() got1 = %v, want %v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
79
service/ical/ical.go
Normal file
79
service/ical/ical.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package ical
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jordic/goics"
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"github.com/pocketbase/pocketbase"
|
||||||
|
"htwk-planner/service/db"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const expirationTime = 5 * time.Minute
|
||||||
|
|
||||||
|
var cache = make(map[string]*FeedModel)
|
||||||
|
|
||||||
|
func FeedURL(c echo.Context, app *pocketbase.PocketBase) error {
|
||||||
|
token := randomToken(20)
|
||||||
|
_, err := createFeedForToken(app, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, fmt.Sprintf("FeedToken: %s", token))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Feed(c echo.Context, app *pocketbase.PocketBase) error {
|
||||||
|
|
||||||
|
var result string
|
||||||
|
var responseWriter http.ResponseWriter
|
||||||
|
token := c.Request().Header.Get("token")
|
||||||
|
log.Print("iCal feed Token: " + token)
|
||||||
|
feed, ok := cache[token]
|
||||||
|
if !ok || feed == nil {
|
||||||
|
return c.JSON(http.StatusNotFound, "No FeedModel for this Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
result = feed.Content
|
||||||
|
if feed.ExpiresAt.Before(time.Now()) {
|
||||||
|
newFeed, err := createFeedForToken(app, token)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
result = newFeed.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
responseWriter.Header().Set("Content-type", "text/calendar")
|
||||||
|
responseWriter.Header().Set("charset", "utf-8")
|
||||||
|
responseWriter.Header().Set("Content-Disposition", "inline")
|
||||||
|
responseWriter.Header().Set("filename", "calendar.ics")
|
||||||
|
writeSuccess(result, responseWriter)
|
||||||
|
c.Response().Writer = responseWriter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFeedForToken(app *pocketbase.PocketBase, token string) (*FeedModel, error) {
|
||||||
|
res := db.GetPlanForCourseAndSemester(app, "22INM", "ws")
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
goics.NewICalEncode(&b).Encode(res)
|
||||||
|
feed := &FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)}
|
||||||
|
cache[token] = feed
|
||||||
|
return feed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomToken(len int) string {
|
||||||
|
b := make([]byte, len)
|
||||||
|
read, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSuccess(message string, w http.ResponseWriter) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(message))
|
||||||
|
}
|
24
service/ical/icalModel.go
Normal file
24
service/ical/icalModel.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package ical
|
||||||
|
|
||||||
|
import (
|
||||||
|
"htwk-planner/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FeedModel is an iCal feed
|
||||||
|
type FeedModel struct {
|
||||||
|
Content string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry is a time entry
|
||||||
|
type Entry struct {
|
||||||
|
DateStart time.Time `json:"dateStart"`
|
||||||
|
DateEnd time.Time `json:"dateEnd"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries is a collection of entries
|
||||||
|
type Entries []*Entry
|
||||||
|
|
||||||
|
type Events []*model.Event
|
Reference in New Issue
Block a user