From 4792b07e8c87863679c14a8d77e0d42f59fbc7e3 Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Tue, 18 Jun 2024 18:53:29 +0200 Subject: [PATCH] fix:#36 fixed tests, naming, and removed old duplicated code --- services/data-manager/service/db/dbEvents.go | 5 +- .../data-manager/service/db/dbEvents_test.go | 8 +- .../data-manager/service/db/dbFunctions.go | 17 -- .../service/db/dbFunctions_test.go | 50 ---- .../data-manager/service/fetch/v2/fetcher.go | 2 + .../service/ical/icalFileGeneration.go | 173 ------------ .../service/ical/icalFileGeneration_test.go | 258 ------------------ .../service/ical/icsComponenter.go | 62 ----- 8 files changed, 9 insertions(+), 566 deletions(-) delete mode 100644 services/data-manager/service/db/dbFunctions_test.go delete mode 100644 services/data-manager/service/ical/icalFileGeneration.go delete mode 100644 services/data-manager/service/ical/icalFileGeneration_test.go delete mode 100644 services/data-manager/service/ical/icsComponenter.go diff --git a/services/data-manager/service/db/dbEvents.go b/services/data-manager/service/db/dbEvents.go index f6cf5a2..7c7c174 100644 --- a/services/data-manager/service/db/dbEvents.go +++ b/services/data-manager/service/db/dbEvents.go @@ -18,6 +18,7 @@ package db import ( "fmt" + "github.com/google/uuid" "github.com/pocketbase/pocketbase/daos" "github.com/pocketbase/pocketbase/tools/types" "htwkalender/data-manager/model" @@ -138,11 +139,11 @@ func buildIcalQueryForModules(modulesUuid []string) dbx.Expression { // following the pattern of only containing alphanumeric characters and dashes for _, moduleUuid := range modulesUuid { - if !IsSafeIdentifier(moduleUuid) { + err := uuid.Validate(moduleUuid) + if err != nil { slog.Warn("Module UUID is not safe: ", "moduleUuid", moduleUuid) return dbx.HashExp{} } - } // build where conditions for each module diff --git a/services/data-manager/service/db/dbEvents_test.go b/services/data-manager/service/db/dbEvents_test.go index 5941c58..8708394 100644 --- a/services/data-manager/service/db/dbEvents_test.go +++ b/services/data-manager/service/db/dbEvents_test.go @@ -38,13 +38,13 @@ func Test_buildIcalQueryForModules(t *testing.T) { }, { name: "one module", - args: args{modules: []string{"test"}}, - want: dbx.HashExp{"uuid": "test"}, + args: args{modules: []string{"77eddc32-c49d-5d0a-8c36-17b266396641"}}, + want: dbx.HashExp{"uuid": "77eddc32-c49d-5d0a-8c36-17b266396641"}, }, { name: "two modules", - args: args{modules: []string{"test", "test2"}}, - want: dbx.Or(dbx.HashExp{"uuid": "test"}, dbx.HashExp{"uuid": "test2"}), + args: args{modules: []string{"9e5081e6-4c56-57b9-9965-f6dc74559755", "48cd8c4e-fb70-595c-9dfb-7035f56326d9"}}, + want: dbx.Or(dbx.HashExp{"uuid": "9e5081e6-4c56-57b9-9965-f6dc74559755"}, dbx.HashExp{"uuid": "48cd8c4e-fb70-595c-9dfb-7035f56326d9"}), }, } for _, tt := range tests { diff --git a/services/data-manager/service/db/dbFunctions.go b/services/data-manager/service/db/dbFunctions.go index 3f6f2b6..05cd48b 100644 --- a/services/data-manager/service/db/dbFunctions.go +++ b/services/data-manager/service/db/dbFunctions.go @@ -19,26 +19,9 @@ package db import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/models" - "log/slog" - "regexp" ) func FindCollection(app *pocketbase.PocketBase, collectionName string) (*models.Collection, error) { collection, dbError := app.Dao().FindCollectionByNameOrId(collectionName) return collection, dbError } - -// IsSafeIdentifier check uuids against sql injection -// uuids are generated by the system and are not user input -// following the pattern of only containing alphanumeric characters and dashes -func IsSafeIdentifier(uuid string) bool { - // Define a regular expression that matches only valid UUID characters (alphanumeric and dashes) - validUUIDPattern := `^[a-zA-Z0-9-]+$` - match, err := regexp.MatchString(validUUIDPattern, uuid) - if err != nil { - // Handle the error according to your application's needs - slog.Warn("Invalid UUID pattern", "uuid", uuid) - return false - } - return match -} diff --git a/services/data-manager/service/db/dbFunctions_test.go b/services/data-manager/service/db/dbFunctions_test.go deleted file mode 100644 index 2f1612f..0000000 --- a/services/data-manager/service/db/dbFunctions_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package db - -import "testing" - -func TestIsSafeIdentifier(t *testing.T) { - type args struct { - uuid string - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "Test safe identifier", - args: args{ - uuid: "1234567890-1234567890-1234567890-1234567890", - }, - want: true, - }, - { - name: "Test safe identifier", - args: args{ - uuid: "1234567890-1234567890-1234567890-1234567890", - }, - want: true, - }, - { - name: "Test safe identifier", - args: args{ - uuid: "77eddc32-c49d-5d0a-8c36-17b266396641", - }, - want: true, - }, - { - name: "Test unsafe identifier", - args: args{ - uuid: "77eddc32-c49d-5d0a-8c36-17/1!!b266396641-", - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := IsSafeIdentifier(tt.args.uuid); got != tt.want { - t.Errorf("IsSafeIdentifier() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/services/data-manager/service/fetch/v2/fetcher.go b/services/data-manager/service/fetch/v2/fetcher.go index f8af754..ed1e77a 100644 --- a/services/data-manager/service/fetch/v2/fetcher.go +++ b/services/data-manager/service/fetch/v2/fetcher.go @@ -195,6 +195,8 @@ func parseHTML(webpage string) (*html.Node, error) { return doc, nil } +// generateUUIDs generates a UUID for each event based on the event name, course and semester +// the UUID is used to identify the event in the database func generateUUIDs(events []model.Event) []model.Event { for i, event := range events { // generate a hash value from the event name, course and semester diff --git a/services/data-manager/service/ical/icalFileGeneration.go b/services/data-manager/service/ical/icalFileGeneration.go deleted file mode 100644 index b2ee39b..0000000 --- a/services/data-manager/service/ical/icalFileGeneration.go +++ /dev/null @@ -1,173 +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 ical - -import ( - "htwkalender/data-manager/model" - "htwkalender/data-manager/service/functions" - clock "htwkalender/data-manager/service/functions/time" - "htwkalender/data-manager/service/names" - "time" - - "github.com/jordic/goics" - _ "time/tzdata" -) - -// IcalModel local type for EmitICal function -type IcalModel struct { - Events model.Events - Mapping map[string]model.FeedCollection -} - -// EmitICal implements the interface for goics -func (icalModel IcalModel) EmitICal() goics.Componenter { - internalClock := clock.RealClock{} - c := generateIcalEmit(icalModel, internalClock) - return c -} - -func generateIcalEmit(icalModel IcalModel, internalClock clock.Clock) *goics.Component { - europeTime, _ := time.LoadLocation("Europe/Berlin") - c := goics.NewComponent() - c.SetType("VCALENDAR") - // PRODID is required by the standard - c.AddProperty("PRODID", "-//HTWK Kalender//htwkalender.de//DE") - - c.AddProperty("VERSION", "2.0") - c.AddProperty("CALSCALE", "GREGORIAN") - c.AddProperty("TZID", "EUROPE/BERLIN") - c.AddProperty("X-WR-CALNAME", "HTWK Kalender") - c.AddProperty("X-WR-TIMEZONE", "EUROPE/BERLIN") - //add v time zone - icalModel.vtimezone(c) - - for _, event := range icalModel.Events { - mapEntry, mappingFound := icalModel.Mapping[event.UUID] - - s := goics.NewComponent() - s.SetType("VEVENT") - - s.AddProperty(goics.FormatDateTime("DTSTAMP", internalClock.Now().Local().In(europeTime))) - - // create a unique id for the event by hashing the event start, end, course and name - var eventHash = functions.HashString(event.Start.String() + event.End.String() + event.Course + event.Name + event.Rooms) - - s.AddProperty("UID", eventHash+"@htwkalender.de") - s.AddProperty(goics.FormatDateTime("DTEND", event.End.Time().Local().In(europeTime))) - s.AddProperty(goics.FormatDateTime("DTSTART", event.Start.Time().Local().In(europeTime))) - - if mappingFound { - addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry)) - addAlarmIfSpecified(s, event, mapEntry, internalClock) - } else { - addPropertyIfNotEmpty(s, "SUMMARY", event.Name) - } - - addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event)) - addPropertyIfNotEmpty(s, "LOCATION", event.Rooms) - c.AddComponent(s) - } - return c -} - -func (icalModel IcalModel) vtimezone(c *goics.Component) { - tz := goics.NewComponent() - tz.SetType("VTIMEZONE") - tz.AddProperty("TZID", "EUROPE/BERLIN") - //add standard time - icalModel.standard(tz) - //add daylight time - icalModel.daylight(tz) - - c.AddComponent(tz) -} - -func (icalModel IcalModel) standard(tz *goics.Component) { - st := NewHtwkalenderComponent() - st.SetType("STANDARD") - st.AddProperty("DTSTART", "19701025T030000") - st.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU") - st.AddProperty("TZOFFSETFROM", "+0200") - st.AddProperty("TZOFFSETTO", "+0100") - st.AddProperty("TZNAME", "CET") - tz.AddComponent(st) -} - -// create an override for goics component function Write -// to add the RRULE property - -func (icalModel IcalModel) daylight(tz *goics.Component) { - dt := NewHtwkalenderComponent() - dt.SetType("DAYLIGHT") - dt.AddProperty("DTSTART", "19700329T020000") - dt.AddProperty("TZOFFSETFROM", "+0100") - dt.AddProperty("TZOFFSETTO", "+0200") - dt.AddProperty("TZNAME", "CEST") - dt.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU") - tz.AddComponent(dt) -} - -// if reminder is specified in the configuration for this event, an alarm will be added to the event -func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection, clock clock.Clock) { - // if event.Start > now - // then add alarm - if event.Start.Time().Local().After(clock.Now().Local()) && mapping.Reminder { - a := goics.NewComponent() - a.SetType("VALARM") - a.AddProperty("TRIGGER", "-P0DT0H15M0S") - a.AddProperty("ACTION", "DISPLAY") - a.AddProperty("DESCRIPTION", "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms) - s.AddComponent(a) - } -} - -// replaceNameIfUserDefined replaces the name of the event with the user defined name if it is not empty -// all contained template strings will be replaced with the corresponding values from the event -func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string { - if !functions.OnlyWhitespace(mapping.UserDefinedName) { - return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event) - } - - return event.Name -} - -// AddPropertyIfNotEmpty adds a property to the component if the value is not empty -// or contains only whitespaces -func addPropertyIfNotEmpty(component *goics.Component, key string, value string) { - if !functions.OnlyWhitespace(value) { - component.AddProperty(key, value) - } -} - -func generateDescription(event model.Event) string { - var description string - - if !functions.OnlyWhitespace(event.Prof) { - description += "Profs: " + event.Prof + "\n" - } - if !functions.OnlyWhitespace(event.Course) { - 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/services/data-manager/service/ical/icalFileGeneration_test.go b/services/data-manager/service/ical/icalFileGeneration_test.go deleted file mode 100644 index 383a387..0000000 --- a/services/data-manager/service/ical/icalFileGeneration_test.go +++ /dev/null @@ -1,258 +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 ical - -import ( - "github.com/jordic/goics" - "htwkalender/data-manager/model" - mockTime "htwkalender/data-manager/service/functions/time" - "reflect" - "testing" - "time" -) - -func TestIcalModel_EmitICal(t *testing.T) { - type fields struct { - Events model.Events - Mapping map[string]model.FeedCollection - } - tests := []struct { - name string - fields fields - want *goics.Component - }{ - { - name: "Test EmitICal", - fields: fields{ - Events: model.Events{ - { - UUID: "123", - Name: "Test", - EventType: "Test", - Notes: "Test", - Prof: "Test", - Rooms: "Test", - BookedAt: "Test", - }, - }, - Mapping: map[string]model.FeedCollection{ - "123": { - UUID: "123", - Name: "Test", - Course: "Test", - UserDefinedName: "Test", - }, - }, - }, - want: &goics.Component{ - Tipo: "VCALENDAR", - Elements: []goics.Componenter{ - &goics.Component{ - Tipo: "VTIMEZONE", - Elements: []goics.Componenter{ - &HtwkalenderComponent{ - Component: &goics.Component{ - Tipo: "STANDARD", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTART": {"19701025T030000"}, - "RRULE": {"FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU"}, - "TZOFFSETFROM": {"+0200"}, - "TZOFFSETTO": {"+0100"}, - "TZNAME": {"CET"}, - }, - }, - }, - &HtwkalenderComponent{ - Component: &goics.Component{ - Tipo: "DAYLIGHT", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTART": {"19700329T020000"}, - "TZOFFSETFROM": {"+0100"}, - "TZOFFSETTO": {"+0200"}, - "TZNAME": {"CEST"}, - "RRULE": {"FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU"}, - }, - }, - }, - }, - Properties: map[string][]string{ - "TZID": {"EUROPE/BERLIN"}, - }, - }, - &goics.Component{ - Tipo: "VEVENT", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTAMP": {"20231201T000000Z"}, - "UID": {"a8d627d93f518e9096b6f40e36d27b7660fa26d318ef1adc43da750e49ebe4be@htwkalender.de"}, - "DTEND": {"00010101T000000Z"}, - "DTSTART": {"00010101T000000Z"}, - "SUMMARY": {"Test"}, - "DESCRIPTION": {"Profs: Test\nTyp: Test\nNotizen: Test\n"}, - "LOCATION": {"Test"}, - }, - }, - }, - Properties: map[string][]string{ - "PRODID": {"-//HTWK Kalender//htwkalender.de//DE"}, - "VERSION": {"2.0"}, - "CALSCALE": {"GREGORIAN"}, - "TZID": {"EUROPE/BERLIN"}, - "X-WR-CALNAME": {"HTWK Kalender"}, - "X-WR-TIMEZONE": {"EUROPE/BERLIN"}, - }, - }, - }, - { - name: "Test Similar Events like Sport Courses", - fields: fields{ - Events: model.Events{ - { - UUID: "123", - Name: "Test", - Course: "Test", - EventType: "Test", - Notes: "Test", - Prof: "Test", - Rooms: "ZU430", - BookedAt: "Test", - Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), - End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 1, 0, 0, 0, time.UTC)), - }, - { - UUID: "123", - Name: "Test", - Course: "Test", - EventType: "Test", - Notes: "Test", - Prof: "Test", - Rooms: "ZU221", - BookedAt: "Test", - Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), - End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 1, 0, 0, 0, time.UTC)), - }, - }, - Mapping: map[string]model.FeedCollection{ - "123": { - UUID: "123", - Name: "Test", - Course: "Test", - UserDefinedName: "UserDefinedName", - }, - }, - }, - want: &goics.Component{ - Tipo: "VCALENDAR", - Elements: []goics.Componenter{ - &goics.Component{ - Tipo: "VTIMEZONE", - Elements: []goics.Componenter{ - &HtwkalenderComponent{ - Component: &goics.Component{ - Tipo: "STANDARD", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTART": {"19701025T030000"}, - "RRULE": {"FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU"}, - "TZOFFSETFROM": {"+0200"}, - "TZOFFSETTO": {"+0100"}, - "TZNAME": {"CET"}, - }, - }, - }, - &HtwkalenderComponent{ - Component: &goics.Component{ - Tipo: "DAYLIGHT", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTART": {"19700329T020000"}, - "TZOFFSETFROM": {"+0100"}, - "TZOFFSETTO": {"+0200"}, - "TZNAME": {"CEST"}, - "RRULE": {"FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU"}, - }, - }, - }, - }, - Properties: map[string][]string{ - "TZID": {"EUROPE/BERLIN"}, - }, - }, - &goics.Component{ - Tipo: "VEVENT", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTAMP": {"20231201T000000Z"}, - "UID": {"b52a7a081f46eeba9b402114493278a34a48b572c84e53d7ac4da9dea15cdff2@htwkalender.de"}, - "DTEND": {"20231201T010000Z"}, - "DTSTART": {"20231201T000000Z"}, - "SUMMARY": {"UserDefinedName"}, - "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, - "LOCATION": {"ZU430"}, - }, - }, - &goics.Component{ - Tipo: "VEVENT", - Elements: []goics.Componenter{}, - Properties: map[string][]string{ - "DTSTAMP": {"20231201T000000Z"}, - "UID": {"5e946c0c4474bc6e6337262093e3ef31477e026bbc6bab398d755b002506d9d7@htwkalender.de"}, - "DTEND": {"20231201T010000Z"}, - "DTSTART": {"20231201T000000Z"}, - "SUMMARY": {"UserDefinedName"}, - "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, - "LOCATION": {"ZU221"}, - }, - }, - }, - Properties: map[string][]string{ - "PRODID": {"-//HTWK Kalender//htwkalender.de//DE"}, - "VERSION": {"2.0"}, - "CALSCALE": {"GREGORIAN"}, - "TZID": {"EUROPE/BERLIN"}, - "X-WR-CALNAME": {"HTWK Kalender"}, - "X-WR-TIMEZONE": {"EUROPE/BERLIN"}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - icalModel := IcalModel{ - Events: tt.fields.Events, - Mapping: tt.fields.Mapping, - } - - mockClock := mockTime.MockClock{ - NowTime: time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC), - } - - if got := generateIcalEmit(icalModel, mockClock); !reflect.DeepEqual(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]) - } - } - } - }) - } -} diff --git a/services/data-manager/service/ical/icsComponenter.go b/services/data-manager/service/ical/icsComponenter.go deleted file mode 100644 index b8c3deb..0000000 --- a/services/data-manager/service/ical/icsComponenter.go +++ /dev/null @@ -1,62 +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 ical - -import ( - "github.com/jordic/goics" - "sort" - "strings" -) - -type HtwkalenderComponent struct { - *goics.Component -} - -func NewHtwkalenderComponent() *HtwkalenderComponent { - return &HtwkalenderComponent{ - Component: goics.NewComponent(), - } -} - -// Writes the component to the Writer -func (c *HtwkalenderComponent) Write(w *goics.ICalEncode) { - w.WriteLine("BEGIN:" + c.Tipo + goics.CRLF) - - // Iterate over component properties - var keys []string - for k := range c.Properties { - keys = append(keys, k) - } - sort.Strings(keys) - for _, key := range keys { - vals := c.Properties[key] - for _, val := range vals { - w.WriteLine(WriteStringField(key, val)) - } - } - - for _, xc := range c.Elements { - xc.Write(w) - } - - w.WriteLine("END:" + c.Tipo + goics.CRLF) -} - -// WriteStringField UID:asdfasdfаs@dfasdf.com -func WriteStringField(key string, val string) string { - return strings.ToUpper(key) + ":" + (val) + goics.CRLF -}