diff --git a/services/data-manager/Dockerfile b/services/data-manager/Dockerfile
index 9e52f5d..0799b2d 100644
--- a/services/data-manager/Dockerfile
+++ b/services/data-manager/Dockerfile
@@ -50,7 +50,7 @@ EXPOSE 8090
ENTRYPOINT ["./main", "serve"]
-FROM golang:1.21.6 AS dev
+FROM golang:1.23 AS dev
# Set the Current Working Directory inside the container
WORKDIR /htwkalender-data-manager
diff --git a/services/go.mod b/services/go.mod
index 0b5f256..94733a1 100644
--- a/services/go.mod
+++ b/services/go.mod
@@ -24,6 +24,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
+ github.com/arran4/golang-ical v0.3.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
diff --git a/services/go.sum b/services/go.sum
index 66c6218..6e16631 100644
--- a/services/go.sum
+++ b/services/go.sum
@@ -73,6 +73,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
+github.com/arran4/golang-ical v0.3.1 h1:v13B3eQZ9VDHTAvT6M11vVzxYgcYmjyPBE2eAZl3VZk=
+github.com/arran4/golang-ical v0.3.1/go.mod h1:LZWxF8ZIu/sjBVUCV0udiVPrQAgq3V0aa0RfbO99Qkk=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@@ -139,6 +141,7 @@ github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnTh
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -267,6 +270,8 @@ github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -293,6 +298,7 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -330,6 +336,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@@ -491,9 +498,12 @@ google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6h
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/services/ical/Dockerfile b/services/ical/Dockerfile
index 19d231c..446d796 100644
--- a/services/ical/Dockerfile
+++ b/services/ical/Dockerfile
@@ -49,7 +49,7 @@ EXPOSE 8091
ENTRYPOINT ["./main"]
-FROM golang:1.21.6 AS dev
+FROM golang:1.23 AS dev
# Set the Current Working Directory inside the container
WORKDIR /htwkalender-ical
diff --git a/services/ical/main.go b/services/ical/main.go
index 1769786..0b8b19f 100644
--- a/services/ical/main.go
+++ b/services/ical/main.go
@@ -62,7 +62,12 @@ func main() {
DataManagerURL: "http://" + host + ":8090",
}
- fiberApp.Use(logger.New())
+ fiberApp.Use(logger.New(
+ logger.Config{
+ Format: "[${time}] ${status} - ${latency} ${method} ${path} ${error}\n",
+ TimeFormat: "02-01-2006 15:04:05",
+ },
+ ))
// Add routes to the app instance for the data-manager ical service
service.AddFeedRoutes(app)
diff --git a/services/ical/service/ical/ical.go b/services/ical/service/ical/ical.go
index ba8e7f2..6a331d5 100644
--- a/services/ical/service/ical/ical.go
+++ b/services/ical/service/ical/ical.go
@@ -17,9 +17,7 @@
package ical
import (
- "bytes"
"fmt"
- "github.com/jordic/goics"
"htwkalender/ical/model"
"htwkalender/ical/service/connector"
htwkalenderGrpc "htwkalender/ical/service/connector/grpc"
@@ -31,7 +29,7 @@ const expirationTime = 5 * time.Minute
var FeedDeletedError = fmt.Errorf("feed deleted")
-func Feed(app model.AppType, token string) (string, error) {
+func Feed(app model.AppType, token string, userAgent string) (string, error) {
var events model.Events
modules := map[string]model.FeedCollection{}
@@ -68,9 +66,8 @@ func Feed(app model.AppType, token string) (string, error) {
}
}
- b := bytes.Buffer{}
- goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules})
- icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))}
+ cal := GenerateIcalFeed(events, modules, userAgent)
+ icalFeed := &model.FeedModel{Content: cal.Serialize(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))}
return icalFeed.Content, nil
}
diff --git a/services/ical/service/ical/icalDescriptionGenerator.go b/services/ical/service/ical/icalDescriptionGenerator.go
new file mode 100644
index 0000000..d88559c
--- /dev/null
+++ b/services/ical/service/ical/icalDescriptionGenerator.go
@@ -0,0 +1,153 @@
+//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 (
+ ics "github.com/arran4/golang-ical"
+ "htwkalender/ical/model"
+ "htwkalender/ical/service/functions"
+ "net/url"
+ "strings"
+ _ "time/tzdata"
+)
+
+// Generates user-agent specific description and adds it to the calendar event.
+func generateUserAgentSpecificDescription(vEvent *ics.VEvent, event model.Event, userAgent string) {
+ // Generate description and ALTREP (alternative representation) based on user agent.
+ description, altrep := generateDescription(event, userAgent)
+
+ if isThunderbird(userAgent) && altrep != "" {
+ // Thunderbird-specific handling: Add DESCRIPTION with ALTREP attribute.
+ // create property altrep
+ altrepParam := WithAltRep(altrep)
+
+ vEvent.AddProperty(ics.ComponentPropertyDescription, description, altrepParam)
+ AddProperty(ics.ComponentPropertyDescription, description, altrepParam, vEvent)
+ } else {
+ // Default handling: Add plain DESCRIPTION property for other user agents.
+ vEvent.AddProperty("DESCRIPTION", description)
+ }
+
+}
+
+func AddProperty(property ics.ComponentProperty, value string, prop ics.PropertyParameter, cb *ics.VEvent) {
+ baseProperty := &ics.BaseProperty{
+ IANAToken: string(property),
+ Value: value,
+ ICalParameters: map[string][]string{},
+ }
+
+ customProperty := NewCustomProperty(*baseProperty)
+
+ r := ics.IANAProperty{
+ BaseProperty: customProperty,
+ }
+ k, v := prop.KeyValue()
+ r.ICalParameters[k] = v
+ cb.Properties = append(cb.Properties, r)
+}
+
+// Generates description based on the event details and user agent.
+func generateDescription(event model.Event, userAgent string) (string, string) {
+ plainDescription := buildPlainTextDescription(event)
+
+ if isThunderbird(userAgent) {
+ // Thunderbird-specific: Generate HTML and ALTREP format for Thunderbird users.
+ htmlDescription := generateThunderbirdHTMLDescription(event)
+ altrep := `"data:text/html,` + url.PathEscape(htmlDescription) + `"`
+ return plainDescription, altrep
+ }
+
+ // Google Calendar-specific handling.
+ if isGoogleCalendar(userAgent) {
+ plainDescription += generateGoogleCalendarDescription(event.Rooms)
+ }
+
+ // Default: Return plain text description without ALTREP for non-Thunderbird cases.
+ return plainDescription, ""
+}
+
+// Builds the plain text description from the event details.
+func buildPlainTextDescription(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
+}
+
+// Helper function to generate HTML description for Thunderbird's ALTREP.
+func generateThunderbirdHTMLDescription(event model.Event) string {
+ var htmlDescription strings.Builder
+
+ // HTML-encoded description similar to plain text.
+ if !functions.OnlyWhitespace(event.Prof) {
+ htmlDescription.WriteString("Profs: " + event.Prof + "
")
+ }
+ if !functions.OnlyWhitespace(event.Course) {
+ htmlDescription.WriteString("Gruppen: " + event.Course + "
")
+ }
+ if !functions.OnlyWhitespace(event.EventType) {
+ htmlDescription.WriteString("Typ: " + event.EventType + event.Compulsory + "
")
+ }
+
+ // Add the HTML link to the room map.
+ htmlDescription.WriteString(`Link: HTWK-Karte`)
+
+ return htmlDescription.String()
+}
+
+// Generates a room description with links for Google Calendar.
+func generateGoogleCalendarDescription(rooms string) string {
+ var description strings.Builder
+ roomList := strings.Split(rooms, " ")
+ description.WriteString("Orte: \n ")
+
+ for _, room := range roomList {
+ description.WriteString("HTWK-Karte\n")
+ }
+
+ return description.String()
+}
+
+// Checks if the user agent is Thunderbird.
+func isThunderbird(userAgent string) bool {
+ return strings.Contains(userAgent, "Thunderbird")
+}
+
+// Checks if the user agent is Google Calendar.
+func isGoogleCalendar(userAgent string) bool {
+ return strings.Contains(userAgent, "Google-Calendar-Importer")
+}
+
+func WithAltRep(altRepUrl string) ics.PropertyParameter {
+ return &ics.KeyValues{
+ Key: string(ics.ParameterAltrep),
+ Value: []string{altRepUrl},
+ }
+}
diff --git a/services/ical/service/ical/icalFileGeneration.go b/services/ical/service/ical/icalFileGeneration.go
index 9af0c60..fed15df 100644
--- a/services/ical/service/ical/icalFileGeneration.go
+++ b/services/ical/service/ical/icalFileGeneration.go
@@ -17,121 +17,153 @@
package ical
import (
+ ics "github.com/arran4/golang-ical"
"htwkalender/ical/model"
"htwkalender/ical/service/functions"
clock "htwkalender/ical/service/functions/time"
"htwkalender/ical/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
-}
+func GenerateIcalFeed(events model.Events, mapping map[string]model.FeedCollection, userAgent string) *ics.Calendar {
-// EmitICal implements the interface for goics
-func (icalModel IcalModel) EmitICal() goics.Componenter {
- internalClock := clock.RealClock{}
- c := generateIcalEmit(icalModel, internalClock)
- return c
-}
+ cal := ics.NewCalendarFor("HTWK Kalender")
+ cal.SetMethod(ics.MethodPublish)
+ cal.SetProductId("-//HTWK Kalender//htwkalender.de//DE")
+ cal.SetTzid("Europe/Berlin")
+ cal.SetXWRCalName("HTWK Kalender")
+ cal.SetXWRTimezone("Europe/Berlin")
+ cal.SetVersion("2.0")
+ cal.SetCalscale("GREGORIAN")
+
+ vTimeZone := ics.NewTimezone("Europe/Berlin")
+
+ vTimeZone.Components = []ics.Component{
+ &ics.Daylight{
+ ComponentBase: ics.ComponentBase{
+ Properties: []ics.IANAProperty{
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyDtstart),
+ Value: "19700329T020000",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyRrule),
+ Value: "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzname),
+ Value: "CEST",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetfrom),
+ Value: "+0100",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetto),
+ Value: "+0200",
+ },
+ },
+ },
+ },
+ },
+ &ics.Standard{
+ ComponentBase: ics.ComponentBase{
+ Properties: []ics.IANAProperty{
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyDtstart),
+ Value: "19701025T030000",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyRrule),
+ Value: "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzname),
+ Value: "CET",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetfrom),
+ Value: "+0200",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetto),
+ Value: "+0100",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cal.AddVTimezone(vTimeZone)
-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")
+ internalClock := clock.RealClock{}
- 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 events {
+ mapEntry, mappingFound := mapping[event.UUID]
- 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(time.Time(event.Start).String() + time.Time(event.End).String() + event.Course + event.Name + event.Rooms)
- s.AddProperty("UID", eventHash+"@htwkalender.de")
- s.AddProperty(goics.FormatDateTime("DTEND", time.Time(event.End).Local().In(europeTime)))
- s.AddProperty(goics.FormatDateTime("DTSTART", time.Time(event.Start).Local().In(europeTime)))
+ icalEvent := ics.NewEvent(eventHash + "@htwkalender.de")
+
+ icalEvent.SetDtStampTime(internalClock.Now().Local().In(europeTime))
+ icalEvent.SetStartAt(time.Time(event.Start).Local().In(europeTime))
+ icalEvent.SetEndAt(time.Time(event.End).Local().In(europeTime))
if mappingFound {
- addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
- addAlarmIfSpecified(s, event, mapEntry, internalClock)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, replaceNameIfUserDefined(&event, mapEntry))
+ addAlarmIfSpecified(icalEvent, event, mapEntry, internalClock)
} else {
- addPropertyIfNotEmpty(s, "SUMMARY", event.Name)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, event.Name)
}
- addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event))
- addPropertyIfNotEmpty(s, "LOCATION", event.Rooms)
- c.AddComponent(s)
+ generateUserAgentSpecificDescription(icalEvent, event, userAgent)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertyLocation, event.Rooms)
+
+ cal.AddVEvent(icalEvent)
+
}
- return c
+ return cal
}
-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)
+// AddPropertyIfNotEmpty adds a property to the component if the value is not empty
+// or contains only whitespaces
+func addPropertyIfNotEmpty(component *ics.VEvent, key ics.ComponentProperty, value string) {
+ if !functions.OnlyWhitespace(value) {
+ component.AddProperty(key, value)
+ }
}
// 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) {
+func addAlarmIfSpecified(icalEvent *ics.VEvent, event model.Event, mapping model.FeedCollection, clock clock.Clock) {
// if event.Start > now
// then add alarm
if time.Time(event.Start).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)
+ alarm := icalEvent.AddAlarm()
+ alarm.AddProperty(ics.ComponentPropertyTrigger, "-P0DT0H15M0S")
+ alarm.AddProperty(ics.ComponentPropertyAction, "DISPLAY")
+ alarm.AddProperty(ics.ComponentPropertyDescription, "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
}
}
@@ -144,30 +176,3 @@ func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection)
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/ical/service/routes.go b/services/ical/service/routes.go
index c92a0f7..09ffc9e 100644
--- a/services/ical/service/routes.go
+++ b/services/ical/service/routes.go
@@ -34,7 +34,13 @@ func AddFeedRoutes(app model.AppType) {
app.Fiber.Get("/api/feed", func(c fiber.Ctx) error {
token := c.Query("token")
- results, err := ical.Feed(app, token)
+
+ // get request userAgent and check if it is Thunderbird
+ userAgent := c.Get("User-Agent")
+
+ slog.Info("User-Agent", "userAgent", userAgent)
+
+ results, err := ical.Feed(app, token, userAgent)
if errors.Is(err, ical.FeedDeletedError) {
return c.SendStatus(fiber.StatusGone)