From 3e07451c4782affd43b7b919e5e7b1dba05d4367 Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Tue, 22 Apr 2025 12:24:05 +0200 Subject: [PATCH] fix:#65 updated db model for proxyrecord --- services/data-manager/model/seminarGroup.go | 6 + services/data-manager/service/db/dbEvents.go | 264 +++++++++++++++--- services/data-manager/service/db/dbFeeds.go | 112 +++++++- .../data-manager/service/db/dbFunctions.go | 27 -- services/data-manager/service/db/dbGroups.go | 105 ++++++- .../service/events/courseService.go | 16 +- .../fetch/v1/fetchSeminarGroupService.go | 16 +- .../data-manager/service/fetch/v2/fetcher.go | 2 +- services/data-manager/service/ical/ical.go | 2 +- 9 files changed, 451 insertions(+), 99 deletions(-) delete mode 100644 services/data-manager/service/db/dbFunctions.go diff --git a/services/data-manager/model/seminarGroup.go b/services/data-manager/model/seminarGroup.go index cff3235..5d514f3 100644 --- a/services/data-manager/model/seminarGroup.go +++ b/services/data-manager/model/seminarGroup.go @@ -26,3 +26,9 @@ type SeminarGroup struct { Semester string Events []Event } + +type SeminarGroups []SeminarGroup + +func (group *SeminarGroup) UniqueKey() string { + return group.Course + group.Semester +} diff --git a/services/data-manager/service/db/dbEvents.go b/services/data-manager/service/db/dbEvents.go index 2d43adc..4d43826 100644 --- a/services/data-manager/service/db/dbEvents.go +++ b/services/data-manager/service/db/dbEvents.go @@ -29,47 +29,212 @@ import ( "github.com/pocketbase/pocketbase" ) -func SaveSeminarGroupEvents(seminarGroup model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) { - var toBeSavedEvents model.Events - var savedRecords model.Events +var _ core.RecordProxy = (*Event)(nil) - // check if event is already in database and add to toBeSavedEvents if not - for _, event := range seminarGroup.Events { - event = event.SetCourse(seminarGroup.Course) - existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app) - alreadyAddedToSave := toBeSavedEvents.Contains(event) - - if err != nil { - return nil, err - } - - if !existsInDatabase && !alreadyAddedToSave { - toBeSavedEvents = append(toBeSavedEvents, event) - } - } - - // create record for each event that's not already in the database - for _, event := range toBeSavedEvents { - event.MarkAsNew() - // auto mapping for event fields to record fields - err := app.Save(&event) - if err != nil { - return nil, err - } else { - savedRecords = append(savedRecords, event) - } - } - - return savedRecords, nil +type Event struct { + core.BaseRecordProxy } -func SaveEvents(events []model.Event, base *pocketbase.PocketBase) ([]model.Event, error) { +type Events []*Event + +func (event *Event) GetDay() string { + return event.GetString("Day") +} + +func (event *Event) SetDay(day string) { + event.Set("Day", day) +} + +func (event *Event) GetWeek() string { + return event.GetString("Week") +} + +func (event *Event) SetWeek(week string) { + event.Set("Week", week) +} + +func (event *Event) GetName() string { + return event.GetString("Name") +} + +func (event *Event) SetName(name string) { + event.Set("Name", name) +} + +func (event *Event) GetEventType() string { + return event.GetString("EventType") +} + +func (event *Event) SetEventType(eventType string) { + event.Set("EventType", eventType) +} + +func (event *Event) GetProf() string { + return event.GetString("Prof") +} + +func (event *Event) SetProf(prof string) { + event.Set("Prof", prof) +} + +func (event *Event) GetRooms() string { + return event.GetString("Rooms") +} + +func (event *Event) SetRooms(rooms string) { + event.Set("Rooms", rooms) +} + +func (event *Event) GetNotes() string { + return event.GetString("Notes") +} + +func (event *Event) SetNotes(notes string) { + event.Set("Notes", notes) +} + +func (event *Event) GetBookedAt() string { + return event.GetString("BookedAt") +} + +func (event *Event) SetBookedAt(bookedAt string) { + event.Set("BookedAt", bookedAt) +} + +func (event *Event) GetCourse() string { + return event.GetString("course") +} + +func (event *Event) SetCourse(course string) { + event.Set("course", course) +} + +func (event *Event) GetSemester() string { + return event.GetString("semester") +} + +func (event *Event) SetSemester(semester string) { + event.Set("semester", semester) +} + +func (event *Event) GetUUID() string { + return event.GetString("uuid") +} + +func (event *Event) SetUUID(uuid string) { + event.Set("uuid", uuid) +} + +func (event *Event) GetStart() types.DateTime { + return event.GetDateTime("start") +} + +func (event *Event) SetStart(start types.DateTime) { + event.Set("start", start) +} + +func (event *Event) GetEnd() types.DateTime { + return event.GetDateTime("end") +} + +func (event *Event) SetEnd(end types.DateTime) { + event.Set("end", end) +} + +func (event *Event) GetCompulsory() string { + return event.GetString("Compulsory") +} + +func (event *Event) SetCompulsory(compulsory string) { + event.Set("Compulsory", compulsory) +} + +func (event *Event) GetCreated() types.DateTime { + return event.GetDateTime("created") +} + +func (event *Event) SetCreated(created types.DateTime) { + event.Set("created", created) +} + +func (event *Event) GetUpdated() types.DateTime { + return event.GetDateTime("updated") +} + +func (event *Event) SetUpdated(updated types.DateTime) { + event.Set("updated", updated) +} + +func newEvent(record *core.Record) *Event { + return &Event{ + BaseRecordProxy: core.BaseRecordProxy{Record: record}, + } +} + +func NewEvent(collection *core.Collection, event model.Event) (*Event, error) { + // Create a new Event instance + if collection.Name != "events" { + return nil, core.ErrInvalidFieldValue + } + + record := core.NewRecord(collection) + record.Set("id:autogenerate", "") + + ev := newEvent(record) + // Set the fields from the model + ev.SetDay(event.Day) + ev.SetWeek(event.Week) + ev.SetStart(event.Start) + ev.SetEnd(event.End) + ev.SetName(event.Name) + ev.SetEventType(event.EventType) + ev.SetProf(event.Prof) + ev.SetRooms(event.Rooms) + ev.SetNotes(event.Notes) + ev.SetBookedAt(event.BookedAt) + ev.SetCourse(event.Course) + ev.SetSemester(event.Semester) + ev.SetUUID(uuid.NewString()) + return ev, nil +} + +func (event *Event) ToModel() model.Event { + return model.Event{ + Day: event.GetDay(), + Week: event.GetWeek(), + Start: event.GetStart(), + End: event.GetEnd(), + Name: event.GetName(), + EventType: event.GetEventType(), + Prof: event.GetProf(), + Rooms: event.GetRooms(), + Notes: event.GetNotes(), + BookedAt: event.GetBookedAt(), + Course: event.GetCourse(), + Semester: event.GetSemester(), + UUID: event.GetUUID(), + } +} + +func (events *Events) ToEvents() model.Events { + var result model.Events + for _, event := range *events { + result = append(result, event.ToModel()) + } + return result +} + +func SaveSeminarGroupEvents(seminarGroup model.SeminarGroup, base *pocketbase.PocketBase) (model.Events, error) { + return SaveEvents(seminarGroup.Events, base.App) +} + +func SaveEvents(events []model.Event, app core.App) (model.Events, error) { var toBeSavedEvents model.Events var savedRecords model.Events // check if event is already in database and add to toBeSavedEvents if not for _, event := range events { - existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, event.Course, base) + existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, event.Course, app) alreadyAddedToSave := toBeSavedEvents.Contains(event) if err != nil { @@ -80,26 +245,45 @@ func SaveEvents(events []model.Event, base *pocketbase.PocketBase) ([]model.Even toBeSavedEvents = append(toBeSavedEvents, event) } } + + collection, err := app.FindCollectionByNameOrId("events") + if err != nil { + return nil, err + } + // create record for each event that's not already in the database for _, event := range toBeSavedEvents { - event.MarkAsNew() // auto mapping for event fields to record fields - err := base.Save(&event) - if err != nil { - return nil, err + savedEvent, saveErr := saveEvent(collection, event, app) + if saveErr != nil { + return nil, saveErr } else { - savedRecords = append(savedRecords, event) + savedRecords = append(savedRecords, savedEvent.ToModel()) } } return savedRecords, nil } +func saveEvent(collection *core.Collection, event model.Event, app core.App) (*Event, error) { + dbEvent, recordErr := NewEvent(collection, event) + if recordErr != nil { + return nil, recordErr + } + // auto mapping for event fields to record fields + dbErr := app.Save(dbEvent) + if dbErr != nil { + return nil, dbErr + } + + return dbEvent, nil +} + // check if event is already in database and return true if it is and false if it's not -func findEventByDayWeekStartEndNameCourse(event model.Event, course string, base *pocketbase.PocketBase) (bool, error) { +func findEventByDayWeekStartEndNameCourse(event model.Event, course string, app core.App) (bool, error) { var dbEvent model.Event - err := base.DB().Select("*").From("events"). + err := app.DB().Select("*").From("events"). Where(dbx.NewExp( "Day = {:day} AND "+ "Week = {:week} AND "+ diff --git a/services/data-manager/service/db/dbFeeds.go b/services/data-manager/service/db/dbFeeds.go index a50215d..680eff2 100644 --- a/services/data-manager/service/db/dbFeeds.go +++ b/services/data-manager/service/db/dbFeeds.go @@ -21,19 +21,117 @@ import ( "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tools/types" "htwkalender/data-manager/model" "time" ) -func SaveFeed(feed model.Feed, collection *core.Collection, app *pocketbase.PocketBase) (*core.Record, error) { - record := core.NewRecord(collection) - record.Set("modules", feed.Modules) - err := app.Save(record) +var _ core.RecordProxy = (*Feed)(nil) - if err != nil { - return nil, err +type Feed struct { + core.BaseRecordProxy +} + +type Feeds []*Feed + +// Getter and Setter for the Feed struct + +func (f *Feed) GetModules() string { + return f.GetString("modules") +} + +func (f *Feed) SetModules(modules string) { + f.Set("modules", modules) +} + +func (f *Feed) GetRetrieved() types.DateTime { + return f.GetDateTime("retrieved") +} + +func (f *Feed) SetRetrieved(retrieved types.DateTime) { + f.SetRetrieved(retrieved) +} + +func (f *Feed) GetDeleted() bool { + return f.GetBool("deleted") +} + +func (f *Feed) SetDeleted(deleted bool) { + f.Set("deleted", deleted) +} + +func (f *Feed) GetId() string { + return f.GetString("id") +} + +func (f *Feed) SetId(id string) { + f.Set("id", id) +} + +func (f *Feed) GetCreated() types.DateTime { + return f.GetDateTime("created") +} + +func (f *Feed) SetCreated(created time.Time) { + f.Set("created", created) +} + +func (f *Feed) GetUpdated() types.DateTime { + return f.GetDateTime("updated") +} + +func newFeed(record *core.Record) *Feed { + return &Feed{ + BaseRecordProxy: core.BaseRecordProxy{Record: record}, } - return record, nil +} + +func NewFeed(collection *core.Collection, feed model.Feed) (*Feed, error) { + // Create a new Feed instance + if collection.Name != "feeds" { + return nil, core.ErrInvalidFieldValue + } + + record := core.NewRecord(collection) + record.Set("id:autogenerate", "") + + dbFeed := newFeed(record) + dbFeed.SetModules(feed.Modules) + dbFeed.SetRetrieved(feed.Retrieved) + dbFeed.SetDeleted(feed.Deleted) + + return dbFeed, nil +} + +func (f *Feed) ToModel() model.Feed { + return model.Feed{ + Modules: f.GetModules(), + Retrieved: f.GetRetrieved(), + Created: f.GetCreated(), + Updated: f.GetUpdated(), + Deleted: f.GetDeleted(), + } +} + +func SaveFeed(feed model.Feed, collection *core.Collection, app *pocketbase.PocketBase) (model.Feed, error) { + + collection, err := app.FindCollectionByNameOrId("feeds") + if err != nil { + return model.Feed{}, err + } + + dbFeed, recordErr := NewFeed(collection, feed) + if recordErr != nil { + return model.Feed{}, recordErr + } + + // Save the record to the database + saveErr := app.Save(dbFeed) + if saveErr != nil { + return model.Feed{}, saveErr + } + + return dbFeed.ToModel(), nil } func FindFeedByToken(app *pocketbase.PocketBase, token string) (*model.Feed, error) { diff --git a/services/data-manager/service/db/dbFunctions.go b/services/data-manager/service/db/dbFunctions.go deleted file mode 100644 index d6bbb10..0000000 --- a/services/data-manager/service/db/dbFunctions.go +++ /dev/null @@ -1,27 +0,0 @@ -//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 db - -import ( - "github.com/pocketbase/pocketbase" - "github.com/pocketbase/pocketbase/core" -) - -func FindCollection(app *pocketbase.PocketBase, collectionName string) (*core.Collection, error) { - collection, dbError := app.FindCollectionByNameOrId(collectionName) - return collection, dbError -} diff --git a/services/data-manager/service/db/dbGroups.go b/services/data-manager/service/db/dbGroups.go index f99e616..fb2e32d 100644 --- a/services/data-manager/service/db/dbGroups.go +++ b/services/data-manager/service/db/dbGroups.go @@ -5,6 +5,7 @@ import ( "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/types" "htwkalender/data-manager/model" + "log/slog" ) // Ensure SeminarGroup satisfies RecordProxy @@ -75,6 +76,38 @@ func (s *SeminarGroup) Updated() types.DateTime { return s.GetDateTime("updated") } +func (s *SeminarGroup) UniqueKey() string { + return s.Course() + s.Semester() +} + +func NewSeminarGroup(collection *core.Collection, group model.SeminarGroup) (*SeminarGroup, error) { + // Create a new SeminarGroup instance + if collection.Name != "groups" { + return nil, core.ErrInvalidFieldValue + } + + record := core.NewRecord(collection) + record.Set("id:autogenerate", "") + + sg := newSeminarGroup(record) + // Set the fields from the model + sg.SetUniversity(group.University) + sg.SetGroupShortcut(group.GroupShortcut) + sg.SetGroupId(group.GroupId) + sg.SetCourse(group.Course) + sg.SetFaculty(group.Faculty) + sg.SetFacultyId(group.FacultyId) + sg.SetSemester(group.Semester) + + return sg, nil +} + +func newSeminarGroup(record *core.Record) *SeminarGroup { + sg := &SeminarGroup{} + sg.SetProxyRecord(record) + return sg +} + // ToModel Model conversion func (s *SeminarGroup) ToModel() model.SeminarGroup { return model.SeminarGroup{ @@ -88,7 +121,34 @@ func (s *SeminarGroup) ToModel() model.SeminarGroup { } } -func FindCourseByCourseName(app core.App, courseName string) (*SeminarGroup, error) { +func (s *SeminarGroups) ToModelArray() []model.SeminarGroup { + models := make([]model.SeminarGroup, len(*s)) + for i, group := range *s { + models[i] = group.ToModel() + } + return models +} + +func (s *SeminarGroup) FromModel(m model.SeminarGroup) { + s.SetUniversity(m.University) + s.SetGroupShortcut(m.GroupShortcut) + s.SetGroupId(m.GroupId) + s.SetCourse(m.Course) + s.SetFaculty(m.Faculty) + s.SetFacultyId(m.FacultyId) + s.SetSemester(m.Semester) +} + +func (s *SeminarGroups) FromModelArray(m model.SeminarGroups) SeminarGroups { + groups := make(SeminarGroups, len(m)) + for i, group := range m { + groups[i] = &SeminarGroup{} + groups[i].FromModel(group) + } + return groups +} + +func FindCourseByCourseName(app core.App, courseName string) (model.SeminarGroup, error) { group := &SeminarGroup{} err := app.RecordQuery("groups"). AndWhere(dbx.NewExp("course = {:course}", dbx.Params{"course": courseName})). @@ -96,10 +156,10 @@ func FindCourseByCourseName(app core.App, courseName string) (*SeminarGroup, err One(group) if err != nil { - return nil, err + return model.SeminarGroup{}, err } - return group, nil + return group.ToModel(), nil } func GetAllCourses(app core.App) ([]string, error) { @@ -155,19 +215,38 @@ func GetAllCoursesForSemesterWithEvents(app core.App, semester string) ([]string return courses, nil } -func SaveGroups(app core.App, seminarGroups []*SeminarGroup) error { +func SaveGroups(app core.App, seminarGroups []model.SeminarGroup) (model.SeminarGroups, error) { // Delete all existing - if _, err := app.DB().Delete("groups", dbx.NewExp("1 = 1")).Execute(); err != nil { - return err + execute, err := app.DB().Delete("groups", dbx.NewExp("1 = 1")).Execute() + if err != nil { + return nil, err + } + // Check if to delete was successful + rowCount, _ := execute.RowsAffected() + + // Gruppen-Collection abrufen + collection, err := app.FindCollectionByNameOrId("groups") + if err != nil { + return nil, err } - // Save new ones + // Seminargruppen in die Datenbank speichern + savedGroups := SeminarGroups{} for _, group := range seminarGroups { - err := app.Save(group) - if err != nil { - return err - } - } - return nil + dbSeminarGroup, recordErr := NewSeminarGroup(collection, group) + if recordErr != nil { + return nil, err + } + + saveErr := app.Save(dbSeminarGroup) + if saveErr != nil { + return nil, saveErr + } + savedGroups = append(savedGroups, dbSeminarGroup) + } + slog.Info("Saved all groups to the database", "insert", len(savedGroups), "deleted", rowCount) + + return savedGroups.ToModelArray(), nil + } diff --git a/services/data-manager/service/events/courseService.go b/services/data-manager/service/events/courseService.go index 3bf460e..2b81a0c 100644 --- a/services/data-manager/service/events/courseService.go +++ b/services/data-manager/service/events/courseService.go @@ -21,6 +21,7 @@ import ( "htwkalender/data-manager/model" "htwkalender/data-manager/service/db" "htwkalender/data-manager/service/functions" + "log/slog" ) // CourseService defines the methods to be implemented @@ -43,12 +44,23 @@ func NewPocketBaseCourseService(app *pocketbase.PocketBase) *PocketBaseCourseSer // GetAllCourses returns all courses func (s *PocketBaseCourseService) GetAllCourses() []string { - return db.GetAllCourses(s.app) + courseList, err := db.GetAllCourses(s.app) + if err != nil { + slog.Error("Could not get all courses", "error", err) + return nil + } + + return courseList } // GetAllCoursesForSemester returns all courses for a specific semester func (s *PocketBaseCourseService) GetAllCoursesForSemester(semester string) []model.SeminarGroup { - return db.GetAllCoursesForSemester(s.app, semester) + seminarGroups, err := db.GetAllCoursesForSemester(s.app, semester) + if err != nil { + slog.Error("Could not get all courses for semester", "error", err) + return nil + } + return seminarGroups } // GetAllCoursesForSemesterWithEvents returns all courses for a specific semester with events diff --git a/services/data-manager/service/fetch/v1/fetchSeminarGroupService.go b/services/data-manager/service/fetch/v1/fetchSeminarGroupService.go index 899d09b..8672a61 100644 --- a/services/data-manager/service/fetch/v1/fetchSeminarGroupService.go +++ b/services/data-manager/service/fetch/v1/fetchSeminarGroupService.go @@ -57,8 +57,8 @@ func getSeminarHTML(semester string) (string, error) { } -func FetchSeminarGroups(base *pocketbase.PocketBase) (db.SeminarGroups, error) { - var groups db.SeminarGroups +func FetchSeminarGroups(base *pocketbase.PocketBase) (model.SeminarGroups, error) { + var groups model.SeminarGroups semesterString := functions.CalculateSemesterList(time.RealClock{}) var results [2]string @@ -85,8 +85,8 @@ func FetchSeminarGroups(base *pocketbase.PocketBase) (db.SeminarGroups, error) { return insertedGroups, nil } -func removeDuplicates(groups db.SeminarGroups) db.SeminarGroups { - uniqueGroups := make(db.SeminarGroups, 0, len(groups)) +func removeDuplicates(groups model.SeminarGroups) model.SeminarGroups { + uniqueGroups := make(model.SeminarGroups, 0, len(groups)) seen := make(map[string]struct{}) // Use an empty struct to minimize memory usage // unique Identifier is the course and semester @@ -109,7 +109,7 @@ func contains(groups []model.SeminarGroup, group model.SeminarGroup) bool { return false } -func parseSeminarGroups(result string, semester string) db.SeminarGroups { +func parseSeminarGroups(result string, semester string) model.SeminarGroups { var studium model.Studium err := xml.Unmarshal([]byte(result), &studium) @@ -117,11 +117,11 @@ func parseSeminarGroups(result string, semester string) db.SeminarGroups { return nil } - var seminarGroups db.SeminarGroups + var seminarGroups model.SeminarGroups for _, faculty := range studium.Faculty { for _, Studiengang := range faculty.Studiengang { for _, Studienrichtung := range Studiengang.Semgrp { - seminarGroup := db.SeminarGroup{ + seminarGroup := model.SeminarGroup{ University: "HTWK-Leipzig", GroupShortcut: Studiengang.Name, GroupId: Studiengang.ID, @@ -130,7 +130,7 @@ func parseSeminarGroups(result string, semester string) db.SeminarGroups { FacultyId: faculty.ID, Semester: semester, } - seminarGroups = append(seminarGroups, &seminarGroup) + seminarGroups = append(seminarGroups, seminarGroup) } } } diff --git a/services/data-manager/service/fetch/v2/fetcher.go b/services/data-manager/service/fetch/v2/fetcher.go index b0e19fb..877d963 100644 --- a/services/data-manager/service/fetch/v2/fetcher.go +++ b/services/data-manager/service/fetch/v2/fetcher.go @@ -112,7 +112,7 @@ func updateDatabase(base *pocketbase.PocketBase, eventsToBeAdded []model.Event, return err } - addedEvents, err = db.SaveEvents(eventsToBeAdded, base) + addedEvents, err = db.SaveEvents(eventsToBeAdded, app) if err != nil { return err } diff --git a/services/data-manager/service/ical/ical.go b/services/data-manager/service/ical/ical.go index 9711f11..c427445 100644 --- a/services/data-manager/service/ical/ical.go +++ b/services/data-manager/service/ical/ical.go @@ -36,7 +36,7 @@ func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (strin jsonModules, _ := json.Marshal(modules) icalFeed.Modules = string(jsonModules) - collection, dbError := db.FindCollection(app, "feeds") + collection, dbError := app.FindCollectionByNameOrId("feeds") if dbError != nil { return "", apis.NewNotFoundError("Collection could not be found", dbError) }