diff --git a/backend/service/feed/feedFunctions.go b/backend/service/feed/feedFunctions.go index d753854..5beaab3 100644 --- a/backend/service/feed/feedFunctions.go +++ b/backend/service/feed/feedFunctions.go @@ -20,9 +20,12 @@ import ( "database/sql" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase/daos" + "htwkalender/model" database "htwkalender/service/db" + "htwkalender/service/functions" localTime "htwkalender/service/functions/time" "log/slog" + "strings" ) func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) { @@ -50,3 +53,89 @@ func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) { } } } + +func CombineEventsInFeed(events model.Events) model.Events { + // Combine events with the same Prof, Name, Start and End time into one event + // if the note is empty then there is no room displayed in the new note + // room listing isn't displayed if there is only one event. + + combinedEvents := make(model.Events, 0) + combinedEvents = append(combinedEvents, events[0]) + + for index1 := 1; index1 < len(events); index1++ { + for index2 := 0; index2 < len(combinedEvents); index2++ { + + if events[index1].Name == combinedEvents[index2].Name && + events[index1].Start == combinedEvents[index2].Start && + events[index1].End == combinedEvents[index2].End && + events[index1].Course == combinedEvents[index2].Course { + + // if no notes are present skip + if !functions.OnlyWhitespace(events[index1].Notes) { + // if combinedEvents notes are empty, add the new notes + if functions.OnlyWhitespace(combinedEvents[index2].Notes) { + combinedEvents[index2].Notes = descriptionString(events[index1]) + } else { + addNotesIfAlreadyRoomsAdded(events, combinedEvents, index2, index1) + } + } + + combineRooms(events, index1, combinedEvents, index2) + combineProfs(events, index1, combinedEvents, index2) + + // remove the event from the events list + events = append(events[:index1], events[index1+1:]...) + + break + + } else { + // if the event is not in the combinedEvents list, add it + if index2 == len(combinedEvents)-1 { + combinedEvents = append(combinedEvents, events[index1]) + events = append(events[:index1], events[index1+1:]...) + break + } + } + } + } + return combinedEvents +} + +func addNotesIfAlreadyRoomsAdded(events model.Events, combinedEvents model.Events, index2 int, index1 int) { + // check if combinedEvents[index2].Rooms string contains comma "," , + if !strings.Contains(combinedEvents[index2].Rooms, ",") { + combinedEvents[index2].Notes = descriptionString(combinedEvents[index2]) + "\n" + descriptionString(events[index1]) + } else { + combinedEvents[index2].Notes += "\n" + descriptionString(events[index1]) + } +} + +func combineProfs(events model.Events, index1 int, combinedEvents model.Events, index2 int) { + // combine the profs + if events[index1].Prof != "" { + if combinedEvents[index2].Prof == "" { + combinedEvents[index2].Prof = events[index1].Prof + } else { + if !strings.Contains(combinedEvents[index2].Prof, events[index1].Prof) { + combinedEvents[index2].Prof += ", " + events[index1].Prof + } + } + } +} + +func descriptionString(event model.Event) string { + return event.Rooms + " - " + event.Notes + " (" + event.Prof + ")" +} + +func combineRooms(events model.Events, index1 int, combinedEvents model.Events, index2 int) { + // combine the rooms + if events[index1].Rooms != "" { + if combinedEvents[index2].Rooms == "" { + combinedEvents[index2].Rooms = events[index1].Rooms + } else { + if !strings.Contains(combinedEvents[index2].Rooms, events[index1].Rooms) { + combinedEvents[index2].Rooms += ", " + events[index1].Rooms + } + } + } +} diff --git a/backend/service/feed/feedFunctions_test.go b/backend/service/feed/feedFunctions_test.go index 0c93e9d..cc068bb 100644 --- a/backend/service/feed/feedFunctions_test.go +++ b/backend/service/feed/feedFunctions_test.go @@ -21,6 +21,7 @@ import ( "github.com/pocketbase/pocketbase/tests" "htwkalender/model" mockTime "htwkalender/service/functions/time" + "reflect" "testing" "time" ) @@ -97,3 +98,55 @@ func TestClearFeeds(t *testing.T) { }) } } + +func TestCombineEventsInFeed(t *testing.T) { + type args struct { + events model.Events + } + testCases := []struct { + name string + args args + want model.Events + }{ + { + name: "TestCombineEventsInFeed", + args: args{ + events: model.Events{ + { + Name: "Modellierung", + Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), + End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 4, 0, 0, 0, time.UTC)), + Prof: "Prof. Bunt", + Rooms: "LI001", + Notes: "Gruppe 2", + }, + { + Name: "Modellierung", + Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), + End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 4, 0, 0, 0, time.UTC)), + Prof: "Prof. Bunt", + Rooms: "LI002", + Notes: "Gruppe 1", + }, + }, + }, + want: model.Events{ + { + Name: "Modellierung", + Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), + End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 4, 0, 0, 0, time.UTC)), + Prof: "Prof. Bunt", + Rooms: "LI001, LI002", + Notes: "LI001 - Gruppe 2 (Prof. Bunt)\nLI002 - Gruppe 1 (Prof. Bunt)", + }, + }, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + if got := CombineEventsInFeed(tt.args.events); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CombineEventsInFeed() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/backend/service/ical/ical.go b/backend/service/ical/ical.go index 2abac81..e94a2af 100644 --- a/backend/service/ical/ical.go +++ b/backend/service/ical/ical.go @@ -33,22 +33,22 @@ const expirationTime = 5 * time.Minute func Feed(app *pocketbase.PocketBase, token string) (string, error) { var result string - feed, err := db.FindFeedByToken(token, app) - if feed == nil && err != nil { + icalFeed, err := db.FindFeedByToken(token, app) + if icalFeed == nil && err != nil { return "", err } modules := make(map[string]model.FeedCollection) var modulesArray []model.FeedCollection - _ = json.Unmarshal([]byte(feed.Modules), &modulesArray) + _ = json.Unmarshal([]byte(icalFeed.Modules), &modulesArray) for _, module := range modulesArray { modules[module.UUID] = module } - newFeed, err := createFeedForToken(app, modules) - if err != nil { - return "", err + newFeed, createFeedErr := createFeedForToken(app, modules) + if createFeedErr != nil { + return "", createFeedErr } result = newFeed.Content @@ -56,16 +56,18 @@ func Feed(app *pocketbase.PocketBase, token string) (string, error) { } func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) { - res, err := db.GetPlanForModules(app, modules) - + events, err := db.GetPlanForModules(app, modules) if err != nil { return nil, apis.NewNotFoundError("Could not fetch events", err) } + // Combine Events + //events = feed.CombineEventsInFeed(events) + b := bytes.Buffer{} - goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules}) - feed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)} - return feed, nil + goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules}) + icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)} + return icalFeed, nil } func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) { @@ -76,16 +78,16 @@ func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (strin return "", apis.NewNotFoundError("Could not parse request body", err) } - var feed model.Feed + var icalFeed model.Feed jsonModules, _ := json.Marshal(modules) - feed.Modules = string(jsonModules) + icalFeed.Modules = string(jsonModules) collection, dbError := db.FindCollection(app, "feeds") if dbError != nil { return "", apis.NewNotFoundError("Collection could not be found", dbError) } - record, err := db.SaveFeed(feed, collection, app) + record, err := db.SaveFeed(icalFeed, collection, app) if err != nil { return "", apis.NewNotFoundError("Could not save feed", err) } diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go index 147bb6f..31ba7d7 100644 --- a/backend/service/ical/icalFileGeneration.go +++ b/backend/service/ical/icalFileGeneration.go @@ -156,18 +156,18 @@ func addPropertyIfNotEmpty(component *goics.Component, key string, value string) func generateDescription(event model.Event) string { var description string - if !functions.OnlyWhitespace(event.Notes) { - description += "Notizen: " + event.Notes + "\n" - } if !functions.OnlyWhitespace(event.Prof) { - description += "Prof: " + event.Prof + "\n" + description += "Profs: " + event.Prof + "\n" } if !functions.OnlyWhitespace(event.Course) { - description += "Gruppe: " + event.Course + "\n" + description += "Gruppen: " + event.Course + "\n" } if !functions.OnlyWhitespace(event.EventType) { description += "Typ: " + event.EventType + event.Compulsory + "\n" } + if !functions.OnlyWhitespace(event.Notes) { + description += "Notizen: " + event.Notes + "\n" + } return description } diff --git a/backend/service/ical/icalFileGeneration_test.go b/backend/service/ical/icalFileGeneration_test.go index 7b32999..f48f413 100644 --- a/backend/service/ical/icalFileGeneration_test.go +++ b/backend/service/ical/icalFileGeneration_test.go @@ -104,7 +104,7 @@ func TestIcalModel_EmitICal(t *testing.T) { "DTEND": {"00010101T000000Z"}, "DTSTART": {"00010101T000000Z"}, "SUMMARY": {"Test"}, - "DESCRIPTION": {"Notizen: Test\nProf: Test\nTyp: Test\n"}, + "DESCRIPTION": {"Profs: Test\nTyp: Test\nNotizen: Test\n"}, "LOCATION": {"Test"}, }, }, @@ -203,7 +203,7 @@ func TestIcalModel_EmitICal(t *testing.T) { "DTEND": {"20231201T010000Z"}, "DTSTART": {"20231201T000000Z"}, "SUMMARY": {"UserDefinedName"}, - "DESCRIPTION": {"Notizen: Test\nProf: Test\nGruppe: Test\nTyp: Test\n"}, + "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, "LOCATION": {"ZU430"}, }, }, @@ -216,7 +216,7 @@ func TestIcalModel_EmitICal(t *testing.T) { "DTEND": {"20231201T010000Z"}, "DTSTART": {"20231201T000000Z"}, "SUMMARY": {"UserDefinedName"}, - "DESCRIPTION": {"Notizen: Test\nProf: Test\nGruppe: Test\nTyp: Test\n"}, + "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, "LOCATION": {"ZU221"}, }, }, @@ -244,7 +244,14 @@ func TestIcalModel_EmitICal(t *testing.T) { } if got := generateIcalEmit(icalModel, mockClock); !reflect.DeepEqual(got, tt.want) { - t.Errorf("EmitICal() = %v, want %v", got, tt.want) + t.Errorf("EmitICal() = \n%v, want \n%v", got, tt.want) + + // Print the differences + for i, element := range got.Elements { + if !reflect.DeepEqual(element, tt.want.Elements[i]) { + t.Errorf("Element %d: got \n%v, want \n%v", i, element, tt.want.Elements[i]) + } + } } }) }