diff --git a/backend/migrations/1698770845_updated_events.go b/backend/migrations/1698770845_updated_events.go new file mode 100644 index 0000000..8c32cdc --- /dev/null +++ b/backend/migrations/1698770845_updated_events.go @@ -0,0 +1,141 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + json.Unmarshal([]byte(`[]`), &collection.Indexes) + + // remove + collection.Schema.RemoveField("7vsr9h6p") + + // remove + collection.Schema.RemoveField("wwpokofe") + + // add + new_start := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "6hkjwgb4", + "name": "start", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_start) + collection.Schema.AddField(new_start) + + // add + new_end := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "szbefpjf", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_end) + collection.Schema.AddField(new_end) + + // add + new_Compulsory := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "nlnnxu7x", + "name": "Compulsory", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), new_Compulsory) + collection.Schema.AddField(new_Compulsory) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + json.Unmarshal([]byte(`[ + "CREATE UNIQUE INDEX `+"`"+`idx_orp1NWL`+"`"+` ON `+"`"+`events`+"`"+` (\n `+"`"+`Day`+"`"+`,\n `+"`"+`Week`+"`"+`,\n `+"`"+`Start`+"`"+`,\n `+"`"+`End`+"`"+`,\n `+"`"+`Name`+"`"+`,\n `+"`"+`course`+"`"+`,\n `+"`"+`Prof`+"`"+`,\n `+"`"+`Rooms`+"`"+`,\n `+"`"+`EventType`+"`"+`\n)" + ]`), &collection.Indexes) + + // add + del_Start := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "7vsr9h6p", + "name": "Start", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), del_Start) + collection.Schema.AddField(del_Start) + + // add + del_End := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "wwpokofe", + "name": "End", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), del_End) + collection.Schema.AddField(del_End) + + // remove + collection.Schema.RemoveField("6hkjwgb4") + + // remove + collection.Schema.RemoveField("szbefpjf") + + // remove + collection.Schema.RemoveField("nlnnxu7x") + + return dao.SaveCollection(collection) + }) +} diff --git a/backend/migrations/1698770863_updated_events.go b/backend/migrations/1698770863_updated_events.go new file mode 100644 index 0000000..e007df4 --- /dev/null +++ b/backend/migrations/1698770863_updated_events.go @@ -0,0 +1,37 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + json.Unmarshal([]byte(`[ + "CREATE INDEX `+"`"+`idx_4vOTAiC`+"`"+` ON `+"`"+`events`+"`"+` (\n `+"`"+`Name`+"`"+`,\n `+"`"+`course`+"`"+`,\n `+"`"+`start`+"`"+`,\n `+"`"+`end`+"`"+`,\n `+"`"+`semester`+"`"+`,\n `+"`"+`EventType`+"`"+`,\n `+"`"+`Compulsory`+"`"+`\n)" + ]`), &collection.Indexes) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + json.Unmarshal([]byte(`[]`), &collection.Indexes) + + return dao.SaveCollection(collection) + }) +} diff --git a/backend/migrations/1698770891_collections_snapshot.go b/backend/migrations/1698770891_collections_snapshot.go new file mode 100644 index 0000000..ca31bf3 --- /dev/null +++ b/backend/migrations/1698770891_collections_snapshot.go @@ -0,0 +1,431 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" +) + +func init() { + m.Register(func(db dbx.Builder) error { + jsonData := `[ + { + "id": "_pb_users_auth_", + "created": "2023-09-20 10:23:59.315Z", + "updated": "2023-10-17 22:18:39.192Z", + "name": "users", + "type": "auth", + "system": false, + "schema": [ + { + "system": false, + "id": "users_name", + "name": "name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "users_avatar", + "name": "avatar", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "maxSize": 5242880, + "mimeTypes": [ + "image/jpeg", + "image/png", + "image/svg+xml", + "image/gif", + "image/webp" + ], + "thumbs": null, + "protected": false + } + } + ], + "indexes": [], + "listRule": "id = @request.auth.id", + "viewRule": "id = @request.auth.id", + "createRule": "", + "updateRule": "id = @request.auth.id", + "deleteRule": "id = @request.auth.id", + "options": { + "allowEmailAuth": true, + "allowOAuth2Auth": true, + "allowUsernameAuth": true, + "exceptEmailDomains": null, + "manageRule": null, + "minPasswordLength": 8, + "onlyEmailDomains": null, + "requireEmail": false + } + }, + { + "id": "cfq9mqlmd97v8z5", + "created": "2023-09-21 16:53:51.811Z", + "updated": "2023-10-17 22:18:39.190Z", + "name": "groups", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "85msl21p", + "name": "university", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "2sii4dtp", + "name": "shortcut", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "uiwgo28f", + "name": "groupId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "y0l1lrzs", + "name": "course", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "kr62mhbz", + "name": "faculty", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "ya6znpez", + "name": "facultyId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_rcaN2Oq` + "`" + ` ON ` + "`" + `groups` + "`" + ` (` + "`" + `course` + "`" + `)" + ], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "d65h4wh7zk13gxp", + "created": "2023-09-21 16:53:51.812Z", + "updated": "2023-10-17 22:18:39.191Z", + "name": "feeds", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "cowxjfmc", + "name": "modules", + "type": "json", + "required": true, + "presentable": false, + "unique": false, + "options": {} + } + ], + "indexes": [], + "listRule": null, + "viewRule": "", + "createRule": null, + "updateRule": "", + "deleteRule": null, + "options": {} + }, + { + "id": "7her4515qsmrxe8", + "created": "2023-09-28 12:07:17.340Z", + "updated": "2023-10-31 16:47:43.090Z", + "name": "events", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "m8ne8e3m", + "name": "Day", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "xnsxqp7j", + "name": "Week", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "aeuskrjo", + "name": "Name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "klrzqyw0", + "name": "EventType", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "5zltexoy", + "name": "Prof", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "gy3nvfmx", + "name": "Rooms", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "hn7b8dfy", + "name": "Notes", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "axskpwm8", + "name": "BookedAt", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "vyyefxp7", + "name": "course", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "vlbpm9fz", + "name": "semester", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "0kahthzr", + "name": "uuid", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "6hkjwgb4", + "name": "start", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "szbefpjf", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "nlnnxu7x", + "name": "Compulsory", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [ + "CREATE INDEX ` + "`" + `idx_4vOTAiC` + "`" + ` ON ` + "`" + `events` + "`" + ` (\n ` + "`" + `Name` + "`" + `,\n ` + "`" + `course` + "`" + `,\n ` + "`" + `start` + "`" + `,\n ` + "`" + `end` + "`" + `,\n ` + "`" + `semester` + "`" + `,\n ` + "`" + `EventType` + "`" + `,\n ` + "`" + `Compulsory` + "`" + `\n)" + ], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + } + ]` + + collections := []*models.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collections); err != nil { + return err + } + + return daos.New(db).ImportCollections(collections, true, nil) + }, func(db dbx.Builder) error { + return nil + }) +} diff --git a/backend/model/eventModel.go b/backend/model/eventModel.go index 5daf446..355e594 100644 --- a/backend/model/eventModel.go +++ b/backend/model/eventModel.go @@ -1,8 +1,10 @@ package model import ( - "github.com/pocketbase/pocketbase/models" "slices" + + "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/types" ) type Events []Event @@ -12,19 +14,20 @@ func (m Events) Contains(event Event) bool { } type Event struct { - UUID string `db:"uuid" json:"uuid"` - Day string `db:"Day" json:"day"` - Week string `db:"Week" json:"week"` - Start string `db:"Start" json:"start"` - End string `db:"End" json:"end"` - Name string `db:"Name" json:"name"` - EventType string `db:"EventType" json:"eventType"` - Prof string `db:"Prof" json:"prof"` - Rooms string `db:"Rooms" json:"rooms"` - Notes string `db:"Notes" json:"notes"` - BookedAt string `db:"BookedAt" json:"bookedAt"` - Course string `db:"course" json:"course"` - Semester string `db:"semester" json:"semester"` + UUID string `db:"uuid" json:"uuid"` + Day string `db:"Day" json:"day"` + Week string `db:"Week" json:"week"` + Start types.DateTime `db:"start" json:"start"` + End types.DateTime `db:"end" json:"end"` + Name string `db:"Name" json:"name"` + EventType string `db:"EventType" json:"eventType"` + Compulsory string `db:"Compulsory" json:"compulsory"` + Prof string `db:"Prof" json:"prof"` + Rooms string `db:"Rooms" json:"rooms"` + Notes string `db:"Notes" json:"notes"` + BookedAt string `db:"BookedAt" json:"bookedAt"` + Course string `db:"course" json:"course"` + Semester string `db:"semester" json:"semester"` models.BaseModel } diff --git a/backend/model/eventModel_test.go b/backend/model/eventModel_test.go index d73c109..4462f4e 100644 --- a/backend/model/eventModel_test.go +++ b/backend/model/eventModel_test.go @@ -1,8 +1,10 @@ package model import ( - "github.com/pocketbase/pocketbase/models" "testing" + + "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/types" ) func TestEvents_Contains(t *testing.T) { @@ -23,20 +25,20 @@ func TestEvents_Contains(t *testing.T) { }, { name: "one event", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, - args: args{event: Event{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + args: args{event: Event{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { name: "two events", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, - args: args{event: Event{Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + args: args{event: Event{Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: true, }, { name: "two events with different values", - m: Events{{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, - args: args{event: Event{Day: "test3", Week: "test3", Start: "test3", End: "test3", Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, + m: Events{{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, + args: args{event: Event{Day: "test3", Week: "test3", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, want: false, }, } @@ -54,8 +56,8 @@ func TestEvent_Equals(t *testing.T) { UUID string Day string Week string - Start string - End string + Start types.DateTime + End types.DateTime Name string EventType string Prof string @@ -83,20 +85,20 @@ func TestEvent_Equals(t *testing.T) { }, { name: "one empty one not", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, args: args{event: Event{}}, want: false, }, { name: "one event", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, - args: args{event: Event{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, want: true, }, { name: "two events", - fields: fields{Day: "test", Week: "test", Start: "test", End: "test", Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, - args: args{event: Event{Day: "test2", Week: "test2", Start: "test2", End: "test2", Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + fields: fields{Day: "test", Week: "test", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test2", Week: "test2", Start: types.NowDateTime(), End: types.NowDateTime(), Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, want: false, }, } diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index aaf7f1c..e43f911 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -1,10 +1,6 @@ package service import ( - "github.com/labstack/echo/v5" - "github.com/pocketbase/pocketbase" - "github.com/pocketbase/pocketbase/apis" - "github.com/pocketbase/pocketbase/core" "htwkalender/model" "htwkalender/service/events" "htwkalender/service/fetch" @@ -12,6 +8,11 @@ import ( "htwkalender/service/room" "io" "net/http" + + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" ) func AddRoutes(app *pocketbase.PocketBase) { @@ -273,6 +274,7 @@ func AddRoutes(app *pocketbase.PocketBase) { }, Middlewares: []echo.MiddlewareFunc{ apis.ActivityLogger(app), + apis.RequireAdminAuth(), }, }) if err != nil { diff --git a/backend/service/date/dateFormat.go b/backend/service/date/dateFormat.go index 41d9a82..0fa9ee8 100644 --- a/backend/service/date/dateFormat.go +++ b/backend/service/date/dateFormat.go @@ -4,7 +4,8 @@ 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) + europeTime, _ := time.LoadLocation("Europe/Berlin") + firstDayOfYear := time.Date(year, time.January, 1, 0, 0, 0, 0, europeTime) // Calculate the number of days to add to reach the desired week daysToAdd := time.Duration((weekNumber-1)*7) * 24 * time.Hour diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 5358051..acad6af 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -1,9 +1,10 @@ package db import ( + "htwkalender/model" + "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" - "htwkalender/model" ) func SaveEvents(seminarGroup []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) { diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index 183b1aa..81521ae 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -2,11 +2,6 @@ package fetch import ( "fmt" - "github.com/google/uuid" - "github.com/labstack/echo/v5" - "github.com/pocketbase/pocketbase" - "github.com/pocketbase/pocketbase/apis" - "golang.org/x/net/html" "htwkalender/model" "htwkalender/service/date" "htwkalender/service/db" @@ -16,6 +11,13 @@ import ( "strconv" "strings" "time" + + "github.com/google/uuid" + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/tools/types" + "golang.org/x/net/html" ) func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error { @@ -77,6 +79,21 @@ func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.Seminar return seminarGroups } +func splitEventType(events []model.Event) []model.Event { + + for i, event := range events { + matched, _ := regexp.Match("^(V|P|S)(w|p)$", []byte(event.EventType)) + if matched { + eventType := event.EventType + event.EventType = eventType[0:1] + event.Compulsory = eventType[1:2] + events[i] = event + } + } + + return events +} + func parseSeminarGroup(result string) model.SeminarGroup { doc, err := html.Parse(strings.NewReader(result)) if err != nil { @@ -100,6 +117,7 @@ func parseSeminarGroup(result string) model.SeminarGroup { semester, year := extractSemesterAndYear(semesterString) events = convertWeeksToDates(events, semester, year) events = generateUUIDs(events, course) + events = splitEventType(events) var seminarGroup = model.SeminarGroup{ University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data, Course: course, @@ -124,27 +142,23 @@ func convertWeeksToDates(events []model.Event, semester string, year string) []m // 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) + start := replaceTimeForDate(eventDay, event.Start.Time()) + end := replaceTimeForDate(eventDay, event.End.Time()) newEvent := event - newEvent.Start = start.String() - newEvent.End = end.String() + newEvent.Start, _ = types.ParseDateTime(start.In(time.UTC)) + newEvent.End, _ = types.ParseDateTime(end.In(time.UTC)) newEvent.Semester = semester newEvents = append(newEvents, newEvent) } return newEvents } -func addTimeToDate(date time.Time, timeString string) time.Time { - europeTime, _ := time.LoadLocation("Europe/Berlin") - //convert time functions 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, europeTime) +// replaceTimeForDate replaces hour, minute, second, nsec for the selected date +func replaceTimeForDate(date time.Time, replacementTime time.Time) time.Time { + return time.Date(date.Year(), date.Month(), date.Day(), replacementTime.Hour(), replacementTime.Minute(), replacementTime.Second(), replacementTime.Nanosecond(), date.Location()) } func extractSemesterAndYear(semesterString string) (string, string) { @@ -188,11 +202,13 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event { tableData := findTableData(tables[table][row]) if len(tableData) > 0 { + start, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[1]))) + end, _ := types.ParseDateTime(createTimeFromHourAndMinuteString(getTextContent(tableData[2]))) events = append(events, model.Event{ Day: days[table], Week: getTextContent(tableData[0]), - Start: getTextContent(tableData[1]), - End: getTextContent(tableData[2]), + Start: start, + End: end, Name: getTextContent(tableData[3]), EventType: getTextContent(tableData[4]), Prof: getTextContent(tableData[5]), @@ -208,6 +224,16 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event { return events } +// createEventFromTableData should create an event from the table data +// tableTime represents Hour and Minute like HH:MM +// tableDate returns a Time +func createTimeFromHourAndMinuteString(tableTime string) time.Time { + timeParts := strings.Split(tableTime, ":") + hour, _ := strconv.Atoi(timeParts[0]) + minute, _ := strconv.Atoi(timeParts[1]) + return time.Date(0, 0, 0, hour, minute, 0, 0, time.UTC) +} + func splitEventsByWeek(events []model.Event) []model.Event { var newEvents []model.Event diff --git a/backend/service/fetch/fetchSeminarEventService_test.go b/backend/service/fetch/fetchSeminarEventService_test.go index 111f0b8..dde66b4 100644 --- a/backend/service/fetch/fetchSeminarEventService_test.go +++ b/backend/service/fetch/fetchSeminarEventService_test.go @@ -4,6 +4,7 @@ import ( "htwkalender/model" "reflect" "testing" + "time" ) func Test_extractSemesterAndYear(t *testing.T) { @@ -119,6 +120,96 @@ func Test_replaceEmptyEventNames(t *testing.T) { } } +func Test_splitEventType(t *testing.T) { + type args struct { + events []model.Event + } + tests := []struct { + name string + args args + want []model.Event + }{ + { + name: "Test 1", + args: args{ + events: []model.Event{ + { + EventType: "V", + }, + }, + }, + want: []model.Event{ + { + EventType: "V", + Compulsory: "", + }, + }, + }, + { + name: "Test 2", + args: args{ + events: []model.Event{ + { + EventType: "Vw", + }, + }, + }, + want: []model.Event{ + { + EventType: "V", + Compulsory: "w", + }, + }, + }, + { + name: "Test 3", + args: args{ + events: []model.Event{ + { + EventType: "Sperr", + }, + }, + }, + want: []model.Event{ + { + EventType: "Sperr", + Compulsory: "", + }, + }, + }, + { + name: "Test 4", + args: args{ + events: []model.Event{ + { + EventType: "Sperr", + }, + { + EventType: "Vw", + }, + }, + }, + want: []model.Event{ + { + EventType: "Sperr", + Compulsory: "", + }, + { + EventType: "V", + Compulsory: "w", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := splitEventType(tt.args.events); !reflect.DeepEqual(got, tt.want) { + t.Errorf("splitEventType() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_generateUUIDs(t *testing.T) { type args struct { events []model.Event @@ -172,3 +263,80 @@ func Test_generateUUIDs(t *testing.T) { }) } } + +func Test_createTimeFromHourAndMinuteString(t *testing.T) { + europeTime, _ := time.LoadLocation("Europe/Berlin") + type args struct { + tableTime string + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Test 1", + args: args{ + tableTime: "08:00", + }, + want: time.Date(0, 0, 0, 8, 0, 0, 0, europeTime), + }, + { + name: "Test 2", + args: args{ + tableTime: "08:15", + }, + want: time.Date(0, 0, 0, 8, 15, 0, 0, europeTime), + }, + { + name: "Test 3", + args: args{ + tableTime: "08:30", + }, + want: time.Date(0, 0, 0, 8, 30, 0, 0, europeTime), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := createTimeFromHourAndMinuteString(tt.args.tableTime); !reflect.DeepEqual(got, tt.want) { + t.Errorf("createTimeFromHourAndMinuteString() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_replaceTimeInDate(t *testing.T) { + type args struct { + date time.Time + time time.Time + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Test 1", + args: args{ + date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + time: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC), + }, + want: time.Date(2021, 1, 1, 8, 0, 0, 0, time.UTC), + }, + { + name: "Test 2", + args: args{ + date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + time: time.Date(0, 0, 0, 8, 15, 0, 0, time.UTC), + }, + want: time.Date(2021, 1, 1, 8, 15, 0, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := replaceTimeForDate(tt.args.date, tt.args.time); !reflect.DeepEqual(got, tt.want) { + t.Errorf("addTimeToDate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go index 2b77c3a..eb4e2e7 100644 --- a/backend/service/ical/icalFileGeneration.go +++ b/backend/service/ical/icalFileGeneration.go @@ -1,13 +1,15 @@ package ical import ( - "github.com/jordic/goics" "htwkalender/model" "htwkalender/service/functions" + "htwkalender/service/names" "time" + + "github.com/jordic/goics" ) -// local type for EmitICal function +// IcalModel local type for EmitICal function type IcalModel struct { Events model.Events Mapping []model.FeedCollection @@ -15,7 +17,7 @@ type IcalModel struct { // EmitICal implements the interface for goics func (icalModel IcalModel) EmitICal() goics.Componenter { - layout := "2006-01-02 15:04:05 -0700 MST" + europeTime, _ := time.LoadLocation("Europe/Berlin") c := goics.NewComponent() c.SetType("VCALENDAR") c.AddProperty("VERSION", "2.0") @@ -27,13 +29,11 @@ func (icalModel IcalModel) EmitICal() goics.Componenter { for _, event := range icalModel.Events { s := goics.NewComponent() s.SetType("VEVENT") - timeEnd, _ := time.Parse(layout, event.End) - timeStart, _ := time.Parse(layout, event.Start) - k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", timeEnd) + k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", event.End.Time().Local().In(europeTime)) s.AddProperty(k, v) - k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", timeStart) + k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start.Time().Local().In(europeTime)) s.AddProperty(k, v) - s.AddProperty("SUMMARY", replaceNameIfUserDefined(event.Name, icalModel.Mapping)) + s.AddProperty("SUMMARY", replaceNameIfUserDefined(&event, icalModel.Mapping)) s.AddProperty("DESCRIPTION", generateDescription(event)) s.AddProperty("LOCATION", event.Rooms) c.AddComponent(s) @@ -41,13 +41,13 @@ func (icalModel IcalModel) EmitICal() goics.Componenter { return c } -func replaceNameIfUserDefined(name string, mapping []model.FeedCollection) string { +func replaceNameIfUserDefined(event *model.Event, mapping []model.FeedCollection) string { for _, mapEntry := range mapping { - if mapEntry.Name == name && !functions.OnlyWhitespace(mapEntry.UserDefinedName) { - return mapEntry.UserDefinedName + if mapEntry.Name == event.Name && !functions.OnlyWhitespace(mapEntry.UserDefinedName) { + return names.ReplaceTemplateSubStrings(mapEntry.UserDefinedName, *event) } } - return name + return event.Name } func generateDescription(event model.Event) string { @@ -63,7 +63,7 @@ func generateDescription(event model.Event) string { description += "Gruppe: " + event.Course + "\n" } if !functions.OnlyWhitespace(event.EventType) { - description += "Typ: " + event.EventType + "\n" + description += "Typ: " + event.EventType + event.Compulsory + "\n" } return description diff --git a/backend/service/names/userDefinedNameTemplates.go b/backend/service/names/userDefinedNameTemplates.go new file mode 100644 index 0000000..489b120 --- /dev/null +++ b/backend/service/names/userDefinedNameTemplates.go @@ -0,0 +1,23 @@ +package names + +import ( + "htwkalender/model" + "regexp" +) + +func ReplaceTemplateSubStrings(rawString string, event model.Event) string { + re := regexp.MustCompile(`\%(.)`) + + return re.ReplaceAllStringFunc(rawString, func(match string) string { + switch match { + case "%%": + return "%" + case "%t": + return event.EventType + case "%p": + return event.Compulsory + default: + return match + } + }) +} diff --git a/backend/service/names/userDefinedNameTemplates_test.go b/backend/service/names/userDefinedNameTemplates_test.go new file mode 100644 index 0000000..7570a8e --- /dev/null +++ b/backend/service/names/userDefinedNameTemplates_test.go @@ -0,0 +1,78 @@ +package names + +import ( + "htwkalender/model" + "testing" +) + +func TestReplaceTemplateSubStrings(t *testing.T) { + type args struct { + rawString string + event model.Event + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Test 1", + args: args{ + rawString: "%t", + event: model.Event{ + EventType: "Test", + }, + }, + want: "Test", + }, + { + name: "Test 2", + args: args{ + rawString: "%p", + event: model.Event{ + Compulsory: "Test", + }, + }, + want: "Test", + }, + { + name: "Test 3", + args: args{ + rawString: "%%", + event: model.Event{ + EventType: "Test", + }, + }, + want: "%", + }, + { + name: "Test 4", + args: args{ + rawString: "%t %p", + event: model.Event{ + EventType: "Test", + Compulsory: "Test", + }, + }, + want: "Test Test", + }, + { + name: "Test 5", + args: args{ + rawString: "%t %p %%", + event: model.Event{ + EventType: "Test", + Compulsory: "Test", + }, + }, + want: "Test Test %", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ReplaceTemplateSubStrings(tt.args.rawString, tt.args.event); got != tt.want { + t.Errorf("ReplaceTemplateSubStrings() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/frontend/src/components/RenameModules.vue b/frontend/src/components/RenameModules.vue index a43340e..af333bf 100644 --- a/frontend/src/components/RenameModules.vue +++ b/frontend/src/components/RenameModules.vue @@ -3,7 +3,7 @@ import moduleStore from "../store/moduleStore.ts"; import { createIndividualFeed } from "../api/createFeed.ts"; import router from "../router"; import tokenStore from "../store/tokenStore.ts"; -import { ref } from "vue"; +import { Ref, ref } from "vue"; const tableData = ref( moduleStore().modules.map((module) => { @@ -19,6 +19,13 @@ const columns = ref([ { field: "Module", header: "Module" }, ]); +const helpVisible: Ref = ref(false); + +const placeholders = ref([ + { placeholder: "%t", description: "Event Type", examples: "V = Vorlesung, S = Seminar, P = Praktikum/Prüfung" }, + { placeholder: "%p", description: "Mandatory", examples: "w = optional, p = mandatory" }, +]); + async function finalStep() { const token: string = await createIndividualFeed(moduleStore().modules); tokenStore().setToken(token); @@ -30,6 +37,26 @@ async function finalStep() {

Rename your selected Modules to your liking.

+ + +

+ Here you can rename your modules to your liking. This will be the name + of the event in your calendar. +

+

+ You can use the following placeholders in your module names: +

+ + + + + +