//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 events import ( "github.com/pocketbase/pocketbase" "htwkalender/data-manager/model" "htwkalender/data-manager/service/db" "htwkalender/data-manager/service/fetch/v1" "htwkalender/data-manager/service/functions" "log/slog" "strconv" ) type EventService interface { GetModulesForCourseDistinct(course string, semester string) (model.Events, error) GetAllModulesDistinct() ([]model.ModuleDTO, error) GetModuleByUUID(uuid string) (model.Module, error) DeleteAllEventsByCourseAndSemester(course string, semester string) error DeleteAllEvents() error UpdateModulesForCourse(seminarGroup model.SeminarGroup) (model.Events, error) GetEventTypes() ([]string, error) } type Named interface { GetName() string SetName(name string) } type PocketBaseEventService struct { app *pocketbase.PocketBase } func NewPocketBaseEventService(app *pocketbase.PocketBase) *PocketBaseEventService { return &PocketBaseEventService{app: app} } func (s *PocketBaseEventService) GetModulesForCourseDistinct(course string, semester string) (model.Events, error) { modules, err := db.GetAllModulesForCourse(s.app, course, semester) // Convert the []model.Module to []Named var namedEvents []Named for _, module := range modules { namedEvents = append(namedEvents, &module) } replaceEmptyEntry(namedEvents, "Sonderveranstaltungen") return modules, err } // replaceEmptyEntry replaces an empty entry in a module with a replacement string // If the module is not empty, nothing happens func replaceEmptyEntry(namedList []Named, replacement string) { for i, namedItem := range namedList { if functions.OnlyWhitespace(namedItem.GetName()) { namedList[i].SetName(replacement) } } } // 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 func (s *PocketBaseEventService) GetAllModulesDistinct() ([]model.ModuleDTO, error) { modules, err := db.GetAllModulesDistinctByNameAndCourse(s.app) if err != nil { return nil, err } var namedModules []Named for _, module := range modules { namedModules = append(namedModules, &module) } replaceEmptyEntry(namedModules, "Sonderveranstaltungen") return modules, nil } func (s *PocketBaseEventService) GetModuleByUUID(uuid string) (model.Module, error) { module, findModuleErr := db.FindModuleByUUID(s.app, uuid) if findModuleErr != nil { return model.Module{}, findModuleErr } events, findEventsError := db.FindAllEventsByModule(s.app, module) if findEventsError != nil || len(events) == 0 { return model.Module{}, findEventsError } else { return model.Module{ UUID: events[0].UUID, Name: events[0].Name, Events: events, Prof: events[0].Prof, Course: events[0].Course, Semester: events[0].Semester, }, nil } } // DeleteAllEventsByCourseAndSemester deletes all events for a course and a semester // If the deletion was successful, nil is returned // If the deletion was not successful, an error is returned func (s *PocketBaseEventService) DeleteAllEventsByCourseAndSemester(course string, semester string) error { err := db.DeleteAllEventsByCourse(s.app.Dao(), course, semester) if err != nil { return err } else { return nil } } func (s *PocketBaseEventService) DeleteAllEvents() error { err := db.DeleteAllEvents(s.app) if err != nil { return err } else { return nil } } // UpdateModulesForCourse updates all modules for a course // Does Updates for ws and ss semester sequentially // Update runs through the following steps: // 1. Delete all events for the course and the semester // 2. Fetch 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 not successful, an error is returned func (s *PocketBaseEventService) UpdateModulesForCourse(seminarGroup model.SeminarGroup) (model.Events, error) { seminarGroup, err := v1.FetchAndParse(seminarGroup.Semester, seminarGroup.Course) if err != nil { return nil, err } seminarGroup = v1.ReplaceEmptyEventNames(seminarGroup) //check if events in the seminarGroups Events are already in the database //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 there are no events in the database, save the new events //get all events for the course and the semester dbEvents, err := db.GetAllEventsForCourse(s.app, seminarGroup.Course) if err != nil { return nil, err } //if there are no events in the database, save the new events if len(dbEvents) == 0 { events, dbError := db.SaveSeminarGroupEvents(seminarGroup, s.app) if dbError != nil { return nil, dbError } return events, nil } // Create partial update list and delete list for the events var insertList model.Events var deleteList model.Events // check which events are not already in the database and need to be inserted/saved for _, event := range seminarGroup.Events { if !containsEvent(dbEvents, event) { insertList = append(insertList, event) } } // check which events are in the database but not in the seminarGroup and need to be deleted for _, dbEvent := range dbEvents { if !containsEvent(seminarGroup.Events, dbEvent) { deleteList = append(deleteList, dbEvent) } } // delete all events that are in the deleteList err = db.DeleteEvents(deleteList, s.app) if err != nil { slog.Error("Failed to delete events:", "error", err) return nil, err } // save all events that are in the insertList savedEvents, err := db.SaveEvents(insertList, s.app.Dao()) if err != nil { slog.Error("Failed to save events: ", "error", err) return nil, err } slog.Info("Course: " + seminarGroup.Course + " - Event changes: " + strconv.FormatInt(int64(len(insertList)), 10) + " new events, " + strconv.FormatInt(int64(len(deleteList)), 10) + " deleted events") return savedEvents, nil } func containsEvent(events model.Events, event model.Event) bool { for _, e := range events { if e.Name == event.Name && e.Prof == event.Prof && e.Rooms == event.Rooms && e.Semester == event.Semester && e.Start == event.Start && e.End == event.End && e.Course == event.Course { return true } } return false } func (s *PocketBaseEventService) GetEventTypes() ([]string, error) { dbEventTypes, err := db.GetAllEventTypes(s.app) if err != nil { return nil, err } // Convert the []model.EventType to []string var eventTypes []string for _, eventType := range dbEventTypes { eventTypes = append(eventTypes, eventType.EventType) } return eventTypes, nil }