From b1927864c27bef82399fd889491ca8691b9b57d2 Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sat, 30 Sep 2023 21:44:43 +0200 Subject: [PATCH 01/25] changed module model and fixed empty name in event while fetch --- backend/model/eventModel.go | 15 +++++++++++++ backend/model/seminarGroup.go | 15 ------------- backend/service/db/dbEvents.go | 30 ++++++-------------------- backend/service/events/eventService.go | 6 ++---- backend/service/functions/string.go | 7 ++++++ 5 files changed, 31 insertions(+), 42 deletions(-) diff --git a/backend/model/eventModel.go b/backend/model/eventModel.go index f83e22d..daaf229 100644 --- a/backend/model/eventModel.go +++ b/backend/model/eventModel.go @@ -1,3 +1,18 @@ package model type Events []*Event + +type Event struct { + 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"` + 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"` +} diff --git a/backend/model/seminarGroup.go b/backend/model/seminarGroup.go index d9aedbc..d3d63c9 100644 --- a/backend/model/seminarGroup.go +++ b/backend/model/seminarGroup.go @@ -9,18 +9,3 @@ type SeminarGroup struct { FacultyId string Events []Event } - -type Event struct { - Day string `db:"course"` - Week string `db:"Week"` - Start string `db:"Start"` - End string `db:"End"` - Name string `db:"Name"` - EventType string `db:"EventType"` - Prof string `db:"Prof"` - Rooms string `db:"Rooms"` - Notes string `db:"Notes"` - BookedAt string `db:"BookedAt"` - Course string `db:"course"` - Semester string `db:"semester"` -} diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 20dc862..6332e8b 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -5,6 +5,7 @@ import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/models" "htwkalender/model" + "htwkalender/service/functions" "log" ) @@ -39,7 +40,7 @@ func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection record.Set("Week", event.Week) record.Set("Start", event.Start) record.Set("End", event.End) - record.Set("Name", event.Name) + record.Set("Name", functions.ReplaceEmptyString(event.Name, "Sonderveranstaltungen")) record.Set("EventType", event.EventType) record.Set("Prof", event.Prof) record.Set("Rooms", event.Rooms) @@ -120,31 +121,14 @@ func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester return eventArray, nil } -func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]struct { - Name string - Course string -}, error) { - var events []struct { - Name string `db:"Name" json:"Name"` - Course string `db:"course" json:"course"` - } +func GetAllModulesDistinct(app *pocketbase.PocketBase) (model.Events, error) { + var events model.Events - var eventArray []struct { - Name string - Course string - } - - err := app.Dao().DB().Select("Name", "course").From("events").Distinct(true).All(&events) + err := app.Dao().DB().Select("*").From("events").Distinct(true).All(&events) if err != nil { print("Error while getting events from database: ", err) - return eventArray, err + return nil, err } - for _, event := range events { - eventArray = append(eventArray, struct { - Name string - Course string - }{event.Name, event.Course}) - } - return eventArray, nil + return events, nil } diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index b5684f0..8878f0d 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -3,6 +3,7 @@ package events import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" + "htwkalender/model" "htwkalender/service/db" "htwkalender/service/functions" ) @@ -28,10 +29,7 @@ func replaceEmptyEntryInStringArray(modules []string, replacement string) { } } -func replaceEmptyEntry(modules []struct { - Name string - Course string -}, replacement string) { +func replaceEmptyEntry(modules model.Events, replacement string) { //replace empty functions with "Sonderveranstaltungen" for i, module := range modules { if functions.OnlyWhitespace(module.Name) { diff --git a/backend/service/functions/string.go b/backend/service/functions/string.go index 66bd45c..f89ac44 100644 --- a/backend/service/functions/string.go +++ b/backend/service/functions/string.go @@ -20,3 +20,10 @@ func Contains(s []string, e string) bool { } return false } + +func ReplaceEmptyString(word string, replacement string) string { + if OnlyWhitespace(word) { + return replacement + } + return word +} From f982feec4f5f8bdd47148d45b94257d484dfe0e7 Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sat, 30 Sep 2023 22:45:58 +0200 Subject: [PATCH 02/25] refactoring to clear db functions --- backend/service/db/dbEvents.go | 3 +- .../service/fetch/fetchSeminarEventService.go | 13 ++++ .../fetch/fetchSeminarEventService_test.go | 71 ++++++++++++++++++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 6332e8b..2bc15b4 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -5,7 +5,6 @@ import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/models" "htwkalender/model" - "htwkalender/service/functions" "log" ) @@ -40,7 +39,7 @@ func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection record.Set("Week", event.Week) record.Set("Start", event.Start) record.Set("End", event.End) - record.Set("Name", functions.ReplaceEmptyString(event.Name, "Sonderveranstaltungen")) + record.Set("Name", event.Name) record.Set("EventType", event.EventType) record.Set("Prof", event.Prof) record.Set("Rooms", event.Rooms) diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index 17a6125..4e40819 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -30,6 +30,8 @@ func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { seminarGroups = clearEmptySeminarGroups(seminarGroups) + seminarGroups = replaceEmptyEventNames(seminarGroups) + savedRecords, dbError := db.SaveEvents(seminarGroups, collection, app) if dbError != nil { @@ -39,6 +41,17 @@ func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { return c.JSON(http.StatusOK, savedRecords) } +func replaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup { + for i, group := range groups { + for j, event := range group.Events { + if event.Name == "" { + groups[i].Events[j].Name = "Sonderveranstaltungen" + } + } + } + return groups +} + func clearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup { var newSeminarGroups []model.SeminarGroup for _, seminarGroup := range seminarGroups { diff --git a/backend/service/fetch/fetchSeminarEventService_test.go b/backend/service/fetch/fetchSeminarEventService_test.go index ecbe082..88bb460 100644 --- a/backend/service/fetch/fetchSeminarEventService_test.go +++ b/backend/service/fetch/fetchSeminarEventService_test.go @@ -1,6 +1,10 @@ package fetch -import "testing" +import ( + "htwkalender/model" + "reflect" + "testing" +) func Test_extractSemesterAndYear(t *testing.T) { type args struct { @@ -50,3 +54,68 @@ func Test_extractSemesterAndYear(t *testing.T) { }) } } + +func Test_replaceEmptyEventNames(t *testing.T) { + type args struct { + groups []model.SeminarGroup + } + tests := []struct { + name string + args args + want []model.SeminarGroup + }{ + { + name: "Test 1", + args: args{ + groups: []model.SeminarGroup{ + { + Events: []model.Event{ + { + Name: "Test", + }, + }, + }, + }, + }, + want: []model.SeminarGroup{ + { + Events: []model.Event{ + { + Name: "Test", + }, + }, + }, + }, + }, + { + name: "Test 1", + args: args{ + groups: []model.SeminarGroup{ + { + Events: []model.Event{ + { + Name: "", + }, + }, + }, + }, + }, + want: []model.SeminarGroup{ + { + Events: []model.Event{ + { + Name: "Sonderveranstaltungen", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := replaceEmptyEventNames(tt.args.groups); !reflect.DeepEqual(got, tt.want) { + t.Errorf("replaceEmptyEventNames() = %v, want %v", got, tt.want) + } + }) + } +} From f89416170d2b649d19108976e69805d718f38d5b Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:30:19 +0200 Subject: [PATCH 03/25] added schedule for updating all modules --- backend/main.go | 2 + backend/model/moduleModel.go | 9 +++ backend/service/addRoute.go | 59 ++++++++++++++++++- backend/service/addSchedule.go | 37 ++++++++++++ .../service/fetch/fetchSeminarEventService.go | 8 +-- .../fetch/fetchSeminarEventService_test.go | 4 +- frontend/src/model/module.ts | 1 + 7 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 backend/model/moduleModel.go create mode 100644 backend/service/addSchedule.go diff --git a/backend/main.go b/backend/main.go index 5e170be..272cba2 100644 --- a/backend/main.go +++ b/backend/main.go @@ -24,6 +24,8 @@ func main() { service.AddRoutes(app) + service.AddSchedules(app) + if err := app.Start(); err != nil { log.Fatal(err) } diff --git a/backend/model/moduleModel.go b/backend/model/moduleModel.go new file mode 100644 index 0000000..ab0d17f --- /dev/null +++ b/backend/model/moduleModel.go @@ -0,0 +1,9 @@ +package model + +type Module struct { + Name string `json:"name"` + Prof string `json:"prof"` + Course string `json:"course"` + Semester string `json:"semester"` + Events Events `json:"events"` +} diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index 20b9e24..f0abc62 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -132,7 +132,13 @@ func AddRoutes(app *pocketbase.PocketBase) { Handler: func(c echo.Context) error { course := c.QueryParam("course") semester := c.QueryParam("semester") - return events.GetModulesForCourseDistinct(app, c, course, semester) + modules, err := events.GetModulesForCourseDistinct(app, course, semester) + + if err != nil { + return c.JSON(400, err) + } else { + return c.JSON(200, modules) + } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), @@ -161,12 +167,61 @@ 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/module", + Handler: func(c echo.Context) error { + name := c.QueryParam("name") + module, err := events.GetModuleByName(app, name) + + if err != nil { + return c.JSON(400, err) + } else { + return c.JSON(200, module) + } + }, + 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/courses", Handler: func(c echo.Context) error { - return events.GetAllCourses(app, c) + courses := events.GetAllCourses(app) + return c.JSON(200, courses) + }, + 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.MethodDelete, + Path: "/api/events", + Handler: func(c echo.Context) error { + course := c.QueryParam("course") + semester := c.QueryParam("semester") + err := events.DeleteAllEventsByCourseAndSemester(app, course, semester) + if err != nil { + return c.JSON(400, err) + } else { + return c.JSON(200, "Events deleted") + } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), diff --git a/backend/service/addSchedule.go b/backend/service/addSchedule.go new file mode 100644 index 0000000..fa36aff --- /dev/null +++ b/backend/service/addSchedule.go @@ -0,0 +1,37 @@ +package service + +import ( + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/cron" + "htwkalender/service/events" + "log" +) + +func AddSchedules(app *pocketbase.PocketBase) { + + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + scheduler := cron.New() + + scheduler.MustAdd("updateCourse", "*/60 * * * *", func() { + + courses := events.GetAllCourses(app) + + for _, course := range courses { + err := events.UpdateModulesForCourse(app, course) + if err != nil { + log.Println("Update Course: " + course + " failed") + log.Println(err) + } else { + log.Println("Update Course: " + course + " successful") + } + } + + }) + + scheduler.Start() + + return nil + }) + +} diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index 4e40819..24e6baa 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -28,9 +28,9 @@ func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { return apis.NewNotFoundError("Collection not found", dbError) } - seminarGroups = clearEmptySeminarGroups(seminarGroups) + seminarGroups = ClearEmptySeminarGroups(seminarGroups) - seminarGroups = replaceEmptyEventNames(seminarGroups) + seminarGroups = ReplaceEmptyEventNames(seminarGroups) savedRecords, dbError := db.SaveEvents(seminarGroups, collection, app) @@ -41,7 +41,7 @@ func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { return c.JSON(http.StatusOK, savedRecords) } -func replaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup { +func ReplaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup { for i, group := range groups { for j, event := range group.Events { if event.Name == "" { @@ -52,7 +52,7 @@ func replaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup { return groups } -func clearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup { +func ClearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup { var newSeminarGroups []model.SeminarGroup for _, seminarGroup := range seminarGroups { if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" { diff --git a/backend/service/fetch/fetchSeminarEventService_test.go b/backend/service/fetch/fetchSeminarEventService_test.go index 88bb460..b3d95d4 100644 --- a/backend/service/fetch/fetchSeminarEventService_test.go +++ b/backend/service/fetch/fetchSeminarEventService_test.go @@ -113,8 +113,8 @@ func Test_replaceEmptyEventNames(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := replaceEmptyEventNames(tt.args.groups); !reflect.DeepEqual(got, tt.want) { - t.Errorf("replaceEmptyEventNames() = %v, want %v", got, tt.want) + if got := ReplaceEmptyEventNames(tt.args.groups); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReplaceEmptyEventNames() = %v, want %v", got, tt.want) } }) } diff --git a/frontend/src/model/module.ts b/frontend/src/model/module.ts index eff8f67..263f315 100644 --- a/frontend/src/model/module.ts +++ b/frontend/src/model/module.ts @@ -1,5 +1,6 @@ export class Module { constructor( + public Id: string, public Name: string, public Course: string, public UserDefinedName: string, From 54731c0334d87e19cca9652e1ba472470060ce58 Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:31:02 +0200 Subject: [PATCH 04/25] wrote descriptions and refactored for new module response with information --- backend/service/db/dbEvents.go | 40 +++++++---- backend/service/events/courseService.go | 6 +- backend/service/events/eventService.go | 90 ++++++++++++++++++++++--- frontend/src/api/fetchCourse.ts | 6 +- 4 files changed, 115 insertions(+), 27 deletions(-) diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 2bc15b4..a89fdc6 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -100,24 +100,17 @@ func GetPlanForModules(app *pocketbase.PocketBase, modules []model.FeedCollectio return events } -func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) ([]string, error) { - var events []struct { - Name string `db:"Name" json:"Name"` - } - - var eventArray []string +func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) { + var events model.Events // get all events from event records in the events collection - err := app.Dao().DB().Select("Name").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Distinct(true).All(&events) + err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Distinct(true).All(&events) if err != nil { print("Error while getting events from database: ", err) - return eventArray, err + return nil, err } - for _, event := range events { - eventArray = append(eventArray, event.Name) - } - return eventArray, nil + return events, nil } func GetAllModulesDistinct(app *pocketbase.PocketBase) (model.Events, error) { @@ -131,3 +124,26 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase) (model.Events, error) { return events, nil } + +func DeleteAllEventsForCourse(app *pocketbase.PocketBase, course string, semester string) error { + _, err := app.Dao().DB().Delete("events", dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Execute() + + if err != nil { + print("Error while deleting events from database: ", err) + return err + } + + return nil +} + +func FindAllEventsByModule(app *pocketbase.PocketBase, moduleName string) (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) + if err != nil { + print("Error while getting events from database: ", err) + return nil, err + } + + return events, nil +} diff --git a/backend/service/events/courseService.go b/backend/service/events/courseService.go index daf9f09..8388925 100644 --- a/backend/service/events/courseService.go +++ b/backend/service/events/courseService.go @@ -1,12 +1,10 @@ package events import ( - "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "htwkalender/service/db" ) -func GetAllCourses(app *pocketbase.PocketBase, c echo.Context) error { - courses := db.GetAllCourses(app) - return c.JSON(200, courses) +func GetAllCourses(app *pocketbase.PocketBase) []string { + return db.GetAllCourses(app) } diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index 8878f0d..9d58624 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -3,21 +3,18 @@ package events import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" "htwkalender/model" "htwkalender/service/db" + "htwkalender/service/fetch" "htwkalender/service/functions" ) -func GetModulesForCourseDistinct(app *pocketbase.PocketBase, c echo.Context, course string, semester string) error { +func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) { modules, err := db.GetAllModulesForCourse(app, course, semester) - replaceEmptyEntryInStringArray(modules, "Sonderveranstaltungen") - - if err != nil { - return c.JSON(400, err) - } else { - return c.JSON(200, modules) - } + replaceEmptyEntry(modules, "Sonderveranstaltungen") + return modules, err } func replaceEmptyEntryInStringArray(modules []string, replacement string) { @@ -49,3 +46,80 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase, c echo.Context) error { return c.JSON(200, modules) } } + +// GetModuleByName returns a module by its name +// 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) + + if err != nil || len(events) == 0 { + return model.Module{}, err + } else { + return model.Module{ + Name: name, + Events: events, + Prof: events[0].Prof, + Course: events[0].Course, + Semester: events[0].Semester, + }, nil + } +} + +// DeleteAllEventsByCourseAndSemester deletes all events for a course and a semester +// If the deletion was successful, nil is returned +// If the deletion was not successful, an error is returned +func DeleteAllEventsByCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) error { + err := db.DeleteAllEventsForCourse(app, course, semester) + if err != nil { + return err + } else { + return nil + } +} + +// UpdateModulesForCourse updates all modules for a course +// Does Updates for ws and ss semester sequentially +// Update runs through the following steps: +// 1. Delete all events for the course and the semester +// 2. Fetch all events for the course and the semester +// 3. Save all events for the course and the semester +// If the update was successful, nil is returned +// If the update was not successful, an error is returned +func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error { + + var err error + err = DeleteAllEventsByCourseAndSemester(app, course, "ws") + if err != nil { + return err + } + + err = DeleteAllEventsByCourseAndSemester(app, course, "ss") + if err != nil { + return err + } + + //new string array with one element (course) + var courses []string + courses = append(courses, course) + + seminarGroups := fetch.GetSeminarGroupsEventsFromHTML(courses) + + collection, dbError := db.FindCollection(app, "events") + if dbError != nil { + return apis.NewNotFoundError("Collection not found", dbError) + } + + seminarGroups = fetch.ClearEmptySeminarGroups(seminarGroups) + + seminarGroups = fetch.ReplaceEmptyEventNames(seminarGroups) + + _, dbError = db.SaveEvents(seminarGroups, collection, app) + + if dbError != nil { + return apis.NewNotFoundError("Events could not be saved", dbError) + } + + return nil +} diff --git a/frontend/src/api/fetchCourse.ts b/frontend/src/api/fetchCourse.ts index 75bc575..2b4c2b6 100644 --- a/frontend/src/api/fetchCourse.ts +++ b/frontend/src/api/fetchCourse.ts @@ -24,8 +24,8 @@ export async function fetchModulesByCourseAndSemester( return response.json(); }) .then((modulesResponse) => { - modulesResponse.forEach((module: string) => - modules.push(new Module(module, course, module)), + modulesResponse.forEach((module: Module) => + modules.push(new Module(module.Id, module.Name, course, module.Name)), ); }); return modules; @@ -39,7 +39,7 @@ export async function fetchAllModules(): Promise { }) .then((responseModules: Module[]) => { responseModules.forEach((module: Module) => { - modules.push(new Module(module.Name, module.Course, module.Name)); + modules.push(new Module(module.Id, module.Name, module.Course, module.Name)); }); }); From 8b6367b0247390f214083808892c0fc913618821 Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:31:27 +0200 Subject: [PATCH 05/25] removed unused function --- backend/service/events/eventService.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index 9d58624..958e9fa 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -17,15 +17,6 @@ func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, seme return modules, err } -func replaceEmptyEntryInStringArray(modules []string, replacement string) { - //replace empty functions with "Sonderveranstaltungen" - for i, module := range modules { - if functions.OnlyWhitespace(module) { - modules[i] = replacement - } - } -} - func replaceEmptyEntry(modules model.Events, replacement string) { //replace empty functions with "Sonderveranstaltungen" for i, module := range modules { From 3225a9040e920a50ea0b7a2f4925db7561094977 Mon Sep 17 00:00:00 2001 From: Elmar Kresse <18119527+masterElmar@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:31:54 +0200 Subject: [PATCH 06/25] added comments --- backend/service/events/eventService.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index 958e9fa..2ae6a4c 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -17,8 +17,10 @@ func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, seme return modules, err } +// replaceEmptyEntry replaces an empty entry in a module with a replacement string +// If the module is not empty, nothing happens func replaceEmptyEntry(modules model.Events, replacement string) { - //replace empty functions with "Sonderveranstaltungen" + for i, module := range modules { if functions.OnlyWhitespace(module.Name) { modules[i].Name = replacement From 9a1342b954e905c2059c20095ecf47f075cd5558 Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Sun, 8 Oct 2023 18:22:34 +0200 Subject: [PATCH 07/25] fixed schedule and update process --- backend/service/addRoute.go | 11 +++- backend/service/addSchedule.go | 5 +- backend/service/db/dbEvents.go | 2 + backend/service/events/eventService.go | 74 +++++++++++++++++++++----- backend/service/ical/ical.go | 19 +++---- 5 files changed, 82 insertions(+), 29 deletions(-) diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index f0abc62..2bf004b 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -9,6 +9,7 @@ import ( "htwkalender/service/fetch" "htwkalender/service/ical" "htwkalender/service/room" + "io" "net/http" "os" ) @@ -71,6 +72,7 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) + // API Endpoint to get all events for a specific room on a specific day app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, @@ -90,12 +92,19 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) + // API Endpoint to create a new iCal feed app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodPost, Path: "/api/createFeed", Handler: func(c echo.Context) error { - return ical.CreateIndividualFeed(c, app) + requestBody, _ := io.ReadAll(c.Request().Body) + result, err := ical.CreateIndividualFeed(requestBody, app) + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + return c.JSON(http.StatusOK, result) + }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), diff --git a/backend/service/addSchedule.go b/backend/service/addSchedule.go index fa36aff..e50201c 100644 --- a/backend/service/addSchedule.go +++ b/backend/service/addSchedule.go @@ -13,7 +13,10 @@ func AddSchedules(app *pocketbase.PocketBase) { app.OnBeforeServe().Add(func(e *core.ServeEvent) error { scheduler := cron.New() - scheduler.MustAdd("updateCourse", "*/60 * * * *", func() { + // Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *" + // Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *" + // Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *" + scheduler.MustAdd("updateCourse", "*/10 * * * *", func() { courses := events.GetAllCourses(app) diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index a89fdc6..c1113a6 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -77,6 +77,8 @@ func findEventByDayWeekStartEndNameCourse(event model.Event, course string, app return &event, err } +// GetPlanForModules returns all events for the given modules with the given course +// used for the ical feed func GetPlanForModules(app *pocketbase.PocketBase, modules []model.FeedCollection) model.Events { // build query functions with name equals elements in modules for dbx query diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index 2ae6a4c..48a9172 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -82,17 +82,6 @@ func DeleteAllEventsByCourseAndSemester(app *pocketbase.PocketBase, course strin // If the update was not successful, an error is returned func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error { - var err error - err = DeleteAllEventsByCourseAndSemester(app, course, "ws") - if err != nil { - return err - } - - err = DeleteAllEventsByCourseAndSemester(app, course, "ss") - if err != nil { - return err - } - //new string array with one element (course) var courses []string courses = append(courses, course) @@ -108,11 +97,68 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error { seminarGroups = fetch.ReplaceEmptyEventNames(seminarGroups) - _, dbError = db.SaveEvents(seminarGroups, collection, app) + //check if events in the seminarGroups Events are already in the database + //if yes, keep the database as it is + //if no, delete all events for the course and the semester and save the new events + //if there are no events in the database, save the new events - if dbError != nil { - return apis.NewNotFoundError("Events could not be saved", dbError) + //get all events for the course and the semester + events, err := db.GetAllModulesForCourse(app, course, "ws") + if err != nil { + return apis.NewNotFoundError("Events could not be found", err) + } + + //if there are no events in the database, save the new events + if len(events) == 0 { + _, dbError = db.SaveEvents(seminarGroups, collection, app) + if dbError != nil { + return apis.NewNotFoundError("Events could not be saved", dbError) + } + return nil + } + + //check if events in the seminarGroups Events are already in the database + //if yes, keep the database as it is + //if no, delete all events for the course and the semester and save the new events + for _, seminarGroup := range seminarGroups { + for _, event := range seminarGroup.Events { + // if the event is not in the database, delete all events for the course and the semester and save the new events + if !ContainsEvent(events, event) { + + err = DeleteAllEventsByCourseAndSemester(app, course, "ws") + if err != nil { + return err + } + + err = DeleteAllEventsByCourseAndSemester(app, course, "ss") + if err != nil { + return err + } + + //save the new events + _, dbError = db.SaveEvents(seminarGroups, collection, app) + if dbError != nil { + return apis.NewNotFoundError("Events could not be saved", dbError) + } + return nil + } + } } return nil } + +func ContainsEvent(events model.Events, event model.Event) bool { + for _, e := range events { + if e.Name == event.Name && + e.Prof == event.Prof && + e.Rooms == event.Rooms && + e.Semester == event.Semester && + e.Start == event.Start && + e.End == event.End && + e.Course == event.Course { + return true + } + } + return false +} diff --git a/backend/service/ical/ical.go b/backend/service/ical/ical.go index 17f66f5..a0ad8d4 100644 --- a/backend/service/ical/ical.go +++ b/backend/service/ical/ical.go @@ -9,7 +9,6 @@ import ( "github.com/pocketbase/pocketbase/apis" "htwkalender/model" "htwkalender/service/db" - "io" "net/http" "time" ) @@ -63,18 +62,12 @@ func writeSuccess(message string, w http.ResponseWriter) { } } -func CreateIndividualFeed(c echo.Context, app *pocketbase.PocketBase) error { - - // read json from request body +func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) { var modules []model.FeedCollection - requestBodyBytes, err := io.ReadAll(c.Request().Body) - if err != nil { - return apis.NewApiError(400, "Could not bind request body", err) - } - err = json.Unmarshal(requestBodyBytes, &modules) + err := json.Unmarshal(requestBody, &modules) if err != nil { - return apis.NewApiError(400, "Could not bind request body", err) + return "", apis.NewNotFoundError("Could not parse request body", err) } var feed model.Feed @@ -83,13 +76,13 @@ func CreateIndividualFeed(c echo.Context, app *pocketbase.PocketBase) error { collection, dbError := db.FindCollection(app, "feeds") if dbError != nil { - return apis.NewNotFoundError("Collection not found", dbError) + return "", apis.NewNotFoundError("Collection could not be found", dbError) } record, err := db.SaveFeed(feed, collection, app) if err != nil { - return apis.NewNotFoundError("Feed could not be saved", dbError) + return "", apis.NewNotFoundError("Could not save feed", err) } - return c.JSON(http.StatusOK, record.Id) + return record.Id, nil } From e8a94bdff4e32d703cf432326b1197f93ea1d040 Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:41:04 +0200 Subject: [PATCH 08/25] changed frontend for new more detailed backend api endpoint --- frontend/src/api/fetchCourse.ts | 4 ++-- frontend/src/components/ModuleSelection.vue | 2 +- frontend/src/model/module.ts | 8 ++++---- frontend/src/router/index.ts | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/api/fetchCourse.ts b/frontend/src/api/fetchCourse.ts index 2b4c2b6..b8c9da4 100644 --- a/frontend/src/api/fetchCourse.ts +++ b/frontend/src/api/fetchCourse.ts @@ -25,7 +25,7 @@ export async function fetchModulesByCourseAndSemester( }) .then((modulesResponse) => { modulesResponse.forEach((module: Module) => - modules.push(new Module(module.Id, module.Name, course, module.Name)), + modules.push(new Module(module.id, module.name, course, module.name)), ); }); return modules; @@ -39,7 +39,7 @@ export async function fetchAllModules(): Promise { }) .then((responseModules: Module[]) => { responseModules.forEach((module: Module) => { - modules.push(new Module(module.Id, module.Name, module.Course, module.Name)); + modules.push(new Module(module.id, module.name, module.course, module.name)); }); }); diff --git a/frontend/src/components/ModuleSelection.vue b/frontend/src/components/ModuleSelection.vue index ab43ec5..446df5d 100644 --- a/frontend/src/components/ModuleSelection.vue +++ b/frontend/src/components/ModuleSelection.vue @@ -105,7 +105,7 @@ function nextStep() { class="flex flex-column align-items-center sm:align-items-start gap-3" >
- {{ slotProps.data.module.Name }} + {{ slotProps.data.module.name }}
Date: Sun, 8 Oct 2023 20:44:07 +0200 Subject: [PATCH 09/25] fixed db query and scheduled update process --- backend/service/addSchedule.go | 2 +- backend/service/db/dbEvents.go | 2 +- backend/service/events/eventService.go | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/service/addSchedule.go b/backend/service/addSchedule.go index e50201c..a7a2ffa 100644 --- a/backend/service/addSchedule.go +++ b/backend/service/addSchedule.go @@ -16,7 +16,7 @@ func AddSchedules(app *pocketbase.PocketBase) { // Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *" // Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *" // Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *" - scheduler.MustAdd("updateCourse", "*/10 * * * *", func() { + scheduler.MustAdd("updateCourse", "*/30 * * * *", func() { courses := events.GetAllCourses(app) diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index c1113a6..d1427ca 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -106,7 +106,7 @@ func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester var events model.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})).Distinct(true).All(&events) + err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).GroupBy("Name").Distinct(true).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 48a9172..48efbcd 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -105,9 +105,17 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error { //get all events for the course and the semester events, err := db.GetAllModulesForCourse(app, course, "ws") if err != nil { - return apis.NewNotFoundError("Events could not be found", err) + return apis.NewNotFoundError("Events for winter semester could not be found", err) } + // append all events for the course and the semester to the events array for ss + summerEvents, err := db.GetAllModulesForCourse(app, course, "ss") + if err != nil { + return apis.NewNotFoundError("Events for summer semester could not be found", err) + } + + events = append(events, summerEvents...) + //if there are no events in the database, save the new events if len(events) == 0 { _, dbError = db.SaveEvents(seminarGroups, collection, app) From ba2bbf8de99ad9f1b4785c98e5bf5d5c38a5f229 Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:32:50 +0200 Subject: [PATCH 10/25] fixed frontend for new module api endpoint and fixed distinct database request --- backend/service/addSchedule.go | 2 +- backend/service/db/dbEvents.go | 4 ++-- backend/service/events/eventService.go | 4 +++- frontend/src/components/AdditionalModules.vue | 2 +- frontend/src/components/RenameModules.vue | 6 +++--- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/backend/service/addSchedule.go b/backend/service/addSchedule.go index a7a2ffa..4cf41bc 100644 --- a/backend/service/addSchedule.go +++ b/backend/service/addSchedule.go @@ -16,7 +16,7 @@ func AddSchedules(app *pocketbase.PocketBase) { // Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *" // Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *" // Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *" - scheduler.MustAdd("updateCourse", "*/30 * * * *", func() { + scheduler.MustAdd("updateCourse", "0 */3 * * *", func() { courses := events.GetAllCourses(app) diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index d1427ca..3079fbd 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -115,10 +115,10 @@ func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester return events, nil } -func GetAllModulesDistinct(app *pocketbase.PocketBase) (model.Events, error) { +func GetAllModulesDistinctByNameAndCourse(app *pocketbase.PocketBase) (model.Events, error) { var events model.Events - err := app.Dao().DB().Select("*").From("events").Distinct(true).All(&events) + err := app.Dao().DB().Select("*").From("events").GroupBy("Name", "course").Distinct(true).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 48efbcd..450ae43 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -28,8 +28,10 @@ func replaceEmptyEntry(modules model.Events, replacement string) { } } +// GetAllModulesDistinct returns all modules distinct by name and course from the database +// That means you get all modules with duplicates if they have different courses func GetAllModulesDistinct(app *pocketbase.PocketBase, c echo.Context) error { - modules, err := db.GetAllModulesDistinct(app) + modules, err := db.GetAllModulesDistinctByNameAndCourse(app) replaceEmptyEntry(modules, "Sonderveranstaltungen") diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/components/AdditionalModules.vue index c9ef74b..dd49a71 100644 --- a/frontend/src/components/AdditionalModules.vue +++ b/frontend/src/components/AdditionalModules.vue @@ -30,7 +30,7 @@ async function nextStep() { await router.push("/rename-modules"); } -const display = (module: Module) => module.Name + " (" + module.Course + ")"; +const display = (module: Module) => module.name + " (" + module.course + ")"; const selectAll = ref(false); diff --git a/frontend/src/components/RenameModules.vue b/frontend/src/components/RenameModules.vue index 1d46a96..c246ff9 100644 --- a/frontend/src/components/RenameModules.vue +++ b/frontend/src/components/RenameModules.vue @@ -7,7 +7,7 @@ import { ref } from "vue"; const tableData = ref(moduleStore().modules.map((module) => { return { - Course: module.Course, + Course: module.course, Module: module, } }) @@ -36,14 +36,14 @@ async function finalStep() { From 1b5353f3b841e04ec3b8d5edc82a86b0ed6b7c6f Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:08:18 +0200 Subject: [PATCH 11/25] added type to ical Description --- backend/service/ical/icalFileGeneration.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go index 0279bf7..14d64ba 100644 --- a/backend/service/ical/icalFileGeneration.go +++ b/backend/service/ical/icalFileGeneration.go @@ -62,6 +62,9 @@ func generateDescription(event *model.Event) string { if !functions.OnlyWhitespace(event.Course) { description += "Gruppe: " + event.Course + "\n" } + if !functions.OnlyWhitespace(event.EventType) { + description += "Typ: " + event.EventType + "\n" + } return description } From b9acdd3a3437048bd2e9bb23d28cafc1dc8fa1b2 Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:58:43 +0200 Subject: [PATCH 12/25] added information button --- frontend/src/components/AdditionalModules.vue | 47 +++++++++++++++++-- frontend/src/components/ModuleInformation.vue | 27 +++++++++++ frontend/src/main.ts | 4 ++ 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/ModuleInformation.vue diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/components/AdditionalModules.vue index dd49a71..94d9ca1 100644 --- a/frontend/src/components/AdditionalModules.vue +++ b/frontend/src/components/AdditionalModules.vue @@ -1,10 +1,13 @@ + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts index bbcebbf..1ad0267 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -23,6 +23,8 @@ import Accordion from 'primevue/accordion'; import AccordionTab from 'primevue/accordiontab'; import DataTable from "primevue/datatable"; import Column from "primevue/column"; +import DynamicDialog from 'primevue/dynamicdialog'; +import DialogService from 'primevue/dialogservice'; const app = createApp(App); const pinia = createPinia(); @@ -31,6 +33,7 @@ app.use(PrimeVue); app.use(router); app.use(ToastService); app.use(pinia); +app.use(DialogService); app.component("Button", Button); app.component("Menubar", Menubar); app.component("Dropdown", Dropdown); @@ -46,4 +49,5 @@ app.component("Accordion", Accordion); app.component("AccordionTab", AccordionTab); app.component("DataTable", DataTable); app.component("Column", Column); +app.component("DynamicDialog", DynamicDialog); app.mount("#app"); From 87b51fd6fe4740a019fb9cc347886d9bce3d8ea0 Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Tue, 17 Oct 2023 00:04:10 +0200 Subject: [PATCH 13/25] added dialog information transfer --- frontend/src/api/fetchCourse.ts | 4 ++-- frontend/src/components/AdditionalModules.vue | 10 ++++++---- frontend/src/components/ModuleInformation.vue | 20 +++++++++---------- frontend/src/model/module.ts | 1 - 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/api/fetchCourse.ts b/frontend/src/api/fetchCourse.ts index b8c9da4..b3d13c7 100644 --- a/frontend/src/api/fetchCourse.ts +++ b/frontend/src/api/fetchCourse.ts @@ -25,7 +25,7 @@ export async function fetchModulesByCourseAndSemester( }) .then((modulesResponse) => { modulesResponse.forEach((module: Module) => - modules.push(new Module(module.id, module.name, course, module.name)), + modules.push(new Module(module.name, course, module.name)), ); }); return modules; @@ -39,7 +39,7 @@ export async function fetchAllModules(): Promise { }) .then((responseModules: Module[]) => { responseModules.forEach((module: Module) => { - modules.push(new Module(module.id, module.name, module.course, module.name)); + modules.push(new Module(module.name, module.course, module.name)); }); }); diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/components/AdditionalModules.vue index 94d9ca1..15aa235 100644 --- a/frontend/src/components/AdditionalModules.vue +++ b/frontend/src/components/AdditionalModules.vue @@ -38,10 +38,9 @@ const ModuleInformation = defineAsyncComponent( ); //TODO add missing module prop informations for ModuleInformation.vue -const showInfo = () => { +function showInfo(module : Module) { dialog.open(ModuleInformation, { props: { - header: "Product List", style: { width: "50vw", }, @@ -51,8 +50,11 @@ const showInfo = () => { }, modal: true, }, + data: { + module: module, + }, }); -}; +} const display = (module: Module) => module.name + " (" + module.course + ")"; @@ -106,7 +108,7 @@ function selectChange() { rounded outlined aria-label="Information" - @click.stop="showInfo()" + @click.stop="showInfo(slotProps.option)" >
diff --git a/frontend/src/components/ModuleInformation.vue b/frontend/src/components/ModuleInformation.vue index 3d8b8db..ee300e5 100644 --- a/frontend/src/components/ModuleInformation.vue +++ b/frontend/src/components/ModuleInformation.vue @@ -1,26 +1,24 @@