diff --git a/backend/migrations/1698444986_updated_events.go b/backend/migrations/1698444986_updated_events.go new file mode 100644 index 0000000..f0d23e3 --- /dev/null +++ b/backend/migrations/1698444986_updated_events.go @@ -0,0 +1,114 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("7vsr9h6p") + + // remove + collection.Schema.RemoveField("wwpokofe") + + // add + new_start := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "tvxitgwc", + "name": "start", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_start) + collection.Schema.AddField(new_start) + + // add + new_end := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "trbmsfcz", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_end) + collection.Schema.AddField(new_end) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + // add + del_Start := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "7vsr9h6p", + "name": "Start", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), del_Start) + collection.Schema.AddField(del_Start) + + // add + del_End := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "wwpokofe", + "name": "End", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), del_End) + collection.Schema.AddField(del_End) + + // remove + collection.Schema.RemoveField("tvxitgwc") + + // remove + collection.Schema.RemoveField("trbmsfcz") + + return dao.SaveCollection(collection) + }) +} diff --git a/backend/migrations/1698445123_collections_snapshot.go b/backend/migrations/1698445123_collections_snapshot.go new file mode 100644 index 0000000..2e2d92d --- /dev/null +++ b/backend/migrations/1698445123_collections_snapshot.go @@ -0,0 +1,417 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" +) + +func init() { + m.Register(func(db dbx.Builder) error { + jsonData := `[ + { + "id": "cfq9mqlmd97v8z5", + "created": "2023-09-19 17:31:15.957Z", + "updated": "2023-10-27 22:15:09.073Z", + "name": "groups", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "85msl21p", + "name": "university", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "2sii4dtp", + "name": "shortcut", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "uiwgo28f", + "name": "groupId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "y0l1lrzs", + "name": "course", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "kr62mhbz", + "name": "faculty", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "ya6znpez", + "name": "facultyId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_rcaN2Oq` + "`" + ` ON ` + "`" + `groups` + "`" + ` (` + "`" + `course` + "`" + `)" + ], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "d65h4wh7zk13gxp", + "created": "2023-09-19 17:31:15.957Z", + "updated": "2023-10-27 22:15:09.073Z", + "name": "feeds", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "cowxjfmc", + "name": "modules", + "type": "json", + "required": true, + "presentable": false, + "unique": false, + "options": {} + } + ], + "indexes": [], + "listRule": null, + "viewRule": "", + "createRule": null, + "updateRule": "", + "deleteRule": null, + "options": {} + }, + { + "id": "7her4515qsmrxe8", + "created": "2023-09-19 17:31:15.958Z", + "updated": "2023-10-27 22:16:26.924Z", + "name": "events", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "m8ne8e3m", + "name": "Day", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "xnsxqp7j", + "name": "Week", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "aeuskrjo", + "name": "Name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "klrzqyw0", + "name": "EventType", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "5zltexoy", + "name": "Prof", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "gy3nvfmx", + "name": "Rooms", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "hn7b8dfy", + "name": "Notes", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "axskpwm8", + "name": "BookedAt", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "vyyefxp7", + "name": "course", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "vlbpm9fz", + "name": "semester", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "0kahthzr", + "name": "uuid", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "tvxitgwc", + "name": "start", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "trbmsfcz", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_orp1NWL` + "`" + ` ON ` + "`" + `events` + "`" + ` (\n ` + "`" + `Day` + "`" + `,\n ` + "`" + `Week` + "`" + `,\n ` + "`" + `Start` + "`" + `,\n ` + "`" + `End` + "`" + `,\n ` + "`" + `Name` + "`" + `,\n ` + "`" + `course` + "`" + `,\n ` + "`" + `Prof` + "`" + `,\n ` + "`" + `Rooms` + "`" + `,\n ` + "`" + `EventType` + "`" + `\n)" + ], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "_pb_users_auth_", + "created": "2023-10-27 22:15:08.738Z", + "updated": "2023-10-27 22:15:09.074Z", + "name": "users", + "type": "auth", + "system": false, + "schema": [ + { + "system": false, + "id": "users_name", + "name": "name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "users_avatar", + "name": "avatar", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "maxSize": 5242880, + "mimeTypes": [ + "image/jpeg", + "image/png", + "image/svg+xml", + "image/gif", + "image/webp" + ], + "thumbs": null, + "protected": false + } + } + ], + "indexes": [], + "listRule": "id = @request.auth.id", + "viewRule": "id = @request.auth.id", + "createRule": "", + "updateRule": "id = @request.auth.id", + "deleteRule": "id = @request.auth.id", + "options": { + "allowEmailAuth": true, + "allowOAuth2Auth": true, + "allowUsernameAuth": true, + "exceptEmailDomains": null, + "manageRule": null, + "minPasswordLength": 8, + "onlyEmailDomains": null, + "requireEmail": false + } + } + ]` + + collections := []*models.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collections); err != nil { + return err + } + + return daos.New(db).ImportCollections(collections, true, nil) + }, func(db dbx.Builder) error { + return nil + }) +} diff --git a/backend/model/eventModel.go b/backend/model/eventModel.go index d6cd6b2..355e594 100644 --- a/backend/model/eventModel.go +++ b/backend/model/eventModel.go @@ -4,6 +4,7 @@ import ( "slices" "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/types" ) type Events []Event @@ -13,20 +14,20 @@ func (m Events) Contains(event Event) bool { } type Event struct { - UUID string `db:"uuid" json:"uuid"` - Day string `db:"Day" json:"day"` - Week string `db:"Week" json:"week"` - Start string `db:"Start" json:"start"` - End string `db:"End" json:"end"` - Name string `db:"Name" json:"name"` - EventType string `db:"EventType" json:"eventType"` + UUID string `db:"uuid" json:"uuid"` + 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"` + Name string `db:"Name" json:"name"` + EventType string `db:"EventType" json:"eventType"` Compulsory string `db:"Compulsory" json:"compulsory"` - Prof string `db:"Prof" json:"prof"` - Rooms string `db:"Rooms" json:"rooms"` - Notes string `db:"Notes" json:"notes"` - BookedAt string `db:"BookedAt" json:"bookedAt"` - Course string `db:"course" json:"course"` - Semester string `db:"semester" json:"semester"` + Prof string `db:"Prof" json:"prof"` + Rooms string `db:"Rooms" json:"rooms"` + Notes string `db:"Notes" json:"notes"` + BookedAt string `db:"BookedAt" json:"bookedAt"` + Course string `db:"course" json:"course"` + Semester string `db:"semester" json:"semester"` models.BaseModel } diff --git a/backend/model/eventModel_test.go b/backend/model/eventModel_test.go index d73c109..4462f4e 100644 --- a/backend/model/eventModel_test.go +++ b/backend/model/eventModel_test.go @@ -1,8 +1,10 @@ package model import ( - "github.com/pocketbase/pocketbase/models" "testing" + + "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/types" ) func TestEvents_Contains(t *testing.T) { @@ -23,20 +25,20 @@ func TestEvents_Contains(t *testing.T) { }, { name: "one event", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, - args: args{event: Event{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + args: args{event: Event{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { name: "two events", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, - args: args{event: Event{Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + args: args{event: Event{Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: true, }, { name: "two events with different values", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, - args: args{event: Event{Day: "test3", Week: "test3", Start: "test3", End: "test3", Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, + args: args{event: Event{Day: "test3", Week: "test3", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, want: false, }, } @@ -54,8 +56,8 @@ func TestEvent_Equals(t *testing.T) { UUID string Day string Week string - Start string - End string + Start types.DateTime + End types.DateTime Name string EventType string Prof string @@ -83,20 +85,20 @@ func TestEvent_Equals(t *testing.T) { }, { name: "one empty one not", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, args: args{event: Event{}}, want: false, }, { name: "one event", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, - args: args{event: Event{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { name: "two events", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, - args: args{event: Event{Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: false, }, } diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index 795d91d..f2c0e0b 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -1,17 +1,18 @@ package service import ( - "github.com/labstack/echo/v5" - "github.com/pocketbase/pocketbase" - "github.com/pocketbase/pocketbase/apis" - "github.com/pocketbase/pocketbase/core" + "htwkalender/model" "htwkalender/service/events" "htwkalender/service/fetch" "htwkalender/service/ical" "htwkalender/service/room" "io" "net/http" - "net/url" + + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" ) func AddRoutes(app *pocketbase.PocketBase) { @@ -191,12 +192,17 @@ func AddRoutes(app *pocketbase.PocketBase) { app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ - Method: http.MethodGet, + Method: http.MethodPost, Path: "/api/module", Handler: func(c echo.Context) error { - name := c.Request().Header.Get("Name") - name, err := url.QueryUnescape(name) - module, err := events.GetModuleByName(app, name) + + var requestModule model.Module + + if err := c.Bind(&requestModule); err != nil { + return apis.NewBadRequestError("Failed to read request body", err) + } + + module, err := events.GetModuleByName(app, requestModule) if err != nil { return c.JSON(400, err) @@ -248,6 +254,7 @@ func AddRoutes(app *pocketbase.PocketBase) { }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), + apis.RequireAdminAuth(), }, }) if err != nil { @@ -271,6 +278,7 @@ func AddRoutes(app *pocketbase.PocketBase) { }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), + apis.RequireAdminAuth(), }, }) if err != nil { diff --git a/backend/service/date/dateFormat.go b/backend/service/date/dateFormat.go index 41d9a82..0fa9ee8 100644 --- a/backend/service/date/dateFormat.go +++ b/backend/service/date/dateFormat.go @@ -4,7 +4,8 @@ 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) + europeTime, _ := time.LoadLocation("Europe/Berlin") + firstDayOfYear := time.Date(year, time.January, 1, 0, 0, 0, 0, europeTime) // Calculate the number of days to add to reach the desired week daysToAdd := time.Duration((weekNumber-1)*7) * 24 * time.Hour diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 72a2bf4..acad6af 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -191,10 +191,10 @@ func DeleteAllEvents(app *pocketbase.PocketBase) error { return nil } -func FindAllEventsByModule(app *pocketbase.PocketBase, moduleName string) (model.Events, error) { +func FindAllEventsByModule(app *pocketbase.PocketBase, module model.Module) (model.Events, error) { var events model.Events - err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:moduleName}", dbx.Params{"moduleName": moduleName})).All(&events) + err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:moduleName} AND course = {:course}", dbx.Params{"moduleName": module.Name, "course": module.Course})).All(&events) if err != nil { print("Error while getting events from database: ", err) return nil, err diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index e114bff..4b646da 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -46,15 +46,15 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase, c echo.Context) error { // If the module does not exist, an error is returned // If the module exists, the module is returned // Module is a struct that exists in database as events -func GetModuleByName(app *pocketbase.PocketBase, name string) (model.Module, error) { - events, err := db.FindAllEventsByModule(app, name) +func GetModuleByName(app *pocketbase.PocketBase, module model.Module) (model.Module, error) { + events, err := db.FindAllEventsByModule(app, module) if err != nil || len(events) == 0 { return model.Module{}, err } else { return model.Module{ UUID: events[0].UUID, - Name: name, + Name: events[0].Name, Events: events, Prof: events[0].Prof, Course: events[0].Course, diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index e942a78..81521ae 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -13,10 +13,10 @@ import ( "time" "github.com/google/uuid" - "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/net/html" ) @@ -142,27 +142,23 @@ func convertWeeksToDates(events []model.Event, semester string, year string) []m // 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) + start := replaceTimeForDate(eventDay, event.Start.Time()) + end := replaceTimeForDate(eventDay, event.End.Time()) newEvent := event - newEvent.Start = start.String() - newEvent.End = end.String() + newEvent.Start, _ = types.ParseDateTime(start.In(time.UTC)) + newEvent.End, _ = types.ParseDateTime(end.In(time.UTC)) newEvent.Semester = semester newEvents = append(newEvents, newEvent) } return newEvents } -func addTimeToDate(date time.Time, timeString string) time.Time { - europeTime, _ := time.LoadLocation("Europe/Berlin") - //convert time functions 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, europeTime) +// replaceTimeForDate replaces hour, minute, second, nsec for the selected date +func replaceTimeForDate(date time.Time, replacementTime time.Time) time.Time { + return time.Date(date.Year(), date.Month(), date.Day(), replacementTime.Hour(), replacementTime.Minute(), replacementTime.Second(), replacementTime.Nanosecond(), date.Location()) } func extractSemesterAndYear(semesterString string) (string, string) { @@ -206,11 +202,13 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event { tableData := findTableData(tables[table][row]) if len(tableData) > 0 { + start, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[1]))) + end, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[2]))) events = append(events, model.Event{ Day: days[table], Week: getTextContent(tableData[0]), - Start: getTextContent(tableData[1]), - End: getTextContent(tableData[2]), + Start: start, + End: end, Name: getTextContent(tableData[3]), EventType: getTextContent(tableData[4]), Prof: getTextContent(tableData[5]), @@ -226,6 +224,16 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event { return events } +// createEventFromTableData should create an event from the table data +// tableTime represents Hour and Minute like HH:MM +// tableDate returns a Time +func createTimeFromHourAndMinuteString(tableTime string) time.Time { + timeParts := strings.Split(tableTime, ":") + hour, _ := strconv.Atoi(timeParts[0]) + minute, _ := strconv.Atoi(timeParts[1]) + return time.Date(0, 0, 0, hour, minute, 0, 0, time.UTC) +} + func splitEventsByWeek(events []model.Event) []model.Event { var newEvents []model.Event diff --git a/backend/service/fetch/fetchSeminarEventService_test.go b/backend/service/fetch/fetchSeminarEventService_test.go index bb98520..dde66b4 100644 --- a/backend/service/fetch/fetchSeminarEventService_test.go +++ b/backend/service/fetch/fetchSeminarEventService_test.go @@ -4,6 +4,7 @@ import ( "htwkalender/model" "reflect" "testing" + "time" ) func Test_extractSemesterAndYear(t *testing.T) { @@ -262,3 +263,80 @@ func Test_generateUUIDs(t *testing.T) { }) } } + +func Test_createTimeFromHourAndMinuteString(t *testing.T) { + europeTime, _ := time.LoadLocation("Europe/Berlin") + type args struct { + tableTime string + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Test 1", + args: args{ + tableTime: "08:00", + }, + want: time.Date(0, 0, 0, 8, 0, 0, 0, europeTime), + }, + { + name: "Test 2", + args: args{ + tableTime: "08:15", + }, + want: time.Date(0, 0, 0, 8, 15, 0, 0, europeTime), + }, + { + name: "Test 3", + args: args{ + tableTime: "08:30", + }, + want: time.Date(0, 0, 0, 8, 30, 0, 0, europeTime), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := createTimeFromHourAndMinuteString(tt.args.tableTime); !reflect.DeepEqual(got, tt.want) { + t.Errorf("createTimeFromHourAndMinuteString() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_replaceTimeInDate(t *testing.T) { + type args struct { + date time.Time + time time.Time + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Test 1", + args: args{ + date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + time: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC), + }, + want: time.Date(2021, 1, 1, 8, 0, 0, 0, time.UTC), + }, + { + name: "Test 2", + args: args{ + date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + time: time.Date(0, 0, 0, 8, 15, 0, 0, time.UTC), + }, + want: time.Date(2021, 1, 1, 8, 15, 0, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := replaceTimeForDate(tt.args.date, tt.args.time); !reflect.DeepEqual(got, tt.want) { + t.Errorf("addTimeToDate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/backend/service/ical/ical.go b/backend/service/ical/ical.go index 6ff6372..baee837 100644 --- a/backend/service/ical/ical.go +++ b/backend/service/ical/ical.go @@ -23,17 +23,14 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error { return c.JSON(http.StatusNotFound, err) } - created := feed.Created - var modules []model.FeedCollection _ = json.Unmarshal([]byte(feed.Modules), &modules) - if created.Time().Add(time.Hour * 265).Before(time.Now()) { - newFeed, err := createFeedForToken(app, modules) - if err != nil { - return c.JSON(http.StatusInternalServerError, err) - } - result = newFeed.Content + + newFeed, err := createFeedForToken(app, modules) + 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") diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go index 775f909..eb4e2e7 100644 --- a/backend/service/ical/icalFileGeneration.go +++ b/backend/service/ical/icalFileGeneration.go @@ -9,7 +9,7 @@ import ( "github.com/jordic/goics" ) -// local type for EmitICal function +// IcalModel local type for EmitICal function type IcalModel struct { Events model.Events Mapping []model.FeedCollection @@ -17,7 +17,7 @@ type IcalModel struct { // EmitICal implements the interface for goics func (icalModel IcalModel) EmitICal() goics.Componenter { - layout := "2006-01-02 15:04:05 -0700 MST" + europeTime, _ := time.LoadLocation("Europe/Berlin") c := goics.NewComponent() c.SetType("VCALENDAR") c.AddProperty("VERSION", "2.0") @@ -29,11 +29,9 @@ func (icalModel IcalModel) EmitICal() goics.Componenter { for _, event := range icalModel.Events { s := goics.NewComponent() s.SetType("VEVENT") - timeEnd, _ := time.Parse(layout, event.End) - timeStart, _ := time.Parse(layout, event.Start) - k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", timeEnd) + k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", event.End.Time().Local().In(europeTime)) s.AddProperty(k, v) - k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", timeStart) + k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start.Time().Local().In(europeTime)) s.AddProperty(k, v) s.AddProperty("SUMMARY", replaceNameIfUserDefined(&event, icalModel.Mapping)) s.AddProperty("DESCRIPTION", generateDescription(event)) diff --git a/frontend/src/api/fetchModule.ts b/frontend/src/api/fetchModule.ts index a6b2d86..e6a7f1c 100644 --- a/frontend/src/api/fetchModule.ts +++ b/frontend/src/api/fetchModule.ts @@ -1,12 +1,12 @@ import { Module } from "../model/module"; -export async function fetchModule(name: string): Promise { +export async function fetchModule(module: Module): Promise { const request = new Request("/api/module", { - method: "GET", + method: "POST", headers: { "Content-Type": "application/json", - Name: encodeURI(name), }, + body: JSON.stringify(module), }); return await fetch(request) diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/components/AdditionalModules.vue index c989cb1..868fdf4 100644 --- a/frontend/src/components/AdditionalModules.vue +++ b/frontend/src/components/AdditionalModules.vue @@ -38,10 +38,9 @@ const ModuleInformation = defineAsyncComponent( () => import("./ModuleInformation.vue"), ); -async function showInfo(moduleName: string) { - const module: Ref = ref(new Module("", "", "", "", "", "", [])); - await fetchModule(moduleName).then((data) => { - module.value = data; +async function showInfo(module: Module) { + await fetchModule(module).then((data) => { + module = data; }); dialog.open(ModuleInformation, { props: { @@ -66,7 +65,7 @@ const selectAll = ref(false); const onSelectAllChange = (event: MultiSelectAllChangeEvent) => { selectedModules.value = event.checked - ? modules.value.map((module) => module) + ? modules.value.map((module: Module) => module) : []; selectAll.value = event.checked; }; @@ -112,7 +111,7 @@ function selectChange() { rounded outlined aria-label="Information" - @click.stop="showInfo(slotProps.option.name)" + @click.stop="showInfo(slotProps.option)" > diff --git a/frontend/src/components/editCalendar/EditAdditionalModules.vue b/frontend/src/components/editCalendar/EditAdditionalModules.vue index b19f63e..5b07d39 100644 --- a/frontend/src/components/editCalendar/EditAdditionalModules.vue +++ b/frontend/src/components/editCalendar/EditAdditionalModules.vue @@ -36,9 +36,8 @@ const ModuleInformation = defineAsyncComponent( () => import("../ModuleInformation.vue"), ); -async function showInfo(moduleName: string) { - const module: Ref = ref(new Module("", "", "", "", "", "", [])); - await fetchModule(moduleName).then((data) => { +async function showInfo(module: Module) { + await fetchModule(module).then((data) => { module.value = data; }); dialog.open(ModuleInformation, { @@ -110,7 +109,7 @@ function selectChange() { rounded outlined aria-label="Information" - @click.stop="showInfo(slotProps.option.name)" + @click.stop="showInfo(slotProps.option)" >