Merge branch '150-fix-error-response' into 'main'

feat:#150 rewrote error handling for backend

Closes #2

See merge request ekresse/htwkalender!1
This commit is contained in:
ekresse
2024-01-23 18:22:57 +00:00
19 changed files with 759 additions and 694 deletions

View File

@@ -5,7 +5,7 @@ import (
"github.com/pocketbase/pocketbase/plugins/migratecmd" "github.com/pocketbase/pocketbase/plugins/migratecmd"
_ "htwkalender/migrations" _ "htwkalender/migrations"
"htwkalender/service" "htwkalender/service"
"log" "log/slog"
"os" "os"
"strings" "strings"
) )
@@ -27,6 +27,6 @@ func main() {
service.AddSchedules(app) service.AddSchedules(app)
if err := app.Start(); err != nil { if err := app.Start(); err != nil {
log.Fatal(err) slog.Error("Failed to start app: %v", err)
} }
} }

View File

@@ -0,0 +1,183 @@
package service
import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"htwkalender/service/db"
"htwkalender/service/ical"
"io"
"net/http"
)
func addFeedRoutes(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/createFeed",
Handler: func(c echo.Context) error {
requestBody, _ := io.ReadAll(c.Request().Body)
result, err := ical.CreateIndividualFeed(requestBody, app)
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to create individual feed")
}
return c.JSON(http.StatusOK, result)
},
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/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
result, err := ical.Feed(app, token)
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to get feed")
}
var responseWriter = c.Response().Writer
responseWriter.Header().Set("Content-type", "text/calendar")
responseWriter.Header().Set("charset", "utf-8")
responseWriter.Header().Set("Content-Disposition", "inline")
responseWriter.Header().Set("filename", "calendar.ics")
responseWriter.WriteHeader(http.StatusOK)
_, err = responseWriter.Write([]byte(result))
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to write feed")
}
c.Response().Writer = responseWriter
return nil
},
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/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
err := db.DeleteFeed(app.Dao(), token)
if err != nil {
return c.JSON(http.StatusNotFound, err)
} else {
return c.JSON(http.StatusOK, "Feed deleted")
}
},
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.MethodPut,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
return c.JSON(http.StatusOK, "token: "+token)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
// Route for Thunderbird to get calendar server information
// Response is a 200 OK without additional content
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: "PROPFIND",
Path: "/",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
// Route for Thunderbird to get calendar server information
// Response is a 200 OK without additional content
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: "PROPFIND",
Path: "/.well-known/caldav",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
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: "PROPFIND",
Path: "/api/feed",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
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.MethodHead,
Path: "/api/feed",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
}

View File

@@ -1,14 +1,13 @@
package service package service
import ( import (
"htwkalender/service/db"
"htwkalender/service/events" "htwkalender/service/events"
"htwkalender/service/fetch/sport" "htwkalender/service/fetch/sport"
v1 "htwkalender/service/fetch/v1" v1 "htwkalender/service/fetch/v1"
v2 "htwkalender/service/fetch/v2" v2 "htwkalender/service/fetch/v2"
"htwkalender/service/ical" "htwkalender/service/ical"
"htwkalender/service/room" "htwkalender/service/room"
"io" "log/slog"
"net/http" "net/http"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
@@ -24,7 +23,13 @@ func AddRoutes(app *pocketbase.PocketBase) {
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 {
return v2.ParseEventsFromRemote(c, app) savedEvents, err := v2.ParseEventsFromRemote(app)
if err != nil {
slog.Error("Failed to parse events from remote: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to parse events from remote")
} else {
return c.JSON(http.StatusOK, savedEvents)
}
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -42,7 +47,11 @@ func AddRoutes(app *pocketbase.PocketBase) {
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 {
return v1.SeminarGroups(c, app) 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{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -61,8 +70,11 @@ func AddRoutes(app *pocketbase.PocketBase) {
Path: "/api/fetch/sports", Path: "/api/fetch/sports",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
sportEvents := sport.FetchAndUpdateSportEvents(app) sportEvents, err := sport.FetchAndUpdateSportEvents(app)
return c.JSON(200, sportEvents) if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to fetch sport events")
}
return c.JSON(http.StatusOK, sportEvents)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -80,7 +92,11 @@ func AddRoutes(app *pocketbase.PocketBase) {
Method: http.MethodDelete, Method: http.MethodDelete,
Path: "/api/modules", Path: "/api/modules",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
return events.DeleteAllEvents(app) 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{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -98,7 +114,11 @@ func AddRoutes(app *pocketbase.PocketBase) {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/rooms", Path: "/api/rooms",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
return room.GetRooms(c, app) 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{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -118,7 +138,12 @@ func AddRoutes(app *pocketbase.PocketBase) {
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")
return room.GetRoomScheduleForDay(c, app, roomParam, date) roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date)
if err != nil {
slog.Error("Failed to get room schedule for day: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day")
}
return c.JSON(http.StatusOK, roomSchedule)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -139,30 +164,12 @@ 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")
return room.GetRoomSchedule(c, app, roomParam, from, to) roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to)
},
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.MethodPost,
Path: "/api/createFeed",
Handler: func(c echo.Context) error {
requestBody, _ := io.ReadAll(c.Request().Body)
result, err := ical.CreateIndividualFeed(requestBody, app)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) slog.Error("Failed to get room schedule: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule")
} }
return c.JSON(http.StatusOK, result) return c.JSON(http.StatusOK, roomSchedule)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -174,64 +181,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil return nil
}) })
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { addFeedRoutes(app)
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
return ical.Feed(c, app, token)
},
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/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
err := db.DeleteFeed(app.Dao(), token)
if err != nil {
return c.JSON(http.StatusNotFound, err)
} else {
return c.JSON(http.StatusOK, "Feed deleted")
}
},
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.MethodPut,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
return c.JSON(http.StatusOK, "token: "+token)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{ _, err := e.Router.AddRoute(echo.Route{
@@ -243,9 +193,10 @@ func AddRoutes(app *pocketbase.PocketBase) {
modules, err := events.GetModulesForCourseDistinct(app, course, semester) modules, err := events.GetModulesForCourseDistinct(app, course, semester)
if err != nil { if err != nil {
return c.JSON(400, err) slog.Error("Failed to get modules for course and semester: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester")
} else { } else {
return c.JSON(200, modules) return c.JSON(http.StatusOK, modules)
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
@@ -263,7 +214,12 @@ func AddRoutes(app *pocketbase.PocketBase) {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/modules", Path: "/api/modules",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
return events.GetAllModulesDistinct(app, c) modules, err := events.GetAllModulesDistinct(app)
if err != nil {
slog.Error("Failed to get modules: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to get modules")
}
return c.JSON(http.StatusOK, modules)
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app), apis.ActivityLogger(app),
@@ -283,9 +239,10 @@ func AddRoutes(app *pocketbase.PocketBase) {
requestModule := c.QueryParam("uuid") requestModule := c.QueryParam("uuid")
module, err := events.GetModuleByUUID(app, requestModule) module, err := events.GetModuleByUUID(app, requestModule)
if err != nil { if err != nil {
return c.JSON(400, err) slog.Error("Failed to get module: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to get module")
} else { } else {
return c.JSON(200, module) return c.JSON(http.StatusOK, module)
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
@@ -325,9 +282,10 @@ func AddRoutes(app *pocketbase.PocketBase) {
semester := c.QueryParam("semester") semester := c.QueryParam("semester")
err := events.DeleteAllEventsByCourseAndSemester(app, course, semester) err := events.DeleteAllEventsByCourseAndSemester(app, course, semester)
if err != nil { if err != nil {
return c.JSON(400, err) slog.Error("Failed to delete events: %v", err)
return c.JSON(http.StatusBadRequest, "Failed to delete events")
} else { } else {
return c.JSON(200, "Events deleted") return c.JSON(http.StatusBadRequest, "Events deleted")
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{
@@ -349,9 +307,10 @@ func AddRoutes(app *pocketbase.PocketBase) {
err := ical.MigrateFeedJson(app) err := ical.MigrateFeedJson(app)
if err != nil { if err != nil {
return c.JSON(500, err) slog.Error("Failed to migrate feeds: %v", err)
return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds")
} else { } else {
return c.JSON(200, "Migrated") return c.JSON(http.StatusOK, "Migrated")
} }
}, },
Middlewares: []echo.MiddlewareFunc{ Middlewares: []echo.MiddlewareFunc{

View File

@@ -10,7 +10,7 @@ import (
"htwkalender/service/fetch/sport" "htwkalender/service/fetch/sport"
v2 "htwkalender/service/fetch/v2" v2 "htwkalender/service/fetch/v2"
"htwkalender/service/functions/time" "htwkalender/service/functions/time"
"log" "log/slog"
"strconv" "strconv"
) )
@@ -23,38 +23,42 @@ func AddSchedules(app *pocketbase.PocketBase) {
// Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *" // Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *"
// Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *" // Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *"
scheduler.MustAdd("updateCourse", "0 */3 * * *", func() { scheduler.MustAdd("updateCourse", "0 */3 * * *", func() {
slog.Info("Started updating courses schedule")
course.UpdateCourse(app) course.UpdateCourse(app)
}) })
// 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")
feed.ClearFeeds(app.Dao(), 6, time.RealClock{}) feed.ClearFeeds(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 2am 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() {
sport.FetchAndUpdateSportEvents(app) slog.Info("Started fetching sport events schedule")
sportEvents, err := sport.FetchAndUpdateSportEvents(app)
if err != nil {
slog.Error("Failed to fetch and save sport events: %v", err)
}
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(sportEvents)), 10) + " sport events")
}) })
//delete all events and then fetch all events from remote this should be done every sunday at 2am //delete all events and then fetch all events from remote this should be done every sunday at 2am
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() { scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
err := events.DeleteAllEvents(app) err := events.DeleteAllEvents(app)
if err != nil { if err != nil {
log.Println(err) slog.Error("Failed to delete all events: %v", err)
} }
err, savedEvents := v2.FetchAllEventsAndSave(app) savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{})
if err != nil { if err != nil {
log.Println(err) slog.Error("Failed to fetch and save events: %v", err)
} else { } else {
log.Println("Successfully saved: " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events") slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
} }
}) })
scheduler.Start() scheduler.Start()
return nil return nil
}) })
} }

View File

@@ -3,18 +3,18 @@ package course
import ( import (
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"htwkalender/service/events" "htwkalender/service/events"
"log" "log/slog"
"strconv"
) )
func UpdateCourse(app *pocketbase.PocketBase) { func UpdateCourse(app *pocketbase.PocketBase) {
courses := events.GetAllCourses(app) courses := events.GetAllCourses(app)
for _, course := range courses { for _, course := range courses {
err := events.UpdateModulesForCourse(app, course) savedEvents, err := events.UpdateModulesForCourse(app, course)
if err != nil { if err != nil {
log.Println("Update Course: " + course + " failed") slog.Warn("Update Course: "+course+" failed: %v", err)
log.Println(err)
} else { } else {
log.Println("Update Course: " + course + " successful") slog.Info("Updated Course: " + course + " with " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
} }
} }
} }

View File

@@ -1,7 +1,9 @@
package db package db
import ( import (
"fmt"
"htwkalender/model" "htwkalender/model"
"log/slog"
"time" "time"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
@@ -145,7 +147,7 @@ func buildIcalQueryForModules(modules []model.FeedCollection) dbx.Expression {
// GetPlanForModules returns all events for the given modules with the given course // GetPlanForModules returns all events for the given modules with the given course
// used for the ical feed // used for the ical feed
func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) model.Events { func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (model.Events, error) {
var events model.Events var events model.Events
@@ -168,12 +170,11 @@ func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.Feed
// get all events from event records in the events collection // get all events from event records in the events collection
err := app.Dao().DB().Select("*").From("events").Where(selectedModulesQuery).All(&events) err := app.Dao().DB().Select("*").From("events").Where(selectedModulesQuery).All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err) return nil, err
return nil
} }
} }
return events return events, nil
} }
func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) { func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
@@ -182,8 +183,8 @@ func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester
// get all events from event records in the events collection // get all events from event records in the events collection
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).GroupBy("Name").Distinct(true).All(&events) err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).GroupBy("Name").Distinct(true).All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err) slog.Error("Error while getting events from database: ", err)
return nil, err return nil, fmt.Errorf("error while getting events from database for course %s and semester %s", course, semester)
} }
return events, nil return events, nil
@@ -194,8 +195,8 @@ func GetAllModulesDistinctByNameAndCourse(app *pocketbase.PocketBase) ([]model.M
err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules) err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules)
if err != nil { if err != nil {
print("Error while getting events from database: ", err) slog.Error("Error while getting events from database: ", err)
return nil, err return nil, fmt.Errorf("error while getting events distinct by name and course from data")
} }
return modules, nil return modules, nil
@@ -205,10 +206,8 @@ func DeleteAllEventsForCourse(app *pocketbase.PocketBase, course string, semeste
_, err := app.Dao().DB().Delete("events", dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Execute() _, err := app.Dao().DB().Delete("events", dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Execute()
if err != nil { if err != nil {
print("Error while deleting events from database: ", err)
return err return err
} }
return nil return nil
} }
@@ -228,7 +227,6 @@ func FindModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, er
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("uuid = {:uuid}", dbx.Params{"uuid": uuid})).One(&module) err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("uuid = {:uuid}", dbx.Params{"uuid": uuid})).One(&module)
if err != nil { if err != nil {
print("Error while getting events from database: ", err)
return model.Module{}, err return model.Module{}, err
} }
@@ -240,7 +238,6 @@ func FindAllEventsByModule(app *pocketbase.PocketBase, module model.Module) (mod
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:moduleName} AND course = {:course}", dbx.Params{"moduleName": module.Name, "course": module.Course})).All(&events) err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:moduleName} AND course = {:course}", dbx.Params{"moduleName": module.Name, "course": module.Course})).All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err)
return nil, err return nil, err
} }
@@ -252,7 +249,6 @@ func GetAllModulesByNameAndDateRange(app *pocketbase.PocketBase, name string, st
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND Start >= {:startDate} AND End <= {:endDate}", dbx.Params{"name": name, "startDate": startDate, "endDate": endDate})).All(&events) err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND Start >= {:startDate} AND End <= {:endDate}", dbx.Params{"name": name, "startDate": startDate, "endDate": endDate})).All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err)
return nil, err return nil, err
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"htwkalender/model" "htwkalender/model"
"log/slog"
) )
func SaveGroups(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) ([]*models.Record, error) { func SaveGroups(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) ([]*models.Record, error) {
@@ -67,8 +68,8 @@ func GetAllCourses(app *pocketbase.PocketBase) []string {
// get all rooms from event records in the events collection // get all rooms from event records in the events collection
err := app.Dao().DB().Select("course").From("groups").All(&courses) err := app.Dao().DB().Select("course").From("groups").All(&courses)
if err != nil { if err != nil {
print("Error while getting groups from database: ", err) slog.Error("Error while getting groups from database: ", err)
return nil return []string{}
} }
var courseArray []string var courseArray []string

View File

@@ -1,7 +1,6 @@
package db package db
import ( import (
"fmt"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/functions" "htwkalender/service/functions"
"strings" "strings"
@@ -11,7 +10,7 @@ import (
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
) )
func GetRooms(app *pocketbase.PocketBase) []string { func GetRooms(app *pocketbase.PocketBase) ([]string, error) {
var events []struct { var events []struct {
Rooms string `db:"Rooms" json:"Rooms"` Rooms string `db:"Rooms" json:"Rooms"`
@@ -21,22 +20,25 @@ func GetRooms(app *pocketbase.PocketBase) []string {
// get all rooms from event records in the events collection // get all rooms from event records in the events collection
err := app.Dao().DB().Select("Rooms", "course").From("events").Distinct(true).All(&events) err := app.Dao().DB().Select("Rooms", "course").From("events").Distinct(true).All(&events)
if err != nil { if err != nil {
print("Error while getting rooms from database: ", err) return nil, err
return nil
} }
roomArray := clearAndSeparateRooms([]struct { roomArray, err := clearAndSeparateRooms([]struct {
Rooms string Rooms string
Course string Course string
}(events)) }(events))
return roomArray if err != nil {
return nil, err
}
return roomArray, nil
} }
func clearAndSeparateRooms(events []struct { func clearAndSeparateRooms(events []struct {
Rooms string Rooms string
Course string Course string
}) []string { }) ([]string, error) {
var roomArray []string var roomArray []string
for _, event := range events { for _, event := range events {
@@ -60,10 +62,10 @@ func clearAndSeparateRooms(events []struct {
} }
} }
} }
return roomArray return roomArray, nil
} }
func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) []model.Event { func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) ([]model.Event, error) {
var events []model.Event var events []model.Event
// get all events from event records in the events collection // get all events from event records in the events collection
@@ -73,24 +75,21 @@ func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string)
GroupBy("Week", "Start", "End", "Rooms"). GroupBy("Week", "Start", "End", "Rooms").
All(&events) All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err) return nil, err
return nil
} }
return events return events, nil
} }
func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to string) []model.Event { func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to string) ([]model.Event, error) {
var events []model.Event var events []model.Event
fromDate, err := time.Parse("2006-01-02", from) fromDate, err := time.Parse("2006-01-02", from)
if err != nil { if err != nil {
fmt.Println("Error parsing date 'from':", err) return nil, err
return nil
} }
toDate, err := time.Parse("2006-01-02", to) toDate, err := time.Parse("2006-01-02", to)
if err != nil { if err != nil {
fmt.Println("Error parsing date 'to':", err) return nil, err
return nil
} }
// get all events from event records in the events collection // get all events from event records in the events collection
@@ -101,8 +100,7 @@ func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to st
All(&events) All(&events)
if err != nil { if err != nil {
print("Error while getting events from database: ", err) return nil, err
return nil
} }
return events return events, nil
} }

View File

@@ -1,9 +1,7 @@
package events package events
import ( import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"htwkalender/service/fetch/v1" "htwkalender/service/fetch/v1"
@@ -41,21 +39,17 @@ 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, c echo.Context) error { func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]model.ModuleDTO, error) {
modules, err := db.GetAllModulesDistinctByNameAndCourse(app) modules, err := db.GetAllModulesDistinctByNameAndCourse(app)
if err != nil {
return nil, err
}
var namedModules []Named var namedModules []Named
for _, module := range modules { for _, module := range modules {
namedModules = append(namedModules, &module) namedModules = append(namedModules, &module)
} }
replaceEmptyEntry(namedModules, "Sonderveranstaltungen") replaceEmptyEntry(namedModules, "Sonderveranstaltungen")
return modules, nil
if err != nil {
return c.JSON(400, err)
} else {
return c.JSON(200, modules)
}
} }
func GetModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, error) { func GetModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, error) {
@@ -105,7 +99,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, course string) error { func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) (model.Events, error) {
//new string array with one element (course) //new string array with one element (course)
var courses []string var courses []string
@@ -125,29 +119,32 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error {
//get all events for the course and the semester //get all events for the course and the semester
events, err := db.GetAllModulesForCourse(app, course, "ws") events, err := db.GetAllModulesForCourse(app, course, "ws")
if err != nil { if err != nil {
return apis.NewNotFoundError("Events for winter semester could not be found", err) return nil, err
} }
// append all events for the course and the semester to the events array for ss // append all events for the course and the semester to the events array for ss
summerEvents, err := db.GetAllModulesForCourse(app, course, "ss") summerEvents, err := db.GetAllModulesForCourse(app, course, "ss")
if err != nil { if err != nil {
return apis.NewNotFoundError("Events for summer semester could not be found", err) return nil, err
} }
events = append(events, summerEvents...) events = append(events, summerEvents...)
//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(events) == 0 { if len(events) == 0 {
_, dbError := db.SaveSeminarGroupEvents(seminarGroups, app) events, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
if dbError != nil { if dbError != nil {
return apis.NewNotFoundError("Events could not be saved", dbError) return nil, dbError
} }
return nil return events, nil
} }
//check if events in the seminarGroups Events are already in the database //check if events in the seminarGroups Events are already in the database
//if yes, keep the database as it is //if yes, keep the database as it is
//if no, delete all events for the course and the semester and save the new events //if no, delete all events for the course and the semester and save the new events
var savedEvents model.Events
for _, seminarGroup := range seminarGroups { for _, seminarGroup := range seminarGroups {
for _, event := range seminarGroup.Events { for _, event := range seminarGroup.Events {
// if the event is not in the database, delete all events for the course and the semester and save the new events // if the event is not in the database, delete all events for the course and the semester and save the new events
@@ -155,25 +152,24 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) error {
err = DeleteAllEventsByCourseAndSemester(app, course, "ws") err = DeleteAllEventsByCourseAndSemester(app, course, "ws")
if err != nil { if err != nil {
return err return nil, err
} }
err = DeleteAllEventsByCourseAndSemester(app, course, "ss") err = DeleteAllEventsByCourseAndSemester(app, course, "ss")
if err != nil { if err != nil {
return err return nil, err
} }
//save the new events //save the new events
_, dbError := db.SaveSeminarGroupEvents(seminarGroups, app) savedEvent, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
if dbError != nil { if dbError != nil {
return apis.NewNotFoundError("Events could not be saved", dbError) return nil, dbError
} }
return nil savedEvents = append(savedEvents, savedEvent...)
} }
} }
} }
return savedEvents, nil
return nil
} }
func ContainsEvent(events model.Events, event model.Event) bool { func ContainsEvent(events model.Events, event model.Event) bool {

View File

@@ -1,17 +1,18 @@
package feed package feed
import ( import (
"database/sql"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos" "github.com/pocketbase/pocketbase/daos"
database "htwkalender/service/db" database "htwkalender/service/db"
localTime "htwkalender/service/functions/time" localTime "htwkalender/service/functions/time"
"log" "log/slog"
) )
func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) { func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
feeds, err := database.GetAllFeeds(db) feeds, err := database.GetAllFeeds(db)
if err != nil { if err != nil {
log.Println("CleanFeeds: get all feeds failed") slog.Error("CleanFeeds: failed to get all feeds", err)
return return
} }
for _, feed := range feeds { for _, feed := range feeds {
@@ -22,13 +23,13 @@ func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
if feedRetrievedTime.Before(timeShift) { if feedRetrievedTime.Before(timeShift) {
// delete feed // delete feed
sqlResult, err := db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute() var sqlResult sql.Result
sqlResult, err = db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute()
if err != nil { if err != nil {
log.Println("CleanFeeds: delete feed " + feed.GetId() + " failed") slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", err)
log.Println(err) slog.Error("SQL Result: ", sqlResult)
log.Println(sqlResult)
} else { } else {
log.Println("CleanFeeds: delete feed " + feed.GetId() + " successful") slog.Info("CleanFeeds: delete feed " + feed.GetId() + " successful")
} }
} }
} }

View File

@@ -8,6 +8,8 @@ import (
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"htwkalender/service/functions" "htwkalender/service/functions"
"io"
"log/slog"
"net/http" "net/http"
"regexp" "regexp"
"strconv" "strconv"
@@ -21,9 +23,14 @@ import (
// FetchAndUpdateSportEvents fetches all sport events from the HTWK sport website // FetchAndUpdateSportEvents fetches all sport events from the HTWK sport website
// it deletes them first and then saves them to the database // it deletes them first and then saves them to the database
// It returns all saved events // It returns all saved events
func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event { func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) ([]model.Event, error) {
sportCourseLinks, err := fetchAllAvailableSportCourses()
if err != nil {
return nil, err
}
var sportCourseLinks = fetchAllAvailableSportCourses()
sportEntries := fetchHTWKSportCourses(sportCourseLinks) sportEntries := fetchHTWKSportCourses(sportCourseLinks)
events := formatEntriesToEvents(sportEntries) events := formatEntriesToEvents(sportEntries)
@@ -43,7 +50,7 @@ func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event {
// get all events from database where name = Feiertage und lehrveranstaltungsfreie Tage // get all events from database where name = Feiertage und lehrveranstaltungsfreie Tage
holidays, err := db.GetAllModulesByNameAndDateRange(app, "Feiertage und lehrveranstaltungsfreie Tage", earliestDate, latestDate) holidays, err := db.GetAllModulesByNameAndDateRange(app, "Feiertage und lehrveranstaltungsfreie Tage", earliestDate, latestDate)
if err != nil { if err != nil {
return nil return nil, err
} }
// remove all events that have same year, month and day as items in holidays // remove all events that have same year, month and day as items in holidays
@@ -60,17 +67,17 @@ func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event {
// @TODO: delete and save events in one transaction and it only should delete events that are not in the new events list and save events that are not in the database // @TODO: delete and save events in one transaction and it only should delete events that are not in the new events list and save events that are not in the database
err = db.DeleteAllEventsForCourse(app, "Sport", functions.GetCurrentSemesterString()) err = db.DeleteAllEventsForCourse(app, "Sport", functions.GetCurrentSemesterString())
if err != nil { if err != nil {
return nil return nil, err
} }
// save events to database // save events to database
savedEvents, err := db.SaveEvents(events, app) savedEvents, err := db.SaveEvents(events, app)
if err != nil { if err != nil {
return nil return nil, err
} }
return savedEvents return savedEvents, nil
} }
@@ -305,13 +312,14 @@ func checkSemester(date time.Time) string {
} }
// fetch the main page where all sport courses are listed and extract all links to the sport courses // fetch the main page where all sport courses are listed and extract all links to the sport courses
func fetchAllAvailableSportCourses() []string { func fetchAllAvailableSportCourses() ([]string, error) {
var url = "https://sport.htwk-leipzig.de/sportangebote" var url = "https://sport.htwk-leipzig.de/sportangebote"
var doc, err = htmlRequest(url) var doc, err = htmlRequest(url)
if err != nil { if err != nil {
return nil slog.Error("Error while fetching sport courses from webpage", err)
return nil, err
} }
// link list of all sport courses // link list of all sport courses
@@ -325,7 +333,7 @@ func fetchAllAvailableSportCourses() []string {
} }
}) })
return links return links, nil
} }
// fetchAllHTWKSportCourses fetches all sport courses from the given links. // fetchAllHTWKSportCourses fetches all sport courses from the given links.
@@ -372,7 +380,13 @@ func htmlRequest(url string) (*goquery.Document, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func(Body io.ReadCloser) {
readErr := Body.Close()
if readErr != nil {
slog.Error("Error while closing response body from html request", readErr)
return
}
}(resp.Body)
doc, err := goquery.NewDocumentFromReader(resp.Body) doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil { if err != nil {

View File

@@ -2,43 +2,18 @@ package v1
import ( import (
"fmt" "fmt"
"github.com/google/uuid"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/net/html"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/date" "htwkalender/service/date"
"htwkalender/service/db"
"htwkalender/service/fetch" "htwkalender/service/fetch"
"net/http"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/net/html"
) )
func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error {
seminarGroupsLabel := db.GetAllCourses(app)
seminarGroups := GetSeminarGroupsEventsFromHTML(seminarGroupsLabel)
seminarGroups = ClearEmptySeminarGroups(seminarGroups)
seminarGroups = ReplaceEmptyEventNames(seminarGroups)
savedRecords, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
if dbError != nil {
return apis.NewNotFoundError("Events could not be saved", dbError.Error())
}
return c.JSON(http.StatusOK, savedRecords)
}
func ReplaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup { func ReplaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup {
for i, group := range groups { for i, group := range groups {
for j, event := range group.Events { for j, event := range group.Events {

View File

@@ -3,13 +3,12 @@ package v1
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"io" "io"
"log/slog"
"net/http" "net/http"
) )
@@ -41,12 +40,23 @@ func getSeminarHTML(semester string) (string, error) {
} }
func SeminarGroups(c echo.Context, app *pocketbase.PocketBase) error { func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) {
var groups []model.SeminarGroup var groups []model.SeminarGroup
resultSummer, _ := getSeminarHTML("ss") resultSummer, err := getSeminarHTML("ss")
if err != nil {
slog.Error("Error while fetching seminar groups for winter semester", err)
return nil, err
}
resultWinter, _ := getSeminarHTML("ws") resultWinter, _ := getSeminarHTML("ws")
if err != nil {
slog.Error("Error while fetching seminar groups for summer semester", err)
return nil, err
}
groups = parseSeminarGroups(resultSummer) groups = parseSeminarGroups(resultSummer)
groups = append(groups, parseSeminarGroups(resultWinter)...) groups = append(groups, parseSeminarGroups(resultWinter)...)
@@ -55,16 +65,18 @@ func SeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
collection, dbError := db.FindCollection(app, "groups") collection, dbError := db.FindCollection(app, "groups")
if dbError != nil { if dbError != nil {
return apis.NewNotFoundError("Collection not found", dbError) slog.Error("Error while searching collection groups", dbError)
return nil, err
} }
var insertedGroups []*models.Record var insertedGroups []*models.Record
insertedGroups, dbError = db.SaveGroups(groups, collection, app) insertedGroups, dbError = db.SaveGroups(groups, collection, app)
if dbError != nil { if dbError != nil {
return apis.NewNotFoundError("Records could not be saved", dbError) slog.Error("Error while saving groups", dbError)
return nil, err
} }
return c.JSON(http.StatusOK, insertedGroups) return insertedGroups, nil
} }
func removeDuplicates(groups []model.SeminarGroup) []model.SeminarGroup { func removeDuplicates(groups []model.SeminarGroup) []model.SeminarGroup {

View File

@@ -1,34 +1,27 @@
package v2 package v2
import ( import (
"fmt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"golang.org/x/net/html" "golang.org/x/net/html"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"htwkalender/service/fetch" "htwkalender/service/fetch"
"strconv" localTime "htwkalender/service/functions/time"
"strings" "strings"
"time"
) )
func ParseEventsFromRemote(c echo.Context, app *pocketbase.PocketBase) error { func ParseEventsFromRemote(app *pocketbase.PocketBase) (model.Events, error) {
savedRecords, err := FetchAllEventsAndSave(app, localTime.RealClock{})
err, savedRecords := FetchAllEventsAndSave(app)
if err != nil { if err != nil {
return err return nil, err
} else {
savedRecordsLength := strconv.FormatInt(int64(len(savedRecords)), 10)
return c.JSON(200, "Successfully saved "+savedRecordsLength+" events")
} }
return savedRecords, nil
} }
func FetchAllEventsAndSave(app *pocketbase.PocketBase) (error, []model.Event) { func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([]model.Event, error) {
var err error
var savedRecords []model.Event var savedRecords []model.Event
var events []model.Event
var stubUrl = [2]string{ var stubUrl = [2]string{
"https://stundenplan.htwk-leipzig.de/", "https://stundenplan.htwk-leipzig.de/",
@@ -55,22 +48,32 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase) (error, []model.Event) {
"%0A?&template=sws_modul&weeks=1-65&combined=yes", "%0A?&template=sws_modul&weeks=1-65&combined=yes",
} }
if (time.Now().Month() >= 3) && (time.Now().Month() <= 10) { if (clock.Now().Month() >= 3) && (clock.Now().Month() <= 10) {
url := stubUrl[0] + "ss" + stubUrl[1] url := stubUrl[0] + "ss" + stubUrl[1]
events, err = parseEventForOneSemester(url) events, err := parseEventForOneSemester(url)
if err != nil {
return nil, fmt.Errorf("failed to parse events for summmer semester: %w", err)
}
savedEvents, dbError := db.SaveEvents(events, app) savedEvents, dbError := db.SaveEvents(events, app)
err = dbError if dbError != nil {
savedRecords = append(savedRecords, savedEvents...) return nil, fmt.Errorf("failed to save events: %w", dbError)
}
savedRecords = append(savedEvents, events...)
} }
if (time.Now().Month() >= 9) || (time.Now().Month() <= 4) { if (clock.Now().Month() >= 9) || (clock.Now().Month() <= 4) {
url := stubUrl[0] + "ws" + stubUrl[1] url := stubUrl[0] + "ws" + stubUrl[1]
events, err = parseEventForOneSemester(url) events, err := parseEventForOneSemester(url)
if err != nil {
return nil, fmt.Errorf("failed to parse events for winter semester: %w", err)
}
savedEvents, dbError := db.SaveEvents(events, app) savedEvents, dbError := db.SaveEvents(events, app)
err = dbError if dbError != nil {
return nil, fmt.Errorf("failed to save events: %w", dbError)
}
savedRecords = append(savedRecords, savedEvents...) savedRecords = append(savedRecords, savedEvents...)
} }
return err, savedRecords return savedRecords, nil
} }
func parseEventForOneSemester(url string) ([]model.Event, error) { func parseEventForOneSemester(url string) ([]model.Event, error) {
@@ -81,25 +84,18 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
} }
// Parse HTML to Node Tree // Parse HTML to Node Tree
doc, err2 := parseHTML(err, webpage) var doc *html.Node
if err2 != nil { doc, err = parseHTML(webpage, err)
return nil, err2 if err != nil {
return nil, err
} }
// Get all event tables and all day labels // Get all event tables and all day labels
eventTables := getEventTables(doc) eventTables := getEventTables(doc)
allDayLabels := getAllDayLabels(doc) allDayLabels := getAllDayLabels(doc)
if eventTables == nil || allDayLabels == nil {
return nil, err
}
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels) eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
if eventsWithCombinedWeeks == nil {
return nil, err
}
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks) splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
events := splitEventsBySingleWeek(splitEventsByWeekVal) events := splitEventsBySingleWeek(splitEventsByWeekVal)
@@ -108,6 +104,11 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
} }
table := findFirstTable(doc) table := findFirstTable(doc)
if table == nil {
return nil, fmt.Errorf("failed to find first table")
}
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
semester, year := extractSemesterAndYear(semesterString) semester, year := extractSemesterAndYear(semesterString)
events = convertWeeksToDates(events, semester, year) events = convertWeeksToDates(events, semester, year)
@@ -131,7 +132,7 @@ func switchNameAndNotesForExam(events []model.Event) []model.Event {
return events return events
} }
func parseHTML(err error, webpage string) (*html.Node, error) { func parseHTML(webpage string, err error) (*html.Node, error) {
doc, err := html.Parse(strings.NewReader(webpage)) doc, err := html.Parse(strings.NewReader(webpage))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -5,23 +5,21 @@ import (
"encoding/json" "encoding/json"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"net/http"
"time" "time"
"github.com/jordic/goics" "github.com/jordic/goics"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/apis"
) )
const expirationTime = 5 * time.Minute const expirationTime = 5 * time.Minute
func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error { func Feed(app *pocketbase.PocketBase, token string) (string, error) {
var result string var result string
var responseWriter = c.Response().Writer
feed, err := db.FindFeedByToken(token, app) feed, err := db.FindFeedByToken(token, app)
if feed == nil && err != nil { if feed == nil && err != nil {
return c.JSON(http.StatusNotFound, err) return "", err
} }
modules := make(map[string]model.FeedCollection) modules := make(map[string]model.FeedCollection)
@@ -34,35 +32,26 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error {
newFeed, err := createFeedForToken(app, modules) newFeed, err := createFeedForToken(app, modules)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) return "", err
} }
result = newFeed.Content result = newFeed.Content
responseWriter.Header().Set("Content-type", "text/calendar") return result, nil
responseWriter.Header().Set("charset", "utf-8")
responseWriter.Header().Set("Content-Disposition", "inline")
responseWriter.Header().Set("filename", "calendar.ics")
writeSuccess(result, responseWriter)
c.Response().Writer = responseWriter
return nil
} }
func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) { func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) {
res := db.GetPlanForModules(app, modules) res, err := db.GetPlanForModules(app, modules)
if err != nil {
return nil, apis.NewNotFoundError("Could not fetch events", err)
}
b := bytes.Buffer{} b := bytes.Buffer{}
goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules}) goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules})
feed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)} feed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)}
return feed, nil return feed, nil
} }
func writeSuccess(message string, w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(message))
if err != nil {
return
}
}
func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) { func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) {
var modules []model.FeedCollection var modules []model.FeedCollection

View File

@@ -1,32 +1,41 @@
package room package room
import ( import (
"github.com/pocketbase/pocketbase"
"htwkalender/model" "htwkalender/model"
"htwkalender/service/db" "htwkalender/service/db"
"net/http"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
) )
func GetRooms(c echo.Context, app *pocketbase.PocketBase) error { func GetRooms(app *pocketbase.PocketBase) ([]string, error) {
rooms := db.GetRooms(app) rooms, err := db.GetRooms(app)
return c.JSON(http.StatusOK, rooms) if err != nil {
return nil, err
} else {
return rooms, nil
}
} }
func GetRoomScheduleForDay(c echo.Context, app *pocketbase.PocketBase, room string, date string) error { func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) ([]model.AnonymizedEventDTO, error) {
events := db.GetRoomScheduleForDay(app, room, date) roomSchedule, err := db.GetRoomScheduleForDay(app, room, date)
return c.JSON(http.StatusOK, anonymizeRooms(events)) if err != nil {
return nil, err
}
anonymizedRoomSchedule := anonymizeRooms(roomSchedule)
return anonymizedRoomSchedule, nil
} }
func GetRoomSchedule(c echo.Context, app *pocketbase.PocketBase, room string, from string, to string) error { func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to string) ([]model.AnonymizedEventDTO, error) {
events := db.GetRoomSchedule(app, room, from, to) roomSchedule, err := db.GetRoomSchedule(app, room, from, to)
return c.JSON(http.StatusOK, anonymizeRooms(events)) if err != nil {
return nil, err
}
anonymizedRoomSchedule := anonymizeRooms(roomSchedule)
return anonymizedRoomSchedule, nil
} }
// Transform the events to anonymized events throwing away all unnecessary information // Transform the events to anonymized events throwing away all unnecessary information
func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO { func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO {
var anonymizedEvents = []model.AnonymizedEventDTO{} var anonymizedEvents []model.AnonymizedEventDTO
for _, event := range events { for _, event := range events {
anonymizedEvents = append(anonymizedEvents, event.AnonymizeEvent()) anonymizedEvents = append(anonymizedEvents, event.AnonymizeEvent())
} }

View File

@@ -55,7 +55,7 @@ func Test_anonymizeRooms(t *testing.T) {
args: args{ args: args{
events: []model.Event{}, events: []model.Event{},
}, },
want: []model.AnonymizedEventDTO{}, want: nil,
}, },
{ {
name: "anonymize multiple events", name: "anonymize multiple events",

File diff suppressed because it is too large Load Diff

6
package-lock.json generated
View File

@@ -1,6 +0,0 @@
{
"name": "htwkalender",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}