Files
htwkalender/services/data-manager/service/events/eventService.go
2025-03-08 23:25:53 +01:00

233 lines
7.5 KiB
Go

//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 <https://www.gnu.org/licenses/>.
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.GetAllEventsForCourseAndSemester(s.app, seminarGroup.Course, seminarGroup.Semester)
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 + " [" + seminarGroup.Semester + "] - Events: " + strconv.FormatInt(int64(len(savedEvents)), 10) + " new, " + strconv.FormatInt(int64(len(deleteList)), 10) + " deleted" + ", " + strconv.FormatInt(int64(len(dbEvents)-len(deleteList)+len(savedEvents)), 10) + " total")
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
}