add first ical implementation

This commit is contained in:
Elmar Kresse
2023-08-24 19:53:46 +02:00
parent 0686b9397f
commit 075bf3c899
13 changed files with 447 additions and 19 deletions

View File

@ -6,6 +6,8 @@ import (
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"htwk-planner/service"
"htwk-planner/service/fetch"
"htwk-planner/service/ical"
"net/http"
"os"
)
@ -22,7 +24,7 @@ func addRoutes(app *pocketbase.PocketBase) {
Method: http.MethodGet,
Path: "/api/fetchPlans",
Handler: func(c echo.Context) error {
return service.FetchHTWK(c, app)
return fetch.FetchHTWK(c, app)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
@ -39,7 +41,7 @@ func addRoutes(app *pocketbase.PocketBase) {
Method: http.MethodGet,
Path: "/api/fetchGroups",
Handler: func(c echo.Context) error {
return service.FetchSeminarGroups(c, app)
return fetch.SeminarGroups(c, app)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
@ -68,4 +70,38 @@ func addRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/feedURL",
Handler: func(c echo.Context) error {
return ical.FeedURL(c, app)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/feed",
Handler: func(c echo.Context) error {
return ical.Feed(c, app)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
}

1
go.mod
View File

@ -3,6 +3,7 @@ module htwk-planner
go 1.20
require (
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/pocketbase/pocketbase v0.17.5
golang.org/x/net v0.14.0

7
go.sum
View File

@ -81,6 +81,7 @@ github.com/ganigeorgiev/fexpr v0.3.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
@ -125,6 +126,7 @@ github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -133,12 +135,16 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0 h1:p+k2RozdR141dIkAbOuZafkZjrcjT/YvwYYH7qCSG+c=
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0/go.mod h1:YHaw6sOIeFRob8Y9q/blEAMfVcLpeE9+vdhrwyEMxoI=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4=
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@ -146,6 +152,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=

View File

@ -7,10 +7,10 @@ type SeminarGroup struct {
Course string
Faculty string
FacultyId string
Events []Events
Events []Event
}
type Events struct {
type Event struct {
Day string
Week string
Start string
@ -21,4 +21,5 @@ type Events struct {
Rooms string
Notes string
BookedAt string
Semester string
}

View File

@ -281,6 +281,18 @@
"max": null,
"pattern": ""
}
},
{
"id": "vlbpm9fz",
"name": "semester",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [

View File

@ -0,0 +1,47 @@
package date
import "time"
func GetDateFromWeekNumber(year int, weekNumber int, dayName string) (time.Time, error) {
// Create a time.Date for the first day of the year
firstDayOfYear := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
// Calculate the number of days to add to reach the desired week
daysToAdd := time.Duration((weekNumber-1)*7) * 24 * time.Hour
// Find the starting day of the week (e.g., Monday)
startingDayOfWeek := firstDayOfYear
// check if the first day of the year is friday or saturday or sunday
if startingDayOfWeek.Weekday() == time.Friday || startingDayOfWeek.Weekday() == time.Saturday || startingDayOfWeek.Weekday() == time.Sunday {
for startingDayOfWeek.Weekday() != time.Monday {
startingDayOfWeek = startingDayOfWeek.Add(24 * time.Hour)
}
} else {
for startingDayOfWeek.Weekday() != time.Monday {
startingDayOfWeek = startingDayOfWeek.Add(-24 * time.Hour)
}
}
// Calculate the desired date by adding daysToAdd and adjusting for the day name
desiredDate := startingDayOfWeek.Add(daysToAdd)
// Find the day of the week
dayOfWeek := map[string]time.Weekday{
"Montag": time.Monday,
"Dienstag": time.Tuesday,
"Mittwoch": time.Wednesday,
"Donnerstag": time.Thursday,
"Freitag": time.Friday,
"Samstag": time.Saturday,
"Sonntag": time.Sunday,
}[dayName]
// Adjust to the desired day of the week
for desiredDate.Weekday() != dayOfWeek {
desiredDate = desiredDate.Add(24 * time.Hour)
}
return desiredDate, nil
}

View File

@ -0,0 +1,64 @@
package date
import (
"reflect"
"testing"
"time"
)
func Test_getDateFromWeekNumber(t *testing.T) {
type args struct {
year int
weekNumber int
dayName string
}
tests := []struct {
name string
args args
want time.Time
wantErr bool
}{
{
name: "Test 1",
args: args{
year: 2021,
weekNumber: 1,
dayName: "Montag",
},
want: time.Date(2021, 1, 4, 0, 0, 0, 0, time.UTC),
wantErr: false,
},
{
name: "Test 2",
args: args{
year: 2023,
weekNumber: 57,
dayName: "Montag",
},
want: time.Date(2024, 1, 29, 0, 0, 0, 0, time.UTC),
wantErr: false,
},
{
name: "Test 3",
args: args{
year: 2023,
weekNumber: 1,
dayName: "Montag",
},
want: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetDateFromWeekNumber(tt.args.year, tt.args.weekNumber, tt.args.dayName)
if (err != nil) != tt.wantErr {
t.Errorf("getDateFromWeekNumber() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getDateFromWeekNumber() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,10 +1,13 @@
package db
import (
"github.com/jordic/goics"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/models"
"htwk-planner/model"
"strings"
"time"
)
func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) error {
@ -24,6 +27,7 @@ func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection
record.Set("Notes", event.Notes)
record.Set("BookedAt", event.BookedAt)
record.Set("course", seminarGroup.Course)
record.Set("semester", event.Semester)
err = app.Dao().SaveRecord(record)
if err != nil {
println("Error while saving record: ", err.Error())
@ -42,7 +46,7 @@ func contains(s []string, e string) bool {
return false
}
// GetRooms function to get all rooms from database that are stored as a string in the Events struct
// GetRooms function to get all rooms from database that are stored as a string in the Event struct
func GetRooms(app *pocketbase.PocketBase) []string {
var events []struct {
@ -70,3 +74,39 @@ func GetRooms(app *pocketbase.PocketBase) []string {
}
return roomArray
}
type Events []*model.Event
// EmitICal implements the interface for goics
func (e Events) EmitICal() goics.Componenter {
c := goics.NewComponent()
c.SetType("VCALENDAR")
c.AddProperty("CALSCAL", "GREGORIAN")
for _, event := range e {
s := goics.NewComponent()
s.SetType("VEVENT")
timeEnd, _ := time.Parse("2024-01-07 07:00:00 +0000 UTC", event.End)
timeStart, _ := time.Parse("2024-01-07 07:00:00 +0000 UTC", event.Start)
k, v := goics.FormatDateTimeField("DTEND", timeEnd)
s.AddProperty(k, v)
k, v = goics.FormatDateTimeField("DTSTART", timeStart)
s.AddProperty(k, v)
s.AddProperty("SUMMARY", event.Name)
s.AddProperty("DESCRIPTION", event.Notes)
s.AddProperty("LOCATION", event.Rooms)
c.AddComponent(s)
}
return c
}
// GetPlanForCourseAndSemester gets all events for specific course and semester
func GetPlanForCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) Events {
var events Events
// get all events from event records in the events collection
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).All(&events)
if err != nil {
print("Error while getting events from database: ", err)
return nil
}
return events
}

View File

@ -1,4 +1,4 @@
package service
package fetch
import (
"encoding/xml"
@ -40,7 +40,7 @@ func getSeminarHTML() (string, error) {
}
func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
func SeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
result, _ := getSeminarHTML()
@ -55,7 +55,7 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
dbError = db.SaveGroups(groups, collection, app)
if dbError != nil {
return apis.NewApiError(400, "Could not save Events into database", dbError)
return apis.NewApiError(400, "Could not save Event into database", dbError)
}
return c.JSON(http.StatusOK, groups)
@ -64,7 +64,6 @@ func FetchSeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
func parseSeminarGroups(result string) []model.SeminarGroup {
var studium model.Studium
err := xml.Unmarshal([]byte(result), &studium)
if err != nil {
return nil

View File

@ -1,4 +1,4 @@
package service
package fetch
import (
"fmt"
@ -8,11 +8,14 @@ import (
"github.com/pocketbase/pocketbase/models"
"golang.org/x/net/html"
"htwk-planner/model"
"htwk-planner/service/date"
"htwk-planner/service/db"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)
func FetchHTWK(c echo.Context, app *pocketbase.PocketBase) error {
@ -44,7 +47,7 @@ func FetchHTWK(c echo.Context, app *pocketbase.PocketBase) error {
dbError = db.SaveEvents(seminarGroups, collection, app)
if dbError != nil {
return apis.NewApiError(400, "Could not save Events into database", dbError)
return apis.NewApiError(400, "Could not save Event into database", dbError)
}
return c.JSON(http.StatusOK, seminarGroups)
@ -73,24 +76,87 @@ func parseSeminarGroup(result string) model.SeminarGroup {
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
events := splitEventsBySingleWeek(splitEventsByWeekVal)
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
semester, year := extractSemesterAndYear(semesterString)
events = convertWeeksToDates(events, semester, year)
var seminarGroup = model.SeminarGroup{
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
Course: findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data,
Events: events,
}
return seminarGroup
}
func toEvents(tables [][]*html.Node, days []string) []model.Events {
var events []model.Events
func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event {
var newEvents []model.Event
eventYear, _ := strconv.Atoi(year)
// for each event we need to calculate the start and end date based on the week and the year
for _, event := range events {
eventWeek, _ := strconv.Atoi(event.Week)
eventDay, _ := date.GetDateFromWeekNumber(eventYear, eventWeek, event.Day)
start := addTimeToDate(eventDay, event.Start)
end := addTimeToDate(eventDay, event.End)
newEvent := event
newEvent.Start = start.String()
newEvent.End = end.String()
newEvent.Semester = semester
newEvents = append(newEvents, newEvent)
}
return newEvents
}
func addTimeToDate(date time.Time, timeString string) time.Time {
//convert time string to time
timeParts := strings.Split(timeString, ":")
hour, _ := strconv.Atoi(timeParts[0])
minute, _ := strconv.Atoi(timeParts[1])
return time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, time.UTC)
}
func extractSemesterAndYear(semesterString string) (string, string) {
winterPattern := "Wintersemester"
summerPattern := "Sommersemester"
winterMatch := strings.Contains(semesterString, winterPattern)
summerMatch := strings.Contains(semesterString, summerPattern)
semester := ""
semesterShortcut := ""
if winterMatch {
semester = "Wintersemester"
semesterShortcut = "ws"
} else if summerMatch {
semester = "Sommersemester"
semesterShortcut = "ss"
} else {
return "", ""
}
yearPattern := `\d{4}`
combinedPattern := semester + `\s` + yearPattern
re := regexp.MustCompile(combinedPattern)
match := re.FindString(semesterString)
year := ""
if match != "" {
reYear := regexp.MustCompile(yearPattern)
year = reYear.FindString(match)
}
return semesterShortcut, year
}
func toEvents(tables [][]*html.Node, days []string) []model.Event {
var events []model.Event
for table := range tables {
for row := range tables[table] {
tableData := findTableData(tables[table][row])
if len(tableData) > 0 {
events = append(events, model.Events{
events = append(events, model.Event{
Day: days[table],
Week: getTextContent(tableData[0]),
Start: getTextContent(tableData[1]),
@ -110,8 +176,8 @@ func toEvents(tables [][]*html.Node, days []string) []model.Events {
return events
}
func splitEventsByWeek(events []model.Events) []model.Events {
var newEvents []model.Events
func splitEventsByWeek(events []model.Event) []model.Event {
var newEvents []model.Event
for _, event := range events {
weeks := strings.Split(event.Week, ",")
@ -124,8 +190,8 @@ func splitEventsByWeek(events []model.Events) []model.Events {
return newEvents
}
func splitEventsBySingleWeek(events []model.Events) []model.Events {
var newEvents []model.Events
func splitEventsBySingleWeek(events []model.Event) []model.Event {
var newEvents []model.Event
for _, event := range events {
if strings.Contains(event.Week, "-") {

View File

@ -0,0 +1,52 @@
package fetch
import "testing"
func Test_extractSemesterAndYear(t *testing.T) {
type args struct {
semesterString string
}
tests := []struct {
name string
args args
want string
want1 string
}{
// TODO: Add test cases.
{
name: "Test 1",
args: args{
semesterString: "Wintersemester 2023/24 (Planungszeitraum 01.09.2023 bis 03.03.2024)",
},
want: "ws",
want1: "2023",
},
{
name: "Test 2",
args: args{
semesterString: "Sommersemester 2023 (Planungszeitraum 06.03. bis 31.08.2023)",
},
want: "ss",
want1: "2023",
},
{
name: "Test 3",
args: args{
semesterString: "Sommersemester 2010 (Planungszeitraum 06.03. bis 31.08.2023)",
},
want: "ss",
want1: "2010",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := extractSemesterAndYear(tt.args.semesterString)
if got != tt.want {
t.Errorf("extractSemesterAndYear() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("extractSemesterAndYear() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

79
service/ical/ical.go Normal file
View File

@ -0,0 +1,79 @@
package ical
import (
"bytes"
"crypto/rand"
"fmt"
"github.com/jordic/goics"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"htwk-planner/service/db"
"log"
"net/http"
"time"
)
const expirationTime = 5 * time.Minute
var cache = make(map[string]*FeedModel)
func FeedURL(c echo.Context, app *pocketbase.PocketBase) error {
token := randomToken(20)
_, err := createFeedForToken(app, token)
if err != nil {
return err
}
return c.JSON(http.StatusOK, fmt.Sprintf("FeedToken: %s", token))
}
func Feed(c echo.Context, app *pocketbase.PocketBase) error {
var result string
var responseWriter http.ResponseWriter
token := c.Request().Header.Get("token")
log.Print("iCal feed Token: " + token)
feed, ok := cache[token]
if !ok || feed == nil {
return c.JSON(http.StatusNotFound, "No FeedModel for this Token")
}
result = feed.Content
if feed.ExpiresAt.Before(time.Now()) {
newFeed, err := createFeedForToken(app, token)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
result = newFeed.Content
}
responseWriter.Header().Set("Content-type", "text/calendar")
responseWriter.Header().Set("charset", "utf-8")
responseWriter.Header().Set("Content-Disposition", "inline")
responseWriter.Header().Set("filename", "calendar.ics")
writeSuccess(result, responseWriter)
c.Response().Writer = responseWriter
return nil
}
func createFeedForToken(app *pocketbase.PocketBase, token string) (*FeedModel, error) {
res := db.GetPlanForCourseAndSemester(app, "22INM", "ws")
b := bytes.Buffer{}
goics.NewICalEncode(&b).Encode(res)
feed := &FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)}
cache[token] = feed
return feed, nil
}
func randomToken(len int) string {
b := make([]byte, len)
read, err := rand.Read(b)
if err != nil {
return ""
}
return fmt.Sprintf("%x", read)
}
func writeSuccess(message string, w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(message))
}

24
service/ical/icalModel.go Normal file
View File

@ -0,0 +1,24 @@
package ical
import (
"htwk-planner/model"
"time"
)
// FeedModel is an iCal feed
type FeedModel struct {
Content string
ExpiresAt time.Time
}
// Entry is a time entry
type Entry struct {
DateStart time.Time `json:"dateStart"`
DateEnd time.Time `json:"dateEnd"`
Description string `json:"description"`
}
// Entries is a collection of entries
type Entries []*Entry
type Events []*model.Event