feat:#34 refactored function to intended service, fixed docker files

This commit is contained in:
Elmar Kresse
2024-06-10 16:57:40 +02:00
parent cb76b5c188
commit 2d7701b0c9
96 changed files with 212 additions and 79 deletions

View File

@@ -0,0 +1,50 @@
//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 ical
import (
"encoding/json"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"htwkalender/model"
"htwkalender/service/db"
)
func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) {
var modules []model.FeedCollection
err := json.Unmarshal(requestBody, &modules)
if err != nil {
return "", apis.NewNotFoundError("Could not parse request body", err)
}
var icalFeed model.Feed
jsonModules, _ := json.Marshal(modules)
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(icalFeed, collection, app)
if err != nil {
return "", apis.NewNotFoundError("Could not save feed", err)
}
return record.Id, nil
}

View File

@@ -0,0 +1,173 @@
//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 ical
import (
"htwkalender/model"
"htwkalender/service/functions"
clock "htwkalender/service/functions/time"
"htwkalender/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
}

View File

@@ -0,0 +1,258 @@
//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 ical
import (
"github.com/jordic/goics"
"htwkalender/model"
mockTime "htwkalender/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])
}
}
}
})
}
}

View File

@@ -0,0 +1,62 @@
//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 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
}