From 7228d17d8475e52cdf4d161f69ae7650fb5fa10e Mon Sep 17 00:00:00 2001 From: masterelmar <18119527+masterElmar@users.noreply.github.com> Date: Sat, 28 Oct 2023 11:10:06 +0200 Subject: [PATCH] feat:#31 updated database start end to date --- .../migrations/1698444986_updated_events.go | 114 +++++ .../1698445123_collections_snapshot.go | 417 ++++++++++++++++++ backend/model/eventModel.go | 27 +- backend/service/date/dateFormat.go | 3 +- .../service/fetch/fetchSeminarEventService.go | 34 +- .../fetch/fetchSeminarEventService_test.go | 95 ++++ backend/service/ical/icalFileGeneration.go | 10 +- 7 files changed, 667 insertions(+), 33 deletions(-) create mode 100644 backend/migrations/1698444986_updated_events.go create mode 100644 backend/migrations/1698445123_collections_snapshot.go diff --git a/backend/migrations/1698444986_updated_events.go b/backend/migrations/1698444986_updated_events.go new file mode 100644 index 0000000..f0d23e3 --- /dev/null +++ b/backend/migrations/1698444986_updated_events.go @@ -0,0 +1,114 @@ +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 + } + + // remove + collection.Schema.RemoveField("7vsr9h6p") + + // remove + collection.Schema.RemoveField("wwpokofe") + + // add + new_start := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "tvxitgwc", + "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": "trbmsfcz", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_end) + collection.Schema.AddField(new_end) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("7her4515qsmrxe8") + if err != nil { + return err + } + + // 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("tvxitgwc") + + // remove + collection.Schema.RemoveField("trbmsfcz") + + return dao.SaveCollection(collection) + }) +} diff --git a/backend/migrations/1698445123_collections_snapshot.go b/backend/migrations/1698445123_collections_snapshot.go new file mode 100644 index 0000000..2e2d92d --- /dev/null +++ b/backend/migrations/1698445123_collections_snapshot.go @@ -0,0 +1,417 @@ +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": "cfq9mqlmd97v8z5", + "created": "2023-09-19 17:31:15.957Z", + "updated": "2023-10-27 22:15:09.073Z", + "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-19 17:31:15.957Z", + "updated": "2023-10-27 22:15:09.073Z", + "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-19 17:31:15.958Z", + "updated": "2023-10-27 22:16:26.924Z", + "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": "tvxitgwc", + "name": "start", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "trbmsfcz", + "name": "end", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": [ + "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)" + ], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "_pb_users_auth_", + "created": "2023-10-27 22:15:08.738Z", + "updated": "2023-10-27 22:15:09.074Z", + "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 + } + } + ]` + + 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..b25ccbf 100644 --- a/backend/model/eventModel.go +++ b/backend/model/eventModel.go @@ -3,6 +3,7 @@ package model import ( "github.com/pocketbase/pocketbase/models" "slices" + "time" ) type Events []Event @@ -12,19 +13,19 @@ 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 time.Time `db:"start" json:"start"` + End time.Time `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"` models.BaseModel } 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/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index 183b1aa..0040df7 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -129,22 +129,21 @@ func convertWeeksToDates(events []model.Event, semester string, year string) []m start := addTimeToDate(eventDay, event.Start) end := addTimeToDate(eventDay, event.End) newEvent := event - newEvent.Start = start.String() - newEvent.End = end.String() + newEvent.Start = start + newEvent.End = end 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) +// addTimeToDate adds each value onto date +func addTimeToDate(date time.Time, addDate time.Time) time.Time { + newDate := date + newDate = newDate.Add(time.Second * time.Duration(addDate.Second())) + newDate = newDate.Add(time.Hour * time.Duration(addDate.Hour())) + newDate = newDate.Add(time.Minute * time.Duration(addDate.Minute())) + return newDate } func extractSemesterAndYear(semesterString string) (string, string) { @@ -191,8 +190,8 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event { events = append(events, model.Event{ Day: days[table], Week: getTextContent(tableData[0]), - Start: getTextContent(tableData[1]), - End: getTextContent(tableData[2]), + Start: createTimeFromHourAndMinuteString(getTextContent(tableData[1])), + End: createTimeFromHourAndMinuteString(getTextContent(tableData[2])), Name: getTextContent(tableData[3]), EventType: getTextContent(tableData[4]), Prof: getTextContent(tableData[5]), @@ -208,6 +207,17 @@ 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 { + europeTime, _ := time.LoadLocation("Europe/Berlin") + timeParts := strings.Split(tableTime, ":") + hour, _ := strconv.Atoi(timeParts[0]) + minute, _ := strconv.Atoi(timeParts[1]) + return time.Date(0, 0, 0, hour, minute, 0, 0, europeTime) +} + 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..f8161d8 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) { @@ -172,3 +173,97 @@ 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_addTimeToDate(t *testing.T) { + europeTime, _ := time.LoadLocation("Europe/Berlin") + 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), + }, + { + name: "Test 3", + args: args{ + date: time.Date(2002, 12, 31, 17, 0, 0, 0, time.UTC), + time: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC), + }, + want: time.Date(2003, 1, 1, 1, 0, 0, 0, time.UTC), + }, + { + name: "Test 4", + args: args{ + date: time.Date(2023, 10, 29, 0, 0, 0, 0, europeTime), + time: time.Date(0, 0, 0, 10, 0, 0, 0, time.UTC), + }, + want: time.Date(2023, 10, 29, 9, 0, 0, 0, europeTime), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := addTimeToDate(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..a0efba4 100644 --- a/backend/service/ical/icalFileGeneration.go +++ b/backend/service/ical/icalFileGeneration.go @@ -4,10 +4,9 @@ import ( "github.com/jordic/goics" "htwkalender/model" "htwkalender/service/functions" - "time" ) -// local type for EmitICal function +// IcalModel local type for EmitICal function type IcalModel struct { Events model.Events Mapping []model.FeedCollection @@ -15,7 +14,6 @@ type IcalModel struct { // EmitICal implements the interface for goics func (icalModel IcalModel) EmitICal() goics.Componenter { - layout := "2006-01-02 15:04:05 -0700 MST" c := goics.NewComponent() c.SetType("VCALENDAR") c.AddProperty("VERSION", "2.0") @@ -27,11 +25,9 @@ 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) s.AddProperty(k, v) - k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", timeStart) + k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start) s.AddProperty(k, v) s.AddProperty("SUMMARY", replaceNameIfUserDefined(event.Name, icalModel.Mapping)) s.AddProperty("DESCRIPTION", generateDescription(event))