//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format. //Copyright (C) 2024 HTWKalender support@htwkalender.de //This program is free software: you can redistribute it and/or modify //it under the terms of the GNU Affero General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. //This program is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU Affero General Public License for more details. //You should have received a copy of the GNU Affero General Public License //along with this program. If not, see . package service import ( "htwkalender/service/events" "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" "net/http" "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "go.mongodb.org/mongo-driver/bson" ) const RoomOccupancyGranularity = 15 func AddRoutes(app *pocketbase.PocketBase) { 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) if err != nil { slog.Error("Failed to parse events from remote: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse events from remote") } else { return c.JSON(http.StatusOK, savedEvents) } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), apis.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/groups", Handler: func(c echo.Context) error { groups, err := v1.FetchSeminarGroups(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.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/fetch/sports", Handler: func(c echo.Context) error { sportEvents, err := sport.FetchAndUpdateSportEvents(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.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodDelete, Path: "/api/modules", Handler: func(c echo.Context) error { err := events.DeleteAllEvents(app) 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.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/rooms", Handler: func(c echo.Context) error { rooms, err := room.GetRooms(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), }, }) if err != nil { return err } return nil }) // API Endpoint to get all events for a specific room on a specific day app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, 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) if err != nil { slog.Error("Failed to get room schedule for day: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day") } return c.JSON(http.StatusOK, roomSchedule) }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) // API Endpoint to get room occupancy for a time period for all rooms, when requested as BSON app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/schedule/rooms", Handler: func(c echo.Context) error { rooms, err := room.GetRoomOccupancyList(app, RoomOccupancyGranularity) if err != nil { slog.Error("Failed to get room occupancy: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room occupancy") } bson_coded, err := bson.Marshal(rooms) if err != nil { slog.Error("Failed to encode room occupancy to BSON: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to encode room occupancy to BSON") } return c.Blob(http.StatusOK, "application/bson", bson_coded) }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) // API Endpoint to create a new iCal feed app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/schedule", Handler: func(c echo.Context) error { roomParam := c.QueryParam("room") to := c.QueryParam("to") from := c.QueryParam("from") roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to) if err != nil { slog.Error("Failed to get room schedule: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule") } return c.JSON(http.StatusOK, roomSchedule) }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } 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", "error", 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", "error", 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", "error", 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 { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/course/modules", Handler: func(c echo.Context) error { course := c.QueryParam("course") semester := c.QueryParam("semester") modules, err := events.GetModulesForCourseDistinct(app, course, semester) if err != nil { slog.Error("Failed to get modules for course and semester: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester") } else { return c.JSON(http.StatusOK, modules) } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/modules", Handler: func(c echo.Context) error { modules, err := events.GetAllModulesDistinct(app) if err != nil { slog.Error("Failed to get modules: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get modules") } return c.JSON(http.StatusOK, modules) }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/module", Handler: func(c echo.Context) error { requestModule := c.QueryParam("uuid") module, err := events.GetModuleByUUID(app, requestModule) if err != nil { slog.Error("Failed to get module: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get module") } else { return c.JSON(http.StatusOK, module) } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/courses", Handler: func(c echo.Context) error { semester := c.QueryParam("semester") if semester == "" { courses := events.GetAllCourses(app) return c.JSON(200, courses) } else { courses := events.GetAllCoursesForSemester(app, semester) return c.JSON(200, courses) } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) // api end point to get all courses for a specific semester with courses that have events 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) if err != nil { slog.Error("Failed to get courses for semester with events: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events") } else { return c.JSON(200, courses) } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodDelete, Path: "/api/events", Handler: func(c echo.Context) error { course := c.QueryParam("course") semester := c.QueryParam("semester") err := events.DeleteAllEventsByCourseAndSemester(app, course, semester) if err != nil { slog.Error("Failed to delete events: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to delete events") } else { return c.JSON(http.StatusBadRequest, "Events deleted") } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), apis.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, Path: "/api/feeds/migrate", Handler: func(c echo.Context) error { err := ical.MigrateFeedJson(app) if err != nil { slog.Error("Failed to migrate feeds: %v", "error", err) return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds") } else { return c.JSON(http.StatusOK, "Migrated") } }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), apis.RequireAdminAuth(), }, }) if err != nil { return err } return nil }) }