From 4c16605bd6aff098181f0fd9f363b797af1b1b34 Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Wed, 24 Jan 2024 02:07:30 +0100 Subject: [PATCH] feat:#5 added free rooms api endpoint --- backend/openapi.yml | 21 +++ backend/service/addRoute.go | 34 +++++ backend/service/db/dbEvents.go | 15 +- backend/service/db/dbRooms.go | 4 +- backend/service/functions/string.go | 6 + backend/service/functions/time/parse.go | 7 + backend/service/room/roomService.go | 45 ++++++ backend/service/room/roomService_test.go | 170 ++++++++++++++++++++++- 8 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 backend/service/functions/time/parse.go diff --git a/backend/openapi.yml b/backend/openapi.yml index c32b761..8488838 100644 --- a/backend/openapi.yml +++ b/backend/openapi.yml @@ -46,6 +46,27 @@ paths: responses: '200': description: Successful response + /api/rooms/free: + get: + summary: Get Free Rooms + parameters: + - name: from + in: query + description: start date + example: "2006-01-02T15:04:05Z" + required: true + schema: + type: string + - name: to + in: query + description: end date + example: "2006-01-02T15:04:05Z" + required: true + schema: + type: string + responses: + '200': + description: Successful response /api/schedule/day: get: summary: Get Day Schedule diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index da1dcc7..0eeb892 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -5,6 +5,7 @@ import ( "htwkalender/service/fetch/sport" v1 "htwkalender/service/fetch/v1" v2 "htwkalender/service/fetch/v2" + "htwkalender/service/functions/time" "htwkalender/service/ical" "htwkalender/service/room" "log/slog" @@ -181,6 +182,39 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) + // API Endpoint to get all rooms that have no events in a specific time frame + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + _, err := e.Router.AddRoute(echo.Route{ + Method: http.MethodGet, + Path: "/api/rooms/free", + Handler: func(c echo.Context) error { + from, err := time.ParseTime(c.QueryParam("from")) + if err != nil { + slog.Error("Failed to parse time: %v", err) + return c.JSON(http.StatusBadRequest, "Failed to parse time") + } + to, err := time.ParseTime(c.QueryParam("to")) + if err != nil { + slog.Error("Failed to parse time: %v", err) + return c.JSON(http.StatusBadRequest, "Failed to parse time") + } + rooms, err := room.GetFreeRooms(app, from, to) + if err != nil { + slog.Error("Failed to get free rooms: %v", err) + return c.JSON(http.StatusBadRequest, "Failed to get free rooms") + } + return c.JSON(http.StatusOK, rooms) + }, + Middlewares: []echo.MiddlewareFunc{ + apis.ActivityLogger(app), + }, + }) + if err != nil { + return err + } + return nil + }) + addFeedRoutes(app) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index dec5da3..9078c01 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -10,12 +10,12 @@ import ( "github.com/pocketbase/pocketbase" ) -func SaveSeminarGroupEvents(seminarGroup []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) { +func SaveSeminarGroupEvents(seminarGroups []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) { var toBeSavedEvents model.Events var savedRecords model.Events // check if event is already in database and add to toBeSavedEvents if not - for _, seminarGroup := range seminarGroup { + for _, seminarGroup := range seminarGroups { for _, event := range seminarGroup.Events { event = event.SetCourse(seminarGroup.Course) existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app) @@ -254,3 +254,14 @@ func GetAllModulesByNameAndDateRange(app *pocketbase.PocketBase, name string, st return events, nil } + +func GetEventsInTimeRange(app *pocketbase.PocketBase, from time.Time, to time.Time) (model.Events, error) { + var events model.Events + + err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End <= {:endDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events) + if err != nil { + return nil, err + } + + return events, nil +} diff --git a/backend/service/db/dbRooms.go b/backend/service/db/dbRooms.go index 1c49a54..b1d6a36 100644 --- a/backend/service/db/dbRooms.go +++ b/backend/service/db/dbRooms.go @@ -47,9 +47,7 @@ func clearAndSeparateRooms(events []struct { // sport rooms don't have to be separated if event.Course != "Sport" { //split rooms by comma, tab, newline, carriage return, semicolon, space and non-breaking space - room = strings.FieldsFunc(event.Rooms, functions.IsSeparator( - []rune{',', '\t', '\n', '\r', ';', ' ', '\u00A0'}), - ) + room = functions.SeperateRoomString(event.Rooms) } else { room = append(room, event.Rooms) } diff --git a/backend/service/functions/string.go b/backend/service/functions/string.go index 8f71850..7975b6f 100644 --- a/backend/service/functions/string.go +++ b/backend/service/functions/string.go @@ -45,3 +45,9 @@ func HashString(s string) string { hashInBytes := hash.Sum(nil) return hex.EncodeToString(hashInBytes) } + +func SeperateRoomString(rooms string) []string { + return strings.FieldsFunc(rooms, IsSeparator( + []rune{',', '\t', '\n', '\r', ';', ' ', '\u00A0'}), + ) +} diff --git a/backend/service/functions/time/parse.go b/backend/service/functions/time/parse.go new file mode 100644 index 0000000..9381372 --- /dev/null +++ b/backend/service/functions/time/parse.go @@ -0,0 +1,7 @@ +package time + +import "time" + +func ParseTime(timeString string) (time.Time, error) { + return time.Parse("2006-01-02T15:04:05Z", timeString) +} diff --git a/backend/service/room/roomService.go b/backend/service/room/roomService.go index 47e6bad..7ce96ba 100644 --- a/backend/service/room/roomService.go +++ b/backend/service/room/roomService.go @@ -4,6 +4,8 @@ import ( "github.com/pocketbase/pocketbase" "htwkalender/model" "htwkalender/service/db" + "htwkalender/service/functions" + "time" ) func GetRooms(app *pocketbase.PocketBase) ([]string, error) { @@ -41,3 +43,46 @@ func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO { } return anonymizedEvents } + +func GetFreeRooms(app *pocketbase.PocketBase, from time.Time, to time.Time) ([]string, error) { + rooms, err := db.GetRooms(app) + if err != nil { + return nil, err + } + var events model.Events + events, err = db.GetEventsInTimeRange(app, from, to) + if err != nil { + return nil, err + } + freeRooms := removeRoomsThatHaveEvents(rooms, events) + return freeRooms, nil +} + +func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string { + var freeRooms []string + for _, room := range rooms { + if !isRoomInSchedule(room, schedule) { + freeRooms = append(freeRooms, room) + } + } + return freeRooms +} + +func isRoomInSchedule(room string, schedule []model.Event) bool { + for _, event := range schedule { + if event.Course != "Sport" { + rooms := functions.SeperateRoomString(event.Rooms) + // check if room is in rooms + for _, r := range rooms { + if r == room { + return true + } + } + } else { + if event.Rooms == room { + return true + } + } + } + return false +} diff --git a/backend/service/room/roomService_test.go b/backend/service/room/roomService_test.go index eefd163..7a9aae7 100644 --- a/backend/service/room/roomService_test.go +++ b/backend/service/room/roomService_test.go @@ -1,11 +1,10 @@ package room import ( + "github.com/pocketbase/pocketbase/tools/types" "htwkalender/model" "reflect" "testing" - - "github.com/pocketbase/pocketbase/tools/types" ) func Test_anonymizeRooms(t *testing.T) { @@ -123,3 +122,170 @@ func Test_anonymizeRooms(t *testing.T) { }) } } + +func Test_isRoomInSchedule(t *testing.T) { + type args struct { + room string + schedule []model.Event + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "room is in schedule", + args: args{ + room: "Room", + schedule: []model.Event{ + { + UUID: "testUUID", + Day: "Montag", + Week: "52", + Start: types.DateTime{}, + End: types.DateTime{}, + Name: "Secret", + EventType: "V", + Prof: "Prof. Dr. Secret", + Rooms: "Room", + Notes: "Secret", + }, + }, + }, + want: true, + }, + { + name: "room is not in schedule", + args: args{ + room: "Z324", + schedule: []model.Event{ + { + UUID: "testUUID", + Day: "Montag", + Week: "52", + Start: types.DateTime{}, + End: types.DateTime{}, + Name: "Secret", + EventType: "V", + Prof: "Prof. Dr. Bond", + Rooms: "LI007", + Notes: "Keine Zeit für die Uni", + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isRoomInSchedule(tt.args.room, tt.args.schedule); got != tt.want { + t.Errorf("isRoomInSchedule() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getFreeRooms(t *testing.T) { + type args struct { + rooms []string + schedule []model.Event + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "remove room1 from list", + args: args{ + rooms: []string{ + "Room1", + "Room2", + "Room3", + }, + schedule: []model.Event{ + { + UUID: "testUUID", + Day: "Montag", + Week: "52", + Start: types.DateTime{}, + End: types.DateTime{}, + Name: "Secret", + EventType: "V", + Prof: "Prof. Dr. Secret", + Rooms: "Room1", + Notes: "Secret", + }, + }, + }, + want: []string{ + "Room2", + "Room3", + }, + }, + { + name: "remove room2 from list", + args: args{ + rooms: []string{ + "Room1", + "Room2", + "Room3", + }, + schedule: []model.Event{ + { + UUID: "testUUID", + Day: "Montag", + Week: "52", + Start: types.DateTime{}, + End: types.DateTime{}, + Name: "Secret", + EventType: "V", + Prof: "Prof. Dr. Secret", + Rooms: "Room3", + Notes: "Secret", + }, + }, + }, + want: []string{ + "Room1", + "Room2", + }, + }, + { + name: "remove no room from list", + args: args{ + rooms: []string{ + "Room1", + "Room2", + "Room3", + }, + schedule: []model.Event{ + { + UUID: "testUUID", + Day: "Montag", + Week: "52", + Start: types.DateTime{}, + End: types.DateTime{}, + Name: "Secret", + EventType: "V", + Prof: "Prof. Dr. Secret", + Rooms: "Room4", + Notes: "Secret", + }, + }, + }, + want: []string{ + "Room1", + "Room2", + "Room3", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := removeRoomsThatHaveEvents(tt.args.rooms, tt.args.schedule); !reflect.DeepEqual(got, tt.want) { + t.Errorf("removeRoomsThatHaveEvents() = %v, want %v", got, tt.want) + } + }) + } +}