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
-}