diff --git a/services/data-manager/main.go b/services/data-manager/main.go index 1e50b50..a5fbcf6 100644 --- a/services/data-manager/main.go +++ b/services/data-manager/main.go @@ -20,7 +20,9 @@ import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/plugins/migratecmd" _ "htwkalender/data-manager/migrations" + "htwkalender/data-manager/model/serviceModel" "htwkalender/data-manager/service" + "htwkalender/data-manager/service/events" "htwkalender/data-manager/service/grpc" "log/slog" "os" @@ -29,6 +31,14 @@ import ( func setupApp() *pocketbase.PocketBase { app := pocketbase.New() + courseService := events.NewPocketBaseCourseService(app) + eventService := events.NewPocketBaseEventService(app) + + services := serviceModel.Service{ + CourseService: courseService, + EventService: eventService, + App: app, + } // loosely check if it was executed using "go run" isGoRun := strings.HasPrefix(os.Args[0], os.TempDir()) @@ -41,8 +51,8 @@ func setupApp() *pocketbase.PocketBase { // (the isGoRun check is to enable it only during development) Automigrate: isGoRun, }) - service.AddRoutes(app) - service.AddSchedules(app) + service.AddRoutes(services) + service.AddSchedules(services) return app } diff --git a/services/data-manager/model/eventModel_test.go b/services/data-manager/model/eventModel_test.go index 2749143..d67cd00 100644 --- a/services/data-manager/model/eventModel_test.go +++ b/services/data-manager/model/eventModel_test.go @@ -37,25 +37,25 @@ func TestEventsContains(t *testing.T) { want bool }{ { - name: "empty events", + name: "event contains empty events", m: Events{}, args: args{event: Event{}}, want: false, }, { - name: "one event", + name: "event contains one event", m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { - name: "two events", + name: "event contains two events", m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: true, }, { - name: "two events with different values", + name: "event contains two events with different values", m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, args: args{event: Event{Day: "test3", Week: "test3", Start: specificTime, End: specificTime, Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, want: false, @@ -99,25 +99,25 @@ func TestEventEquals(t *testing.T) { want bool }{ { - name: "empty events", + name: "event equals empty events", fields: fields{}, args: args{event: Event{}}, want: true, }, { - name: "one empty one not", + name: "event equals one empty one not", fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, args: args{event: Event{}}, want: false, }, { - name: "one event", + name: "event equals one event", fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { - name: "two events", + name: "event equals two events", fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: false, @@ -172,22 +172,22 @@ func TestEventAnonymizeEvent(t *testing.T) { want AnonymizedEventDTO }{ { - name: "empty event", + name: "event anonymize empty event", fields: fields{}, want: AnonymizedEventDTO{Day: "", Week: "", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "", Free: false}, }, { - name: "one event", + name: "event anonymize one event", fields: fields{Name: "Event", Day: "test", Week: "test", Rooms: "test"}, want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: false}, }, { - name: "one event with free", + name: "event anonymize one event with free", fields: fields{Name: "Räume zur freien Verfügung", Day: "test", Week: "test", Rooms: "test", Course: "test"}, want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: true}, }, { - name: "another free event", + name: "event anonymize another free event", fields: fields{Name: "Zur freien Verfügung", Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Course: "42INM-3"}, want: AnonymizedEventDTO{Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Free: true}, }, @@ -242,12 +242,12 @@ func TestEventGetName(t *testing.T) { want string }{ { - name: "empty event", + name: "event get name - empty event", fields: fields{}, want: "", }, { - name: "one event", + name: "event get name - one event", fields: fields{Name: "Event"}, want: "Event", }, @@ -464,7 +464,7 @@ func TestEventsContains1(t *testing.T) { want bool }{ { - name: "empty events", + name: "event contains - empty events", m: Events{}, args: args{event: Event{}}, want: false, diff --git a/services/data-manager/model/serviceModel/serviceModel.go b/services/data-manager/model/serviceModel/serviceModel.go new file mode 100644 index 0000000..8e2fe69 --- /dev/null +++ b/services/data-manager/model/serviceModel/serviceModel.go @@ -0,0 +1,12 @@ +package serviceModel + +import ( + "github.com/pocketbase/pocketbase" + "htwkalender/data-manager/service/events" +) + +type Service struct { + App *pocketbase.PocketBase + EventService events.EventService + CourseService events.CourseService +} diff --git a/services/data-manager/service/addRoute.go b/services/data-manager/service/addRoute.go index 9c5c068..333b287 100644 --- a/services/data-manager/service/addRoute.go +++ b/services/data-manager/service/addRoute.go @@ -17,8 +17,8 @@ package service import ( + "htwkalender/data-manager/model/serviceModel" "htwkalender/data-manager/service/course" - "htwkalender/data-manager/service/events" "htwkalender/data-manager/service/fetch/sport" v1 "htwkalender/data-manager/service/fetch/v1" v2 "htwkalender/data-manager/service/fetch/v2" @@ -28,19 +28,18 @@ import ( "net/http" "github.com/labstack/echo/v5" - "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" ) -func AddRoutes(app *pocketbase.PocketBase) { +func AddRoutes(services serviceModel.Service) { - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/events", Handler: func(c echo.Context) error { - savedEvents, err := v2.ParseEventsFromRemote(app) + savedEvents, err := v2.ParseEventsFromRemote(services.App) if err != nil { slog.Error("Failed to parse events from remote: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse events from remote") @@ -49,7 +48,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) @@ -59,16 +58,16 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/daily/events", Handler: func(c echo.Context) error { - course.UpdateCourse(app) + course.UpdateCourse(services) return c.JSON(http.StatusOK, "Daily events fetched") }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) @@ -78,19 +77,19 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/groups", Handler: func(c echo.Context) error { - groups, err := v1.FetchSeminarGroups(app) + groups, err := v1.FetchSeminarGroups(services.App) if err != nil { return c.JSON(http.StatusBadRequest, "Failed to fetch seminar groups") } return c.JSON(http.StatusOK, groups) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) @@ -100,20 +99,20 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/sports", Handler: func(c echo.Context) error { - sportEvents, err := sport.FetchAndUpdateSportEvents(app) + sportEvents, err := sport.FetchAndUpdateSportEvents(services.App) if err != nil { return c.JSON(http.StatusBadRequest, "Failed to fetch sport events") } return c.JSON(http.StatusOK, sportEvents) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) @@ -123,19 +122,19 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodDelete, Path: "/api/modules", Handler: func(c echo.Context) error { - err := events.DeleteAllEvents(app) + err := services.EventService.DeleteAllEvents() if err != nil { return c.JSON(http.StatusBadRequest, "Failed to delete events") } return c.JSON(http.StatusOK, "Events deleted") }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) @@ -145,19 +144,19 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/rooms", Handler: func(c echo.Context) error { - rooms, err := room.GetRooms(app) + rooms, err := room.GetRooms(services.App) if err != nil { return c.JSON(http.StatusBadRequest, "Failed to get rooms") } return c.JSON(http.StatusOK, rooms) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -167,14 +166,14 @@ func AddRoutes(app *pocketbase.PocketBase) { }) // API Endpoint to get all events for a specific room on a specific day - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/schedule/day", Handler: func(c echo.Context) error { roomParam := c.QueryParam("room") date := c.QueryParam("date") - roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date) + roomSchedule, err := room.GetRoomScheduleForDay(services.App, roomParam, date) if err != nil { slog.Error("Failed to get room schedule for day: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day") @@ -182,7 +181,7 @@ func AddRoutes(app *pocketbase.PocketBase) { return c.JSON(http.StatusOK, roomSchedule) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -192,7 +191,7 @@ func AddRoutes(app *pocketbase.PocketBase) { }) // API Endpoint to create a new iCal feed - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/schedule", @@ -200,7 +199,7 @@ func AddRoutes(app *pocketbase.PocketBase) { roomParam := c.QueryParam("room") to := c.QueryParam("to") from := c.QueryParam("from") - roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to) + roomSchedule, err := room.GetRoomSchedule(services.App, roomParam, from, to) if err != nil { slog.Error("Failed to get room schedule:", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule") @@ -208,7 +207,7 @@ func AddRoutes(app *pocketbase.PocketBase) { return c.JSON(http.StatusOK, roomSchedule) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -218,7 +217,7 @@ func AddRoutes(app *pocketbase.PocketBase) { }) // API Endpoint to get all rooms that have no events in a specific time frame - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/rooms/free", @@ -233,7 +232,7 @@ func AddRoutes(app *pocketbase.PocketBase) { slog.Error("Failed to parse time: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse time") } - rooms, err := room.GetFreeRooms(app, from, to) + rooms, err := room.GetFreeRooms(services.App, from, to) if err != nil { slog.Error("Failed to get free rooms: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get free rooms") @@ -241,7 +240,7 @@ func AddRoutes(app *pocketbase.PocketBase) { return c.JSON(http.StatusOK, rooms) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -250,15 +249,14 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - addFeedRoutes(app) + addFeedRoutes(services.App) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/course/modules", Handler: func(c echo.Context) error { - modules, err := events.GetModulesForCourseDistinct( - app, + modules, err := services.EventService.GetModulesForCourseDistinct( c.QueryParam("course"), c.QueryParam("semester"), ) @@ -271,7 +269,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -280,12 +278,12 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/modules", Handler: func(c echo.Context) error { - modules, err := events.GetAllModulesDistinct(app) + modules, err := services.EventService.GetAllModulesDistinct() if err != nil { slog.Error("Failed to get modules: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get modules") @@ -293,7 +291,7 @@ func AddRoutes(app *pocketbase.PocketBase) { return c.JSON(http.StatusOK, modules) }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -302,13 +300,13 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.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 { requestModule := c.QueryParam("uuid") - module, err := events.GetModuleByUUID(app, requestModule) + module, err := services.EventService.GetModuleByUUID(requestModule) if err != nil { slog.Error("Failed to get module: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get module") @@ -317,7 +315,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -326,17 +324,17 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.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 { semester := c.QueryParam("semester") if semester == "" { - courses := events.GetAllCourses(app) + courses := services.CourseService.GetAllCourses() return c.JSON(200, courses) } else { - seminarGroups := events.GetAllCoursesForSemester(app, semester) + seminarGroups := services.CourseService.GetAllCoursesForSemester(semester) courseStringList := make([]string, 0) for _, seminarGroup := range seminarGroups { courseStringList = append(courseStringList, seminarGroup.Course) @@ -345,7 +343,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -355,14 +353,13 @@ func AddRoutes(app *pocketbase.PocketBase) { }) // api end point to get all courses for a specific semester with courses that have events - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/courses/events", Handler: func(c echo.Context) error { semester := c.QueryParam("semester") - courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester) - + courses, err := services.CourseService.GetAllCoursesForSemesterWithEvents(semester) if err != nil { slog.Error("Failed to get courses for semester with events: ", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events") @@ -371,7 +368,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -381,12 +378,12 @@ func AddRoutes(app *pocketbase.PocketBase) { }) // API Endpoint to get all eventTypes from the database distinct - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/events/types", Handler: func(c echo.Context) error { - eventTypes, err := events.GetEventTypes(app) + eventTypes, err := services.EventService.GetEventTypes() if err != nil { slog.Error("Failed to get event types", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get event types") @@ -395,7 +392,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), }, }) if err != nil { @@ -404,13 +401,12 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.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 { - err := events.DeleteAllEventsByCourseAndSemester( - app, + err := services.EventService.DeleteAllEventsByCourseAndSemester( c.QueryParam("course"), c.QueryParam("semester"), ) @@ -422,7 +418,7 @@ func AddRoutes(app *pocketbase.PocketBase) { } }, Middlewares: []echo.MiddlewareFunc{ - apis.ActivityLogger(app), + apis.ActivityLogger(services.App), apis.RequireAdminAuth(), }, }) diff --git a/services/data-manager/service/addSchedule.go b/services/data-manager/service/addSchedule.go index c24f2e1..da0bac4 100644 --- a/services/data-manager/service/addSchedule.go +++ b/services/data-manager/service/addSchedule.go @@ -17,9 +17,9 @@ package service import ( - "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/cron" + "htwkalender/data-manager/model/serviceModel" "htwkalender/data-manager/service/course" "htwkalender/data-manager/service/feed" "htwkalender/data-manager/service/fetch/sport" @@ -30,9 +30,9 @@ import ( "strconv" ) -func AddSchedules(app *pocketbase.PocketBase) { +func AddSchedules(services serviceModel.Service) { - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { scheduler := cron.New() // !! IMPORTANT !! CRON is based on UTC time zone so in Germany it is UTC+2 in summer and UTC+1 in winter @@ -40,7 +40,7 @@ func AddSchedules(app *pocketbase.PocketBase) { // Every sunday at 10pm update all courses (5 segments - minute, hour, day, month, weekday) "0 22 * * 0" scheduler.MustAdd("updateCourses", "0 22 * * 0", func() { slog.Info("Started updating courses schedule") - groups, err := v1.FetchSeminarGroups(app) + groups, err := v1.FetchSeminarGroups(services.App) if err != nil { slog.Warn("Failed to fetch seminar groups: ", "error", err) } @@ -51,20 +51,20 @@ func AddSchedules(app *pocketbase.PocketBase) { // In Germany it is 7am and 7pm, syllabus gets updated twice a day at German 5:00 Uhr and 17:00 Uhr scheduler.MustAdd("updateEventsByCourse", "0 5,17 * * *", func() { slog.Info("Started updating courses schedule") - course.UpdateCourse(app) + course.UpdateCourse(services) }) // Every sunday at 1am clean all courses (5 segments - minute, hour, day, month, weekday) "0 3 * * 0" scheduler.MustAdd("cleanFeeds", "0 1 * * 0", func() { // clean feeds older than 6 months slog.Info("Started cleaning feeds schedule") - feed.ClearFeeds(app.Dao(), 6, time.RealClock{}) + feed.ClearFeeds(services.App.Dao(), 6, time.RealClock{}) }) // Every sunday at 3am fetch all sport events (5 segments - minute, hour, day, month, weekday) "0 2 * * 0" scheduler.MustAdd("fetchSportEvents", "0 3 * * 0", func() { slog.Info("Started fetching sport events schedule") - sportEvents, err := sport.FetchAndUpdateSportEvents(app) + sportEvents, err := sport.FetchAndUpdateSportEvents(services.App) if err != nil { slog.Error("Failed to fetch and save sport events:", "error", err) } @@ -73,7 +73,7 @@ func AddSchedules(app *pocketbase.PocketBase) { //fetch all events for semester and delete from remote this should be done every sunday at 2am scheduler.MustAdd("fetchEvents", "0 22 * * 6", func() { - savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{}) + savedEvents, err := v2.FetchAllEventsAndSave(services.App, time.RealClock{}) if err != nil { slog.Error("Failed to fetch and save events: ", "error", err) } else { diff --git a/services/data-manager/service/course/courseFunctions.go b/services/data-manager/service/course/courseFunctions.go index 22febd4..c7cc543 100644 --- a/services/data-manager/service/course/courseFunctions.go +++ b/services/data-manager/service/course/courseFunctions.go @@ -17,25 +17,25 @@ package course import ( - "github.com/pocketbase/pocketbase" "htwkalender/data-manager/model" "htwkalender/data-manager/service/events" "htwkalender/data-manager/service/functions" "htwkalender/data-manager/service/functions/time" + "htwkalender/data-manager/model/serviceModel" "log/slog" ) -func UpdateCourse(app *pocketbase.PocketBase) { +func UpdateCourse(service serviceModel.Service) { currentSemesters := functions.CalculateSemesterList(time.RealClock{}) var seminarGroups []model.SeminarGroup for _, semester := range currentSemesters { - seminarGroups = append(seminarGroups, events.GetAllCoursesForSemester(app, semester)...) + seminarGroups = append(seminarGroups, service.EventService.GetAllCoursesForSemester(semester)...) } for _, seminarGroup := range seminarGroups { - _, err := events.UpdateModulesForCourse(app, seminarGroup) + _, err := service.EventService.UpdateModulesForCourse(seminarGroup) if err != nil { slog.Warn("Update Course: "+seminarGroup.Course+" failed:", "error", err) } diff --git a/services/data-manager/service/course/courseFunctions_test.go b/services/data-manager/service/course/courseFunctions_test.go new file mode 100644 index 0000000..846efcc --- /dev/null +++ b/services/data-manager/service/course/courseFunctions_test.go @@ -0,0 +1,97 @@ +package course + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/require" + "htwkalender/data-manager/model" + "htwkalender/data-manager/model/serviceModel" + "htwkalender/data-manager/service/events/mock" + "log/slog" + "regexp" + "testing" +) + +// CustomWriter is a custom writer to capture log output +type CustomWriter struct { + Buffer bytes.Buffer +} + +func (w *CustomWriter) Write(p []byte) (n int, err error) { + return w.Buffer.Write(p) +} + +func TestUpdateCourse(t *testing.T) { + // Create mock services + mockCourseService := new(mock.MockCourseService) + mockEventService := new(mock.MockEventService) + + events := model.Events{} + + // Set up expectations + mockCourseService.On("GetAllCourses").Return([]string{"Course1", "Course2"}) + mockEventService.On("UpdateModulesForCourse", "Course1").Return(events, nil) + mockEventService.On("UpdateModulesForCourse", "Course2").Return(events, nil) + + // Inject mocks into the UpdateCourse function + service := serviceModel.Service{ + CourseService: mockCourseService, + EventService: mockEventService, + App: nil, + } + UpdateCourse(service) + + // Assert that the expectations were met + mockCourseService.AssertExpectations(t) + mockEventService.AssertExpectations(t) + + // Assert that the UpdateCourse function was called twice + mockCourseService.AssertNumberOfCalls(t, "GetAllCourses", 1) + mockEventService.AssertNumberOfCalls(t, "UpdateModulesForCourse", 2) + + // Assert that the UpdateCourse function was called with the correct arguments + mockEventService.AssertCalled(t, "UpdateModulesForCourse", "Course1") + mockEventService.AssertCalled(t, "UpdateModulesForCourse", "Course2") +} + +func TestUpdateCourseErr(t *testing.T) { + // Create mock services + mockCourseService := new(mock.MockCourseService) + mockEventService := new(mock.MockEventService) + + events := model.Events{} + + // Set up expectations + mockCourseService.On("GetAllCourses").Return([]string{"Course1", "Course2"}) + mockEventService.On("UpdateModulesForCourse", "Course1").Return(events, fmt.Errorf("error")) + mockEventService.On("UpdateModulesForCourse", "Course2").Return(events, fmt.Errorf("error")) + + // Create a custom writer to capture log output + customWriter := &CustomWriter{} + originalLogger := slog.Default() + defer slog.SetDefault(originalLogger) + + // Replace the default logger with a custom logger + slog.SetDefault(slog.New(slog.NewTextHandler(customWriter, nil))) + + // Inject mocks into the UpdateCourse function + service := serviceModel.Service{ + CourseService: mockCourseService, + EventService: mockEventService, + App: nil, + } + UpdateCourse(service) + + // Assert that the expectations were met + mockCourseService.AssertExpectations(t) + mockEventService.AssertExpectations(t) + + // Assert that the UpdateCourse function was called twice + mockCourseService.AssertNumberOfCalls(t, "GetAllCourses", 1) + mockEventService.AssertNumberOfCalls(t, "UpdateModulesForCourse", 2) + + // Check the captured log output for the expected messages + logOutput := customWriter.Buffer.String() + require.Regexp(t, regexp.MustCompile(`Update Course: Course1 failed:.*error`), logOutput) + require.Regexp(t, regexp.MustCompile(`Update Course: Course2 failed:.*error`), logOutput) +} diff --git a/services/data-manager/service/events/courseService.go b/services/data-manager/service/events/courseService.go index ea291fd..415f8b1 100644 --- a/services/data-manager/service/events/courseService.go +++ b/services/data-manager/service/events/courseService.go @@ -23,32 +23,51 @@ import ( "htwkalender/data-manager/service/functions" ) -func GetAllCourses(app *pocketbase.PocketBase) []string { - return db.GetAllCourses(app) +// CourseService defines the methods to be implemented +type CourseService interface { + GetAllCourses() []string + GetAllCoursesForSemester(semester string) []string + GetAllCoursesForSemesterWithEvents(semester string) ([]string, error) } -func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []model.SeminarGroup { - return db.GetAllCoursesForSemester(app, semester) +// PocketBaseCourseService is a struct that implements the CourseService interface +type PocketBaseCourseService struct { + app *pocketbase.PocketBase } -func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester string) ([]string, error) { - courses, err := db.GetAllCoursesForSemesterWithEvents(app, semester) +// NewPocketBaseCourseService creates a new PocketBaseCourseService +func NewPocketBaseCourseService(app *pocketbase.PocketBase) *PocketBaseCourseService { + return &PocketBaseCourseService{app: app} +} + +// GetAllCourses returns all courses +func (s *PocketBaseCourseService) GetAllCourses() []string { + return db.GetAllCourses(s.app) +} + +// GetAllCoursesForSemester returns all courses for a specific semester +func (s *PocketBaseCourseService) GetAllCoursesForSemester(semester string) []model.SeminarGroup { + return db.GetAllCoursesForSemester(s.app, semester) +} + +// GetAllCoursesForSemesterWithEvents returns all courses for a specific semester with events +func (s *PocketBaseCourseService) GetAllCoursesForSemesterWithEvents(semester string) ([]string, error) { + courses, err := db.GetAllCoursesForSemesterWithEvents(s.app, semester) if err != nil { return nil, err - } else { - // remove empty courses like " " or "" - courses = removeEmptyCourses(courses) - return courses, nil } + // remove empty courses like " " or "" + courses = removeEmptyCourses(courses) + return courses, nil } // removeEmptyCourses removes empty courses from the list of courses func removeEmptyCourses(courses []string) []string { var filteredCourses []string - for index, course := range courses { + for _, course := range courses { if !functions.OnlyWhitespace(course) || len(course) != 0 { - filteredCourses = append(filteredCourses, courses[index]) + filteredCourses = append(filteredCourses, course) } } return filteredCourses diff --git a/services/data-manager/service/events/eventService.go b/services/data-manager/service/events/eventService.go index ab82d52..f0a3534 100644 --- a/services/data-manager/service/events/eventService.go +++ b/services/data-manager/service/events/eventService.go @@ -26,9 +26,31 @@ import ( "strconv" ) -func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) { +type EventService interface { + GetModulesForCourseDistinct(course string, semester string) (model.Events, error) + GetAllModulesDistinct() ([]model.ModuleDTO, error) + GetModuleByUUID(uuid string) (model.Module, error) + DeleteAllEventsByCourseAndSemester(course string, semester string) error + DeleteAllEvents() error + UpdateModulesForCourse(seminarGroup model.SeminarGroup) (model.Events, error) + GetEventTypes() ([]string, error) +} - modules, err := db.GetAllModulesForCourse(app, course, semester) +type Named interface { + GetName() string + SetName(name string) +} + +type PocketBaseEventService struct { + app *pocketbase.PocketBase +} + +func NewPocketBaseEventService(app *pocketbase.PocketBase) *PocketBaseEventService { + return &PocketBaseEventService{app: app} +} + +func (s *PocketBaseEventService) GetModulesForCourseDistinct(course string, semester string) (model.Events, error) { + modules, err := db.GetAllModulesForCourse(s.app, course, semester) // Convert the []model.Module to []Named var namedEvents []Named @@ -40,11 +62,6 @@ func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, seme return modules, err } -type Named interface { - GetName() string - SetName(name string) -} - // replaceEmptyEntry replaces an empty entry in a module with a replacement string // If the module is not empty, nothing happens func replaceEmptyEntry(namedList []Named, replacement string) { @@ -57,8 +74,8 @@ func replaceEmptyEntry(namedList []Named, 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) ([]model.ModuleDTO, error) { - modules, err := db.GetAllModulesDistinctByNameAndCourse(app) +func (s *PocketBaseEventService) GetAllModulesDistinct() ([]model.ModuleDTO, error) { + modules, err := db.GetAllModulesDistinctByNameAndCourse(s.app) if err != nil { return nil, err } @@ -70,13 +87,13 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]model.ModuleDTO, error return modules, nil } -func GetModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, error) { - module, findModuleErr := db.FindModuleByUUID(app, uuid) +func (s *PocketBaseEventService) GetModuleByUUID(uuid string) (model.Module, error) { + module, findModuleErr := db.FindModuleByUUID(s.app, uuid) if findModuleErr != nil { return model.Module{}, findModuleErr } - events, findEventsError := db.FindAllEventsByModule(app, module) + events, findEventsError := db.FindAllEventsByModule(s.app, module) if findEventsError != nil || len(events) == 0 { return model.Module{}, findEventsError } else { @@ -94,8 +111,8 @@ func GetModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, err // 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.DeleteAllEventsByCourse(app.Dao(), course, semester) +func (s *PocketBaseEventService) DeleteAllEventsByCourseAndSemester(course string, semester string) error { + err := db.DeleteAllEventsByCourse(s.app.Dao(), course, semester) if err != nil { return err } else { @@ -103,8 +120,8 @@ func DeleteAllEventsByCourseAndSemester(app *pocketbase.PocketBase, course strin } } -func DeleteAllEvents(app *pocketbase.PocketBase) error { - err := db.DeleteAllEvents(app) +func (s *PocketBaseEventService) DeleteAllEvents() error { + err := db.DeleteAllEvents(s.app) if err != nil { return err } else { @@ -120,7 +137,7 @@ func DeleteAllEvents(app *pocketbase.PocketBase) error { // 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, seminarGroup model.SeminarGroup) (model.Events, error) { +func (s *PocketBaseEventService) UpdateModulesForCourse(seminarGroup model.SeminarGroup) (model.Events, error) { seminarGroup, err := v1.FetchAndParse(seminarGroup.Semester, seminarGroup.Course) if err != nil { @@ -134,14 +151,14 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, seminarGroup model.Semin //if there are no events in the database, save the new events //get all events for the course and the semester - dbEvents, err := db.GetAllEventsForCourse(app, seminarGroup.Course) + dbEvents, err := db.GetAllEventsForCourse(s.app, seminarGroup.Course) if err != nil { return nil, err } //if there are no events in the database, save the new events if len(dbEvents) == 0 { - events, dbError := db.SaveSeminarGroupEvents(seminarGroup, app) + events, dbError := db.SaveSeminarGroupEvents(seminarGroup, s.app) if dbError != nil { return nil, dbError } @@ -154,27 +171,27 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, seminarGroup model.Semin // check which events are not already in the database and need to be inserted/saved for _, event := range seminarGroup.Events { - if !ContainsEvent(dbEvents, event) { + if !containsEvent(dbEvents, event) { insertList = append(insertList, event) } } // check which events are in the database but not in the seminarGroup and need to be deleted for _, dbEvent := range dbEvents { - if !ContainsEvent(seminarGroup.Events, dbEvent) { + if !containsEvent(seminarGroup.Events, dbEvent) { deleteList = append(deleteList, dbEvent) } } // delete all events that are in the deleteList - err = db.DeleteEvents(deleteList, app) + err = db.DeleteEvents(deleteList, s.app) if err != nil { slog.Error("Failed to delete events:", "error", err) return nil, err } // save all events that are in the insertList - savedEvents, err := db.SaveEvents(insertList, app.Dao()) + savedEvents, err := db.SaveEvents(insertList, s.app.Dao()) if err != nil { slog.Error("Failed to save events: ", "error", err) return nil, err @@ -184,7 +201,7 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, seminarGroup model.Semin return savedEvents, nil } -func ContainsEvent(events model.Events, event model.Event) bool { +func containsEvent(events model.Events, event model.Event) bool { for _, e := range events { if e.Name == event.Name && e.Prof == event.Prof && @@ -199,8 +216,8 @@ func ContainsEvent(events model.Events, event model.Event) bool { return false } -func GetEventTypes(app *pocketbase.PocketBase) ([]string, error) { - dbEventTypes, err := db.GetAllEventTypes(app) +func (s *PocketBaseEventService) GetEventTypes() ([]string, error) { + dbEventTypes, err := db.GetAllEventTypes(s.app) if err != nil { return nil, err } diff --git a/services/data-manager/service/events/eventService_test.go b/services/data-manager/service/events/eventService_test.go index e4f7b68..d64d58c 100644 --- a/services/data-manager/service/events/eventService_test.go +++ b/services/data-manager/service/events/eventService_test.go @@ -50,8 +50,8 @@ func TestContainsEvent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ContainsEvent(tt.args.events, tt.args.event); got != tt.want { - t.Errorf("ContainsEvent() = %v, want %v", got, tt.want) + if got := containsEvent(tt.args.events, tt.args.event); got != tt.want { + t.Errorf("containsEvent() = %v, want %v", got, tt.want) } }) } diff --git a/services/data-manager/service/events/mock/courseMock.go b/services/data-manager/service/events/mock/courseMock.go new file mode 100644 index 0000000..d01c113 --- /dev/null +++ b/services/data-manager/service/events/mock/courseMock.go @@ -0,0 +1,66 @@ +package mock + +import ( + "github.com/stretchr/testify/mock" + "htwkalender/data-manager/model" +) + +// MockCourseService is a mock implementation of the CourseService interface +type MockCourseService struct { + mock.Mock +} + +func (m *MockCourseService) GetAllCourses() []string { + args := m.Called() + return args.Get(0).([]string) +} + +func (m *MockCourseService) GetAllCoursesForSemester(semester string) []string { + args := m.Called(semester) + return args.Get(0).([]string) +} + +func (m *MockCourseService) GetAllCoursesForSemesterWithEvents(semester string) ([]string, error) { + args := m.Called(semester) + return args.Get(0).([]string), args.Error(1) +} + +// MockEventService is a mock implementation of the EventService interface +type MockEventService struct { + mock.Mock +} + +func (m *MockEventService) GetModulesForCourseDistinct(course string, semester string) (model.Events, error) { + args := m.Called(course, semester) + return args.Get(0).(model.Events), args.Error(1) +} + +func (m *MockEventService) GetAllModulesDistinct() ([]model.ModuleDTO, error) { + args := m.Called() + return args.Get(0).([]model.ModuleDTO), args.Error(1) +} + +func (m *MockEventService) GetModuleByUUID(uuid string) (model.Module, error) { + args := m.Called(uuid) + return args.Get(0).(model.Module), args.Error(1) +} + +func (m *MockEventService) DeleteAllEventsByCourseAndSemester(course string, semester string) error { + args := m.Called(course, semester) + return args.Error(0) +} + +func (m *MockEventService) DeleteAllEvents() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockEventService) UpdateModulesForCourse(course string) (model.Events, error) { + args := m.Called(course) + return args.Get(0).(model.Events), args.Error(1) +} + +func (m *MockEventService) GetEventTypes() ([]string, error) { + args := m.Called() + return args.Get(0).([]string), args.Error(1) +} diff --git a/services/go.mod b/services/go.mod index 8d8ee37..a8571b7 100644 --- a/services/go.mod +++ b/services/go.mod @@ -12,6 +12,7 @@ require ( github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 github.com/pocketbase/dbx v1.10.1 github.com/pocketbase/pocketbase v0.22.12 + github.com/stretchr/testify v1.9.0 golang.org/x/net v0.26.0 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.34.1 @@ -41,6 +42,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 // indirect github.com/aws/smithy-go v1.20.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/disintegration/imaging v1.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -62,10 +64,12 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -83,6 +87,7 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.180.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect modernc.org/libc v1.50.5 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/services/go.sum b/services/go.sum index 50f1700..a97f4be 100644 --- a/services/go.sum +++ b/services/go.sum @@ -218,6 +218,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -366,6 +368,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=