mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 09:38: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/core"
|
||||
"htwk-planner/service"
|
||||
"htwk-planner/service/fetch"
|
||||
"htwk-planner/service/ical"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
@ -22,7 +24,7 @@ func addRoutes(app *pocketbase.PocketBase) {
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/fetchPlans",
|
||||
Handler: func(c echo.Context) error {
|
||||
return service.FetchHTWK(c, app)
|
||||
return fetch.FetchHTWK(c, app)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
@ -39,7 +41,7 @@ func addRoutes(app *pocketbase.PocketBase) {
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/fetchGroups",
|
||||
Handler: func(c echo.Context) error {
|
||||
return service.FetchSeminarGroups(c, app)
|
||||
return fetch.SeminarGroups(c, app)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
@ -68,4 +70,38 @@ func addRoutes(app *pocketbase.PocketBase) {
|
||||
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
|
||||
|
||||
require (
|
||||
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
github.com/pocketbase/pocketbase v0.17.5
|
||||
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/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.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
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/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/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/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/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
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/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
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/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
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/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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
|
@ -7,10 +7,10 @@ type SeminarGroup struct {
|
||||
Course string
|
||||
Faculty string
|
||||
FacultyId string
|
||||
Events []Events
|
||||
Events []Event
|
||||
}
|
||||
|
||||
type Events struct {
|
||||
type Event struct {
|
||||
Day string
|
||||
Week string
|
||||
Start string
|
||||
@ -21,4 +21,5 @@ type Events struct {
|
||||
Rooms string
|
||||
Notes string
|
||||
BookedAt string
|
||||
Semester string
|
||||
}
|
||||
|
@ -281,6 +281,18 @@
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vlbpm9fz",
|
||||
"name": "semester",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"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
|
||||
|
||||
import (
|
||||
"github.com/jordic/goics"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwk-planner/model"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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("BookedAt", event.BookedAt)
|
||||
record.Set("course", seminarGroup.Course)
|
||||
record.Set("semester", event.Semester)
|
||||
err = app.Dao().SaveRecord(record)
|
||||
if err != nil {
|
||||
println("Error while saving record: ", err.Error())
|
||||
@ -42,7 +46,7 @@ func contains(s []string, e string) bool {
|
||||
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 {
|
||||
|
||||
var events []struct {
|
||||
@ -70,3 +74,39 @@ func GetRooms(app *pocketbase.PocketBase) []string {
|
||||
}
|
||||
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 (
|
||||
"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()
|
||||
|
||||
@ -55,7 +55,7 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
||||
|
||||
dbError = db.SaveGroups(groups, collection, app)
|
||||
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)
|
||||
@ -64,7 +64,6 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
||||
func parseSeminarGroups(result string) []model.SeminarGroup {
|
||||
|
||||
var studium model.Studium
|
||||
|
||||
err := xml.Unmarshal([]byte(result), &studium)
|
||||
if err != nil {
|
||||
return nil
|
@ -1,4 +1,4 @@
|
||||
package service
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -8,11 +8,14 @@ import (
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"golang.org/x/net/html"
|
||||
"htwk-planner/model"
|
||||
"htwk-planner/service/date"
|
||||
"htwk-planner/service/db"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
@ -73,24 +76,87 @@ func parseSeminarGroup(result string) model.SeminarGroup {
|
||||
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
|
||||
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
||||
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{
|
||||
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
|
||||
Course: findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data,
|
||||
Events: events,
|
||||
}
|
||||
|
||||
return seminarGroup
|
||||
}
|
||||
|
||||
func toEvents(tables [][]*html.Node, days []string) []model.Events {
|
||||
var events []model.Events
|
||||
func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event {
|
||||
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 row := range tables[table] {
|
||||
|
||||
tableData := findTableData(tables[table][row])
|
||||
if len(tableData) > 0 {
|
||||
events = append(events, model.Events{
|
||||
events = append(events, model.Event{
|
||||
Day: days[table],
|
||||
Week: getTextContent(tableData[0]),
|
||||
Start: getTextContent(tableData[1]),
|
||||
@ -110,8 +176,8 @@ func toEvents(tables [][]*html.Node, days []string) []model.Events {
|
||||
return events
|
||||
}
|
||||
|
||||
func splitEventsByWeek(events []model.Events) []model.Events {
|
||||
var newEvents []model.Events
|
||||
func splitEventsByWeek(events []model.Event) []model.Event {
|
||||
var newEvents []model.Event
|
||||
|
||||
for _, event := range events {
|
||||
weeks := strings.Split(event.Week, ",")
|
||||
@ -124,8 +190,8 @@ func splitEventsByWeek(events []model.Events) []model.Events {
|
||||
return newEvents
|
||||
}
|
||||
|
||||
func splitEventsBySingleWeek(events []model.Events) []model.Events {
|
||||
var newEvents []model.Events
|
||||
func splitEventsBySingleWeek(events []model.Event) []model.Event {
|
||||
var newEvents []model.Event
|
||||
|
||||
for _, event := range events {
|
||||
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