diff --git a/services/data-manager/service/addRoute.go b/services/data-manager/service/addRoute.go index 25aa462..562b45d 100644 --- a/services/data-manager/service/addRoute.go +++ b/services/data-manager/service/addRoute.go @@ -78,6 +78,42 @@ func AddRoutes(services serviceModel.Service) { return nil }) + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { + _, err := e.Router.AddRoute(echo.Route{ + Method: http.MethodGet, + Path: "/api/fetch/group", + Handler: func(c echo.Context) error { + + seminarGroupString := c.QueryParam("seminarGroup") + + if seminarGroupString == "" { + return c.JSON(http.StatusBadRequest, "Seminar group could not be empty") + } else { + //find seminar group by name + seminarGroup, err := services.CourseService.FindCourseByCourseName(seminarGroupString) + if err != nil { + return c.JSON(http.StatusBadRequest, "Failed to find seminar group") + } + + events, err := services.EventService.UpdateModulesForCourse(seminarGroup) + if err != nil { + return c.JSON(http.StatusBadRequest, "Failed to fetch seminar group") + } + return c.JSON(http.StatusOK, events) + } + + }, + Middlewares: []echo.MiddlewareFunc{ + apis.ActivityLogger(services.App), + apis.RequireAdminAuth(), + }, + }) + if err != nil { + return err + } + return nil + }) + services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ Method: http.MethodGet, diff --git a/services/data-manager/service/db/dbGroups.go b/services/data-manager/service/db/dbGroups.go index 5e70e21..eef2641 100644 --- a/services/data-manager/service/db/dbGroups.go +++ b/services/data-manager/service/db/dbGroups.go @@ -146,3 +146,17 @@ func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester str return courseArray, nil } + +func FindCourseByCourseName(app *pocketbase.PocketBase, courseName string) (model.SeminarGroup, error) { + + var course SeminarGroup + + // get the course by its name + err := app.Dao().DB().Select("*").From("groups").Where(dbx.NewExp("course = {:course}", dbx.Params{"course": courseName})).One(&course) + if err != nil { + slog.Error("Error while getting group from database: ", "error", err) + return model.SeminarGroup{}, err + } + + return course.toSeminarGroupModel(), nil +} diff --git a/services/data-manager/service/events/courseService.go b/services/data-manager/service/events/courseService.go index 65cc1a8..3bf460e 100644 --- a/services/data-manager/service/events/courseService.go +++ b/services/data-manager/service/events/courseService.go @@ -28,6 +28,7 @@ type CourseService interface { GetAllCourses() []string GetAllCoursesForSemester(semester string) []model.SeminarGroup GetAllCoursesForSemesterWithEvents(semester string) ([]string, error) + FindCourseByCourseName(courseName string) (model.SeminarGroup, error) } // PocketBaseCourseService is a struct that implements the CourseService interface @@ -72,3 +73,8 @@ func removeEmptyCourses(courses []string) []string { } return filteredCourses } + +// FindCourseByCourseName returns a course by its name +func (s *PocketBaseCourseService) FindCourseByCourseName(courseName string) (model.SeminarGroup, error) { + return db.FindCourseByCourseName(s.app, courseName) +} diff --git a/services/data-manager/service/fetch/v1/fetchSeminarEventService.go b/services/data-manager/service/fetch/v1/fetchSeminarEventService.go index 4a02776..9a0aa1a 100644 --- a/services/data-manager/service/fetch/v1/fetchSeminarEventService.go +++ b/services/data-manager/service/fetch/v1/fetchSeminarEventService.go @@ -98,8 +98,13 @@ func parseSeminarGroup(result string) model.SeminarGroup { } table := findFirstTable(doc) - eventTables := getEventTables(doc) allDayLabels := getAllDayLabels(doc) + eventTables := getEventTables(doc, allDayLabels) + + if table == nil { + slog.Error("Failed to find first table") + return model.SeminarGroup{} + } course := findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data @@ -113,7 +118,7 @@ func parseSeminarGroup(result string) model.SeminarGroup { } } - eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels, course) + eventsWithCombinedWeeks := toEvents(eventTables, course) splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks) events := splitEventsBySingleWeek(splitEventsByWeekVal) events = convertWeeksToDates(events, semester, year) @@ -210,18 +215,18 @@ func extractSemesterAndYear(semesterString string) (string, string) { return semesterShortcut, year } -func toEvents(tables [][]*html.Node, days []string, course string) []model.Event { +func toEvents(tables map[string][]*html.Node, course string) []model.Event { var events []model.Event - for table := range tables { - for row := range tables[table] { + for day := range tables { + for row := range tables[day] { - tableData := findTableData(tables[table][row]) + tableData := findTableData(tables[day][row]) if len(tableData) > 0 { start, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[1]))) end, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[2]))) events = append(events, model.Event{ - Day: days[table], + Day: day, Week: getTextContent(tableData[0]), Start: start, End: end, diff --git a/services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go b/services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go index f625705..d93ee4d 100644 --- a/services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go +++ b/services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/pocketbase/pocketbase/tools/types" "htwkalender/data-manager/model" + "os" "reflect" "testing" "time" @@ -588,3 +589,259 @@ func TestIsWinterSemester(t *testing.T) { }) } } + +func Test_parseSeminarGroup(t *testing.T) { + type args struct { + result string + } + + //read string from fil + byteArray, err := os.ReadFile("tests/seminarGroup.html") + if err != nil { + t.Errorf("Error reading file: %v", err) + } + + htmlString := string(byteArray) + + tests := []struct { + name string + args args + want model.SeminarGroup + }{ + { + name: "Test 1", + args: args{ + result: htmlString, + }, + want: model.SeminarGroup{ + Course: "23SAM", + University: "HTWK Leipzig", + Events: []model.Event{ + { + UUID: "15e6d285-5ecd-5039-b4b2-d6fcc3dbc1a7", + Day: "Dienstag", + Week: "42", + Start: parseDateTime("2024-10-15 09:15:00.000Z"), + End: parseDateTime("2024-10-15 10:45:00.000Z"), + Name: "Leitungskompetenzen II", + Notes: "Leitungshandeln", + Prof: "Prof. Dr. phil. Grit Behse-Bartels SA-M 3. FS (pf)", + Rooms: "LI119-S", + BookedAt: "13/06/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "15e6d285-5ecd-5039-b4b2-d6fcc3dbc1a7", + Day: "Dienstag", + Week: "43", + Start: parseDateTime("2024-10-22 09:15:00.000Z"), + End: parseDateTime("2024-10-22 10:45:00.000Z"), + Name: "Leitungskompetenzen II", + Notes: "Leitungshandeln", + Prof: "Prof. Dr. phil. Grit Behse-Bartels SA-M 3. FS (pf)", + Rooms: "LI119-S", + BookedAt: "13/06/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "15e6d285-5ecd-5039-b4b2-d6fcc3dbc1a7", + Day: "Dienstag", + Week: "44", + Start: parseDateTime("2024-10-29 10:15:00.000Z"), + End: parseDateTime("2024-10-29 11:45:00.000Z"), + Name: "Leitungskompetenzen II", + Notes: "Leitungshandeln", + Prof: "Prof. Dr. phil. Grit Behse-Bartels", + Rooms: "LI119-S", + BookedAt: "13/06/2024", + Course: "SA-M 3. FS (pf)", + EventType: "S", + Compulsory: "p", + Semester: "23SAM ws", + }, + { + UUID: "15e6d285-5ecd-5039-b4b2-d6fcc3dbc1a7", + Day: "Dienstag", + Week: "54", + Start: parseDateTime("2025-01-07 10:15:00.000Z"), + End: parseDateTime("2025-01-07 11:45:00.000Z"), + Name: "Leitungskompetenzen II SA-M 3. FS (pf)", + Notes: "Leitungshandeln", + Prof: "Prof. Dr. phil. Grit Behse-Bartels", + Rooms: "LI119-S", + BookedAt: "13/06/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "15e6d285-5ecd-5039-b4b2-d6fcc3dbc1a7", + Day: "Dienstag", + Week: "55", + Start: parseDateTime("2025-01-14 10:15:00.000Z"), + End: parseDateTime("2025-01-14 11:45:00.000Z"), + Name: "Leitungskompetenzen II SA-M 3. FS (pf)", + Notes: "Leitungshandeln", + Prof: "Prof. Dr. phil. Grit Behse-Bartels", + Rooms: "LI119-S", + BookedAt: "13/06/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "6ebe83db-f29e-5ddd-ae8f-8724b5ba8959", + Day: "Mittwoch", + Week: "47", + Start: parseDateTime("2024-11-20 06:00:00.000Z"), + End: parseDateTime("2024-11-20 23:00:00.000Z"), + Name: "Feiertage und lehrveranstaltungsfreie Tage", + Notes: "Buß- und Bettag", + Prof: "", + Rooms: "", + BookedAt: "30/07/2024", + Course: "23SAM", + EventType: "Sperr", + Compulsory: "", + Semester: "ws", + }, + { + UUID: "703e19b7-06ab-543d-a759-4ef72627594c", + Day: "Mittwoch", + Week: "43", + Start: parseDateTime("2024-10-23 07:30:00.000Z"), + End: parseDateTime("2024-10-23 10:45:00.000Z"), + Name: "Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)", + Notes: "", + Prof: "LBA Sarah Otto", + Rooms: "LI016-S", + BookedAt: "27/08/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "703e19b7-06ab-543d-a759-4ef72627594c", + Day: "Mittwoch", + Week: "46", + Start: parseDateTime("2024-11-13 08:30:00.000Z"), + End: parseDateTime("2024-11-13 11:45:00.000Z"), + Name: "Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)", + Notes: "", + Prof: "LBA Sarah Otto", + Rooms: "LI115-L", + BookedAt: "18/09/2024", + Course: "23SAM", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "703e19b7-06ab-543d-a759-4ef72627594c", + Day: "Mittwoch", + Week: "48", + Start: parseDateTime("2024-11-27 08:30:00.000Z"), + End: parseDateTime("2024-11-27 11:45:00.000Z"), + Name: "Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)", + Notes: "", + Prof: "LBA Sarah Otto", + Rooms: "LI201-S", + BookedAt: "18/09/2024", + Course: "SAM23", + EventType: "S", + Compulsory: "p", + Semester: "ws", + }, + { + UUID: "02d01aad-1542-574d-b597-aa5ac9ff0179", + Day: "Mittwoch", + Week: "42", + Start: parseDateTime("2024-10-16 11:45:00.000Z"), + End: parseDateTime("2024-10-16 18:30:00.000Z"), + Name: "zentrale Gremienzeit", + Notes: "", + Prof: "", + Rooms: "", + BookedAt: "09/09/2024", + Course: "23SAM", + EventType: "Sperr", + Compulsory: "", + Semester: "ws", + }, + { + UUID: "02d01aad-1542-574d-b597-aa5ac9ff0179", + Day: "Mittwoch", + Week: "44", + Start: parseDateTime("2024-10-30 12:45:00.000Z"), + End: parseDateTime("2024-10-30 19:30:00.000Z"), + Name: "zentrale Gremienzeit", + Notes: "", + Prof: "", + Rooms: "", + BookedAt: "09/09/2024", + Course: "23SAM", + EventType: "Sperr", + Compulsory: "", + Semester: "ws", + }, + { + UUID: "6ebe83db-f29e-5ddd-ae8f-8724b5ba8959", + Day: "Donnerstag", + Week: "44", + Start: parseDateTime("2024-10-31 06:00:00.000Z"), + End: parseDateTime("2024-10-31 23:00:00.000Z"), + Name: "Feiertage und lehrveranstaltungsfreie Tage", + Notes: "Reformationstag", + Prof: "", + Rooms: "", + BookedAt: "30/07/2024", + Course: "23SAM", + EventType: "Sperr", + Compulsory: "", + Semester: "ws", + }, + { + UUID: "6ebe83db-f29e-5ddd-ae8f-8724b5ba8959", + Day: "Freitag", + Week: "44", + Start: parseDateTime("2024-11-01 06:00:00.000Z"), + End: parseDateTime("2024-11-01 23:00:00.000Z"), + Name: "Feiertage und lehrveranstaltungsfreie Tage", + Notes: "Brückentag Reformationstag", + Prof: "", + Rooms: "", + BookedAt: "30/07/2024", + Course: "23SAM", + EventType: "Sperr", + Compulsory: "", + Semester: "ws", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseSeminarGroup(tt.args.result); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseSeminarGroup() = %v, want %v", got, tt.want) + } + }) + } +} + +func parseDateTime(timeString string) types.DateTime { + dateTime, err := types.ParseDateTime(timeString) + if err != nil { + fmt.Println(err) + } + return dateTime +} diff --git a/services/data-manager/service/fetch/v1/htmlParsingFunctions.go b/services/data-manager/service/fetch/v1/htmlParsingFunctions.go index cf795f2..ac409aa 100644 --- a/services/data-manager/service/fetch/v1/htmlParsingFunctions.go +++ b/services/data-manager/service/fetch/v1/htmlParsingFunctions.go @@ -18,6 +18,7 @@ package v1 import ( "golang.org/x/net/html" + "log/slog" "strings" ) @@ -39,6 +40,10 @@ func findFirstTable(node *html.Node) *html.Node { // Find the first element with the specified class attribute value func findFirstSpanWithClass(node *html.Node, classValue string) *html.Node { + if node == nil { + return nil + } + // Check if the current node is a element with the specified class attribute value if node.Type == html.ElementNode && node.Data == "span" { if hasClassAttribute(node, classValue) { @@ -66,20 +71,45 @@ func hasClassAttribute(node *html.Node, classValue string) bool { return false } +type dayTable struct { + day string + table []*html.Node +} + // Get Tables with days -func getEventTables(node *html.Node) [][]*html.Node { - var eventTables [][]*html.Node +func getEventTables(node *html.Node, dayLabels []string) map[string][]*html.Node { + // Create a map to store the tables with the corresponding day from the dayLabels + dayTablesMap := make(map[string][]*html.Node) + tables := findTables(node) - // get all tables with events - for events := range tables { - rows := findTableRows(tables[events]) - // check that a first row exists - if len(rows) > 0 { - rows = rows[1:] - eventTables = append(eventTables, rows) + + // Ensure we have the same number of tables as day labels + if len(tables) != len(dayLabels) { + // Handle the case where the number of tables doesn't match the dayLabels (log error or return early) + slog.Error("Number of tables does not match number of day labels") + return dayTablesMap // Returning empty map + } + + // Iterate over dayLabels and their corresponding tables + for i, day := range dayLabels { + rows := findTableRows(tables[i]) + + // check that rows exist and skip the header + if len(rows) > 1 { + rows = rows[1:] // Skip header row + // Add the event rows to the map entry for this day + dayTablesMap[day] = rows } } - return eventTables + + // Remove days that have no events (empty slices) + for day, eventTable := range dayTablesMap { + if len(eventTable) == 0 { + delete(dayTablesMap, day) + } + } + + return dayTablesMap } // Get Tables with days diff --git a/services/data-manager/service/fetch/v1/tests/seminarGroup.html b/services/data-manager/service/fetch/v1/tests/seminarGroup.html new file mode 100644 index 0000000..0497ec9 --- /dev/null +++ b/services/data-manager/service/fetch/v1/tests/seminarGroup.html @@ -0,0 +1,352 @@ + + + + sws_semgrp + + + + + + + + + + + + + + + + + +
+ + + + + +
Wintersemester 2024/25 (Planungszeitraum 01.09.2024 bis 28.02.2025)-gültig
+
+ + + + + +
HTWK Leipzig
+
+ + + + + +
Seminargruppenplan 23SAM
+
+ + + + + +
Plannungswoche 1-65 (alle Wochen anzeigen)
+
+ + + + + +
+
+ + + + + +
+
+ +

Montag

+ + +
+ +

Dienstag

+ + + + + + + + + + + + + + + + + + + + + + + + + +
PlanungswochenBeginnEndeVeranstaltungArtDozentRäumeBemerkungenGebucht am
42-44, 54-5511:1512:453.2 Leitungskompetenzen II SA-M 3. FS (pf)SpProf. Dr. phil. Grit Behse-BartelsLI119-SLeitungshandeln13/06/2024
+ +

Mittwoch

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlanungswochenBeginnEndeVeranstaltungArtDozentRäumeBemerkungenGebucht am
477:000:00Feiertage und lehrveranstaltungsfreie TageSperr  Buß- und Bettag30/07/2024
439:3012:453.5 Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)Sp LI016-SLBA Sarah Otto27/08/2024
469:3012:453.5 Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)Sp LI115-LLBA Sarah Otto18/09/2024
489:3012:453.5 Ausgew. Thema aus dem Thema Fachdiskurs Soz. Arbeit SA-M 3. FS (pf)Sp LI201-SLBA Sarah Otto18/09/2024
42, 4413:4520:30zentrale GremienzeitSperr   09/09/2024
+ +

Donnerstag

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlanungswochenBeginnEndeVeranstaltungArtDozentRäumeBemerkungenGebucht am
447:000:00Feiertage und lehrveranstaltungsfreie TageSperr  Reformationstag30/07/2024
+ +

Freitag

+ + + + + + + + + + + + + + + + + + + + + + + + + +
PlanungswochenBeginnEndeVeranstaltungArtDozentRäumeBemerkungenGebucht am
447:000:00Feiertage und lehrveranstaltungsfreie TageSperr  Brückentag Reformationstag30/07/2024
+ +

Samstag

+ + +
+ +

Sonntag

+ + +
+ + + + + + + + + + + + + \ No newline at end of file