Merge branch 'refs/heads/41-bug-all-events-vanish-if-the-official-endpoint-is-offline' into 46-event-update-issue-with-old-courses-in-group-table

# Conflicts:
#	services/data-manager/service/addRoute.go
#	services/data-manager/service/course/courseFunctions.go
#	services/data-manager/service/events/courseService.go
#	services/data-manager/service/events/eventService.go
This commit is contained in:
Elmar Kresse
2024-07-06 15:42:19 +02:00
13 changed files with 350 additions and 125 deletions

View File

@ -20,7 +20,9 @@ import (
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/plugins/migratecmd" "github.com/pocketbase/pocketbase/plugins/migratecmd"
_ "htwkalender/data-manager/migrations" _ "htwkalender/data-manager/migrations"
"htwkalender/data-manager/model/serviceModel"
"htwkalender/data-manager/service" "htwkalender/data-manager/service"
"htwkalender/data-manager/service/events"
"htwkalender/data-manager/service/grpc" "htwkalender/data-manager/service/grpc"
"log/slog" "log/slog"
"os" "os"
@ -29,6 +31,14 @@ import (
func setupApp() *pocketbase.PocketBase { func setupApp() *pocketbase.PocketBase {
app := pocketbase.New() 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" // loosely check if it was executed using "go run"
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir()) 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) // (the isGoRun check is to enable it only during development)
Automigrate: isGoRun, Automigrate: isGoRun,
}) })
service.AddRoutes(app) service.AddRoutes(services)
service.AddSchedules(app) service.AddSchedules(services)
return app return app
} }

View File

@ -37,25 +37,25 @@ func TestEventsContains(t *testing.T) {
want bool want bool
}{ }{
{ {
name: "empty events", name: "event contains empty events",
m: Events{}, m: Events{},
args: args{event: Event{}}, args: args{event: Event{}},
want: false, 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"}}, 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"}}, args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}},
want: true, 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"}}, 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"}}, args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}},
want: true, 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"}}, 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"}}, 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, want: false,
@ -99,25 +99,25 @@ func TestEventEquals(t *testing.T) {
want bool want bool
}{ }{
{ {
name: "empty events", name: "event equals empty events",
fields: fields{}, fields: fields{},
args: args{event: Event{}}, args: args{event: Event{}},
want: true, 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"}, fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"},
args: args{event: Event{}}, args: args{event: Event{}},
want: false, 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"}, 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"}}, args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}},
want: true, 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"}, 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"}}, args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}},
want: false, want: false,
@ -172,22 +172,22 @@ func TestEventAnonymizeEvent(t *testing.T) {
want AnonymizedEventDTO want AnonymizedEventDTO
}{ }{
{ {
name: "empty event", name: "event anonymize empty event",
fields: fields{}, fields: fields{},
want: AnonymizedEventDTO{Day: "", Week: "", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "", Free: false}, 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"}, 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}, 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"}, 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}, 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"}, 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}, 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 want string
}{ }{
{ {
name: "empty event", name: "event get name - empty event",
fields: fields{}, fields: fields{},
want: "", want: "",
}, },
{ {
name: "one event", name: "event get name - one event",
fields: fields{Name: "Event"}, fields: fields{Name: "Event"},
want: "Event", want: "Event",
}, },
@ -464,7 +464,7 @@ func TestEventsContains1(t *testing.T) {
want bool want bool
}{ }{
{ {
name: "empty events", name: "event contains - empty events",
m: Events{}, m: Events{},
args: args{event: Event{}}, args: args{event: Event{}},
want: false, want: false,

View File

@ -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
}

View File

@ -17,8 +17,8 @@
package service package service
import ( import (
"htwkalender/data-manager/model/serviceModel"
"htwkalender/data-manager/service/course" "htwkalender/data-manager/service/course"
"htwkalender/data-manager/service/events"
"htwkalender/data-manager/service/fetch/sport" "htwkalender/data-manager/service/fetch/sport"
v1 "htwkalender/data-manager/service/fetch/v1" v1 "htwkalender/data-manager/service/fetch/v1"
v2 "htwkalender/data-manager/service/fetch/v2" v2 "htwkalender/data-manager/service/fetch/v2"
@ -28,19 +28,18 @@ import (
"net/http" "net/http"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/fetch/events", Path: "/api/fetch/events",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
savedEvents, err := v2.ParseEventsFromRemote(app) savedEvents, err := v2.ParseEventsFromRemote(services.App)
if err != nil { if err != nil {
slog.Error("Failed to parse events from remote: ", "error", err) slog.Error("Failed to parse events from remote: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to parse events from remote") return c.JSON(http.StatusBadRequest, "Failed to parse events from remote")
@ -49,7 +48,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })
@ -59,16 +58,16 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/fetch/daily/events", Path: "/api/fetch/daily/events",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
course.UpdateCourse(app) course.UpdateCourse(services)
return c.JSON(http.StatusOK, "Daily events fetched") return c.JSON(http.StatusOK, "Daily events fetched")
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })
@ -78,19 +77,19 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/fetch/groups", Path: "/api/fetch/groups",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
groups, err := v1.FetchSeminarGroups(app) groups, err := v1.FetchSeminarGroups(services.App)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to fetch seminar groups") return c.JSON(http.StatusBadRequest, "Failed to fetch seminar groups")
} }
return c.JSON(http.StatusOK, groups) return c.JSON(http.StatusOK, groups)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })
@ -100,20 +99,20 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/fetch/sports", Path: "/api/fetch/sports",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
sportEvents, err := sport.FetchAndUpdateSportEvents(app) sportEvents, err := sport.FetchAndUpdateSportEvents(services.App)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to fetch sport events") return c.JSON(http.StatusBadRequest, "Failed to fetch sport events")
} }
return c.JSON(http.StatusOK, sportEvents) return c.JSON(http.StatusOK, sportEvents)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })
@ -123,19 +122,19 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete, Method: http.MethodDelete,
Path: "/api/modules", Path: "/api/modules",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
err := events.DeleteAllEvents(app) err := services.EventService.DeleteAllEvents()
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to delete events") return c.JSON(http.StatusBadRequest, "Failed to delete events")
} }
return c.JSON(http.StatusOK, "Events deleted") return c.JSON(http.StatusOK, "Events deleted")
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })
@ -145,19 +144,19 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/rooms", Path: "/api/rooms",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
rooms, err := room.GetRooms(app) rooms, err := room.GetRooms(services.App)
if err != nil { if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to get rooms") return c.JSON(http.StatusBadRequest, "Failed to get rooms")
} }
return c.JSON(http.StatusOK, rooms) return c.JSON(http.StatusOK, rooms)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { 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 // 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/schedule/day", Path: "/api/schedule/day",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
roomParam := c.QueryParam("room") roomParam := c.QueryParam("room")
date := c.QueryParam("date") date := c.QueryParam("date")
roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date) roomSchedule, err := room.GetRoomScheduleForDay(services.App, roomParam, date)
if err != nil { if err != nil {
slog.Error("Failed to get room schedule for day: ", "error", err) slog.Error("Failed to get room schedule for day: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day") 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) return c.JSON(http.StatusOK, roomSchedule)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -192,7 +191,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
}) })
// API Endpoint to create a new iCal feed // 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/schedule", Path: "/api/schedule",
@ -200,7 +199,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
roomParam := c.QueryParam("room") roomParam := c.QueryParam("room")
to := c.QueryParam("to") to := c.QueryParam("to")
from := c.QueryParam("from") from := c.QueryParam("from")
roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to) roomSchedule, err := room.GetRoomSchedule(services.App, roomParam, from, to)
if err != nil { if err != nil {
slog.Error("Failed to get room schedule:", "error", err) slog.Error("Failed to get room schedule:", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule") 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) return c.JSON(http.StatusOK, roomSchedule)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { 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 // 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/rooms/free", Path: "/api/rooms/free",
@ -233,7 +232,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
slog.Error("Failed to parse time: ", "error", err) slog.Error("Failed to parse time: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to parse time") 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 { if err != nil {
slog.Error("Failed to get free rooms: ", "error", err) slog.Error("Failed to get free rooms: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get free rooms") 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) return c.JSON(http.StatusOK, rooms)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -250,15 +249,14 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/course/modules", Path: "/api/course/modules",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
modules, err := events.GetModulesForCourseDistinct( modules, err := services.EventService.GetModulesForCourseDistinct(
app,
c.QueryParam("course"), c.QueryParam("course"),
c.QueryParam("semester"), c.QueryParam("semester"),
) )
@ -271,7 +269,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -280,12 +278,12 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/modules", Path: "/api/modules",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
modules, err := events.GetAllModulesDistinct(app) modules, err := services.EventService.GetAllModulesDistinct()
if err != nil { if err != nil {
slog.Error("Failed to get modules: ", "error", err) slog.Error("Failed to get modules: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get modules") return c.JSON(http.StatusBadRequest, "Failed to get modules")
@ -293,7 +291,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
return c.JSON(http.StatusOK, modules) return c.JSON(http.StatusOK, modules)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -302,13 +300,13 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/module", Path: "/api/module",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
requestModule := c.QueryParam("uuid") requestModule := c.QueryParam("uuid")
module, err := events.GetModuleByUUID(app, requestModule) module, err := services.EventService.GetModuleByUUID(requestModule)
if err != nil { if err != nil {
slog.Error("Failed to get module: ", "error", err) slog.Error("Failed to get module: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get module") return c.JSON(http.StatusBadRequest, "Failed to get module")
@ -317,7 +315,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -326,17 +324,17 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/courses", Path: "/api/courses",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
semester := c.QueryParam("semester") semester := c.QueryParam("semester")
if semester == "" { if semester == "" {
courses := events.GetAllCourses(app) courses := services.CourseService.GetAllCourses()
return c.JSON(200, courses) return c.JSON(200, courses)
} else { } else {
seminarGroups := events.GetAllCoursesForSemester(app, semester) seminarGroups := services.CourseService.GetAllCoursesForSemester(semester)
courseStringList := make([]string, 0) courseStringList := make([]string, 0)
for _, seminarGroup := range seminarGroups { for _, seminarGroup := range seminarGroups {
courseStringList = append(courseStringList, seminarGroup.Course) courseStringList = append(courseStringList, seminarGroup.Course)
@ -345,7 +343,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { 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 // 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/courses/events", Path: "/api/courses/events",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
semester := c.QueryParam("semester") semester := c.QueryParam("semester")
courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester) courses, err := services.CourseService.GetAllCoursesForSemesterWithEvents(semester)
if err != nil { if err != nil {
slog.Error("Failed to get courses for semester with events: ", "error", err) 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") 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{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -381,12 +378,12 @@ func AddRoutes(app *pocketbase.PocketBase) {
}) })
// API Endpoint to get all eventTypes from the database distinct // 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/events/types", Path: "/api/events/types",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
eventTypes, err := events.GetEventTypes(app) eventTypes, err := services.EventService.GetEventTypes()
if err != nil { if err != nil {
slog.Error("Failed to get event types", "error", err) slog.Error("Failed to get event types", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get event types") return c.JSON(http.StatusBadRequest, "Failed to get event types")
@ -395,7 +392,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
}, },
}) })
if err != nil { if err != nil {
@ -404,13 +401,12 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil 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{ _, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete, Method: http.MethodDelete,
Path: "/api/events", Path: "/api/events",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
err := events.DeleteAllEventsByCourseAndSemester( err := services.EventService.DeleteAllEventsByCourseAndSemester(
app,
c.QueryParam("course"), c.QueryParam("course"),
c.QueryParam("semester"), c.QueryParam("semester"),
) )
@ -422,7 +418,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(services.App),
apis.RequireAdminAuth(), apis.RequireAdminAuth(),
}, },
}) })

View File

@ -17,9 +17,9 @@
package service package service
import ( import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/cron" "github.com/pocketbase/pocketbase/tools/cron"
"htwkalender/data-manager/model/serviceModel"
"htwkalender/data-manager/service/course" "htwkalender/data-manager/service/course"
"htwkalender/data-manager/service/feed" "htwkalender/data-manager/service/feed"
"htwkalender/data-manager/service/fetch/sport" "htwkalender/data-manager/service/fetch/sport"
@ -30,9 +30,9 @@ import (
"strconv" "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() 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 // !! 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" // Every sunday at 10pm update all courses (5 segments - minute, hour, day, month, weekday) "0 22 * * 0"
scheduler.MustAdd("updateCourses", "0 22 * * 0", func() { scheduler.MustAdd("updateCourses", "0 22 * * 0", func() {
slog.Info("Started updating courses schedule") slog.Info("Started updating courses schedule")
groups, err := v1.FetchSeminarGroups(app) groups, err := v1.FetchSeminarGroups(services.App)
if err != nil { if err != nil {
slog.Warn("Failed to fetch seminar groups: ", "error", err) 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 // 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() { scheduler.MustAdd("updateEventsByCourse", "0 5,17 * * *", func() {
slog.Info("Started updating courses schedule") 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" // Every sunday at 1am clean all courses (5 segments - minute, hour, day, month, weekday) "0 3 * * 0"
scheduler.MustAdd("cleanFeeds", "0 1 * * 0", func() { scheduler.MustAdd("cleanFeeds", "0 1 * * 0", func() {
// clean feeds older than 6 months // clean feeds older than 6 months
slog.Info("Started cleaning feeds schedule") 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" // 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() { scheduler.MustAdd("fetchSportEvents", "0 3 * * 0", func() {
slog.Info("Started fetching sport events schedule") slog.Info("Started fetching sport events schedule")
sportEvents, err := sport.FetchAndUpdateSportEvents(app) sportEvents, err := sport.FetchAndUpdateSportEvents(services.App)
if err != nil { if err != nil {
slog.Error("Failed to fetch and save sport events:", "error", err) 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 //fetch all events for semester and delete from remote this should be done every sunday at 2am
scheduler.MustAdd("fetchEvents", "0 22 * * 6", func() { 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 { if err != nil {
slog.Error("Failed to fetch and save events: ", "error", err) slog.Error("Failed to fetch and save events: ", "error", err)
} else { } else {

View File

@ -17,25 +17,25 @@
package course package course
import ( import (
"github.com/pocketbase/pocketbase"
"htwkalender/data-manager/model" "htwkalender/data-manager/model"
"htwkalender/data-manager/service/events" "htwkalender/data-manager/service/events"
"htwkalender/data-manager/service/functions" "htwkalender/data-manager/service/functions"
"htwkalender/data-manager/service/functions/time" "htwkalender/data-manager/service/functions/time"
"htwkalender/data-manager/model/serviceModel"
"log/slog" "log/slog"
) )
func UpdateCourse(app *pocketbase.PocketBase) { func UpdateCourse(service serviceModel.Service) {
currentSemesters := functions.CalculateSemesterList(time.RealClock{}) currentSemesters := functions.CalculateSemesterList(time.RealClock{})
var seminarGroups []model.SeminarGroup var seminarGroups []model.SeminarGroup
for _, semester := range currentSemesters { for _, semester := range currentSemesters {
seminarGroups = append(seminarGroups, events.GetAllCoursesForSemester(app, semester)...) seminarGroups = append(seminarGroups, service.EventService.GetAllCoursesForSemester(semester)...)
} }
for _, seminarGroup := range seminarGroups { for _, seminarGroup := range seminarGroups {
_, err := events.UpdateModulesForCourse(app, seminarGroup) _, err := service.EventService.UpdateModulesForCourse(seminarGroup)
if err != nil { if err != nil {
slog.Warn("Update Course: "+seminarGroup.Course+" failed:", "error", err) slog.Warn("Update Course: "+seminarGroup.Course+" failed:", "error", err)
} }

View File

@ -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)
}

View File

@ -23,32 +23,51 @@ import (
"htwkalender/data-manager/service/functions" "htwkalender/data-manager/service/functions"
) )
func GetAllCourses(app *pocketbase.PocketBase) []string { // CourseService defines the methods to be implemented
return db.GetAllCourses(app) type CourseService interface {
GetAllCourses() []string
GetAllCoursesForSemester(semester string) []string
GetAllCoursesForSemesterWithEvents(semester string) ([]string, error)
} }
func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []model.SeminarGroup { // PocketBaseCourseService is a struct that implements the CourseService interface
return db.GetAllCoursesForSemester(app, semester) type PocketBaseCourseService struct {
app *pocketbase.PocketBase
} }
func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester string) ([]string, error) { // NewPocketBaseCourseService creates a new PocketBaseCourseService
courses, err := db.GetAllCoursesForSemesterWithEvents(app, semester) 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 { if err != nil {
return nil, err 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 // removeEmptyCourses removes empty courses from the list of courses
func removeEmptyCourses(courses []string) []string { func removeEmptyCourses(courses []string) []string {
var filteredCourses []string var filteredCourses []string
for index, course := range courses { for _, course := range courses {
if !functions.OnlyWhitespace(course) || len(course) != 0 { if !functions.OnlyWhitespace(course) || len(course) != 0 {
filteredCourses = append(filteredCourses, courses[index]) filteredCourses = append(filteredCourses, course)
} }
} }
return filteredCourses return filteredCourses

View File

@ -26,9 +26,31 @@ import (
"strconv" "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 // Convert the []model.Module to []Named
var namedEvents []Named var namedEvents []Named
@ -40,11 +62,6 @@ func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, seme
return modules, err return modules, err
} }
type Named interface {
GetName() string
SetName(name string)
}
// replaceEmptyEntry replaces an empty entry in a module with a replacement string // replaceEmptyEntry replaces an empty entry in a module with a replacement string
// If the module is not empty, nothing happens // If the module is not empty, nothing happens
func replaceEmptyEntry(namedList []Named, replacement string) { 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 // 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 // That means you get all modules with duplicates if they have different courses
func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]model.ModuleDTO, error) { func (s *PocketBaseEventService) GetAllModulesDistinct() ([]model.ModuleDTO, error) {
modules, err := db.GetAllModulesDistinctByNameAndCourse(app) modules, err := db.GetAllModulesDistinctByNameAndCourse(s.app)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,13 +87,13 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]model.ModuleDTO, error
return modules, nil return modules, nil
} }
func GetModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, error) { func (s *PocketBaseEventService) GetModuleByUUID(uuid string) (model.Module, error) {
module, findModuleErr := db.FindModuleByUUID(app, uuid) module, findModuleErr := db.FindModuleByUUID(s.app, uuid)
if findModuleErr != nil { if findModuleErr != nil {
return model.Module{}, findModuleErr return model.Module{}, findModuleErr
} }
events, findEventsError := db.FindAllEventsByModule(app, module) events, findEventsError := db.FindAllEventsByModule(s.app, module)
if findEventsError != nil || len(events) == 0 { if findEventsError != nil || len(events) == 0 {
return model.Module{}, findEventsError return model.Module{}, findEventsError
} else { } 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 // DeleteAllEventsByCourseAndSemester deletes all events for a course and a semester
// If the deletion was successful, nil is returned // If the deletion was successful, nil is returned
// If the deletion was not successful, an error is returned // If the deletion was not successful, an error is returned
func DeleteAllEventsByCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) error { func (s *PocketBaseEventService) DeleteAllEventsByCourseAndSemester(course string, semester string) error {
err := db.DeleteAllEventsByCourse(app.Dao(), course, semester) err := db.DeleteAllEventsByCourse(s.app.Dao(), course, semester)
if err != nil { if err != nil {
return err return err
} else { } else {
@ -103,8 +120,8 @@ func DeleteAllEventsByCourseAndSemester(app *pocketbase.PocketBase, course strin
} }
} }
func DeleteAllEvents(app *pocketbase.PocketBase) error { func (s *PocketBaseEventService) DeleteAllEvents() error {
err := db.DeleteAllEvents(app) err := db.DeleteAllEvents(s.app)
if err != nil { if err != nil {
return err return err
} else { } else {
@ -120,7 +137,7 @@ func DeleteAllEvents(app *pocketbase.PocketBase) error {
// 3. Save 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 successful, nil is returned
// If the update was not successful, an error 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) seminarGroup, err := v1.FetchAndParse(seminarGroup.Semester, seminarGroup.Course)
if err != nil { 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 //if there are no events in the database, save the new events
//get all events for the course and the semester //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 { if err != nil {
return nil, err return nil, err
} }
//if there are no events in the database, save the new events //if there are no events in the database, save the new events
if len(dbEvents) == 0 { if len(dbEvents) == 0 {
events, dbError := db.SaveSeminarGroupEvents(seminarGroup, app) events, dbError := db.SaveSeminarGroupEvents(seminarGroup, s.app)
if dbError != nil { if dbError != nil {
return nil, dbError 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 // check which events are not already in the database and need to be inserted/saved
for _, event := range seminarGroup.Events { for _, event := range seminarGroup.Events {
if !ContainsEvent(dbEvents, event) { if !containsEvent(dbEvents, event) {
insertList = append(insertList, event) insertList = append(insertList, event)
} }
} }
// check which events are in the database but not in the seminarGroup and need to be deleted // check which events are in the database but not in the seminarGroup and need to be deleted
for _, dbEvent := range dbEvents { for _, dbEvent := range dbEvents {
if !ContainsEvent(seminarGroup.Events, dbEvent) { if !containsEvent(seminarGroup.Events, dbEvent) {
deleteList = append(deleteList, dbEvent) deleteList = append(deleteList, dbEvent)
} }
} }
// delete all events that are in the deleteList // delete all events that are in the deleteList
err = db.DeleteEvents(deleteList, app) err = db.DeleteEvents(deleteList, s.app)
if err != nil { if err != nil {
slog.Error("Failed to delete events:", "error", err) slog.Error("Failed to delete events:", "error", err)
return nil, err return nil, err
} }
// save all events that are in the insertList // 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 { if err != nil {
slog.Error("Failed to save events: ", "error", err) slog.Error("Failed to save events: ", "error", err)
return nil, err return nil, err
@ -184,7 +201,7 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, seminarGroup model.Semin
return savedEvents, nil 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 { for _, e := range events {
if e.Name == event.Name && if e.Name == event.Name &&
e.Prof == event.Prof && e.Prof == event.Prof &&
@ -199,8 +216,8 @@ func ContainsEvent(events model.Events, event model.Event) bool {
return false return false
} }
func GetEventTypes(app *pocketbase.PocketBase) ([]string, error) { func (s *PocketBaseEventService) GetEventTypes() ([]string, error) {
dbEventTypes, err := db.GetAllEventTypes(app) dbEventTypes, err := db.GetAllEventTypes(s.app)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -50,8 +50,8 @@ func TestContainsEvent(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := ContainsEvent(tt.args.events, tt.args.event); got != tt.want { if got := containsEvent(tt.args.events, tt.args.event); got != tt.want {
t.Errorf("ContainsEvent() = %v, want %v", got, tt.want) t.Errorf("containsEvent() = %v, want %v", got, tt.want)
} }
}) })
} }

View File

@ -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)
}

View File

@ -12,6 +12,7 @@ require (
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/pocketbase/dbx v1.10.1 github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.22.12 github.com/pocketbase/pocketbase v0.22.12
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.26.0 golang.org/x/net v0.26.0
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.34.1 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/ssooidc v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 // 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/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/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // 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/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/ncruces/go-strftime v0.1.9 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // 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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
@ -83,6 +87,7 @@ require (
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.180.0 // indirect google.golang.org/api v0.180.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // 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/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.50.5 // indirect modernc.org/libc v1.50.5 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect

View File

@ -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.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.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.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.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.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.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 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=