diff --git a/backend/model/eventModel.go b/backend/model/eventModel.go new file mode 100644 index 0000000..f83e22d --- /dev/null +++ b/backend/model/eventModel.go @@ -0,0 +1,3 @@ +package model + +type Events []*Event diff --git a/backend/model/icalModel.go b/backend/model/icalModel.go index d66a695..4cfa1b1 100644 --- a/backend/model/icalModel.go +++ b/backend/model/icalModel.go @@ -22,4 +22,8 @@ type Entry struct { // Entries is a collection of entries type Entries []*Entry -type Events []*Event +type FeedCollection struct { + Name string `db:"Name" json:"Name"` + Course string `db:"course" json:"Course"` + UserDefinedName string `db:"userDefinedName" json:"UserDefinedName"` +} diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 9770fbe..c35499e 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -1,15 +1,11 @@ package db import ( - "github.com/jordic/goics" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/models" "htwkalender/model" "log" - "strings" - "time" - "unicode" ) func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) ([]*models.Record, error) { @@ -81,60 +77,10 @@ func findEventByDayWeekStartEndNameCourse(event model.Event, course string, app return &event, err } -func contains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - -// 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 { - Rooms string `db:"Rooms" json:"Rooms"` - } - - // get all rooms from event records in the events collection - err := app.Dao().DB().Select("Rooms").From("events").All(&events) - if err != nil { - print("Error while getting rooms from database: ", err) - return nil - } - - var roomArray []string - - for _, event := range events { - var room = strings.Split(event.Rooms, " ") - //split string room by space and add each room to array if it is not already in there - for _, r := range room { - var text = strings.TrimSpace(r) - if !contains(roomArray, text) && !strings.Contains(text, " ") && len(text) >= 1 { - roomArray = append(roomArray, text) - } - } - } - return roomArray -} - -func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) []model.Event { - var events []model.Event - - // get all events from event records in the events collection - err := app.Dao().DB().Select("*").From("events").Where(dbx.Like("Rooms", room)).AndWhere(dbx.Like("Start", date)).All(&events) - if err != nil { - print("Error while getting events from database: ", err) - return nil - } - return events -} - // gets all events for specific course and semester // TODO add filter for year -func GetPlanForCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) Events { - var events Events +func GetPlanForCourseAndSemester(app *pocketbase.PocketBase, course string, semester string) model.Events { + var events model.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 { @@ -144,12 +90,9 @@ func GetPlanForCourseAndSemester(app *pocketbase.PocketBase, course string, seme return events } -func GetPlanForModules(app *pocketbase.PocketBase, modules []struct { - Name string `db:"Name" json:"Name"` - Course string `db:"course" json:"Course"` -}) Events { +func GetPlanForModules(app *pocketbase.PocketBase, modules []model.FeedCollection) model.Events { - // build query string with name equals elements in modules for dbx query + // build query functions with name equals elements in modules for dbx query var queryString string for i, module := range modules { @@ -160,7 +103,7 @@ func GetPlanForModules(app *pocketbase.PocketBase, modules []struct { } } - var events Events + var events model.Events // get all events from event records in the events collection err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp(queryString)).All(&events) if err != nil { @@ -218,59 +161,3 @@ func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]struct { } return eventArray, nil } - -type Events []*model.Event - -// EmitICal implements the interface for goics -func (e Events) EmitICal() goics.Componenter { - layout := "2006-01-02 15:04:05 -0700 MST" - c := goics.NewComponent() - c.SetType("VCALENDAR") - c.AddProperty("VERSION", "2.0") - c.AddProperty("CALSCAL", "GREGORIAN") - c.AddProperty("TZID", "Europe/Berlin") - c.AddProperty("X-WR-CALNAME", "HTWK Kalender") - c.AddProperty("X-WR-TIMEZONE", "Europe/Berlin") - c.AddProperty("X-LIC-LOCATION", "Europe/Berlin") - for _, event := range e { - 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) - s.AddProperty(k, v) - k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", timeStart) - s.AddProperty(k, v) - s.AddProperty("SUMMARY", event.Name) - s.AddProperty("DESCRIPTION", generateDescription(event)) - s.AddProperty("LOCATION", event.Rooms) - c.AddComponent(s) - } - return c -} - -func generateDescription(event *model.Event) string { - var description string - - if !CheckIfOnlyWhitespace(event.Notes) { - description += "Notizen: " + event.Notes + "\n" - } - if !CheckIfOnlyWhitespace(event.Prof) { - description += "Prof: " + event.Prof + "\n" - } - if !CheckIfOnlyWhitespace(event.Course) { - description += "Gruppe: " + event.Course + "\n" - } - - return description -} - -// check if course is empty or contains only whitespaces -func CheckIfOnlyWhitespace(word string) bool { - for _, letter := range word { - if !unicode.IsSpace(letter) || !(letter == int32(160)) { - return false - } - } - return true -} diff --git a/backend/service/db/dbRooms.go b/backend/service/db/dbRooms.go new file mode 100644 index 0000000..8fc9c19 --- /dev/null +++ b/backend/service/db/dbRooms.go @@ -0,0 +1,49 @@ +package db + +import ( + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase" + "htwkalender/model" + "htwkalender/service/functions" + "strings" +) + +func GetRooms(app *pocketbase.PocketBase) []string { + + var events []struct { + Rooms string `db:"Rooms" json:"Rooms"` + } + + // get all rooms from event records in the events collection + err := app.Dao().DB().Select("Rooms").From("events").All(&events) + if err != nil { + print("Error while getting rooms from database: ", err) + return nil + } + + var roomArray []string + + for _, event := range events { + var room = strings.Split(event.Rooms, " ") + //split functions room by space and add each room to array if it is not already in there + for _, r := range room { + var text = strings.TrimSpace(r) + if !functions.Contains(roomArray, text) && !strings.Contains(text, " ") && len(text) >= 1 { + roomArray = append(roomArray, text) + } + } + } + return roomArray +} + +func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) []model.Event { + var events []model.Event + + // get all events from event records in the events collection + err := app.Dao().DB().Select("*").From("events").Where(dbx.Like("Rooms", room)).AndWhere(dbx.Like("Start", date)).All(&events) + if err != nil { + print("Error while getting events from database: ", err) + return nil + } + return events +} diff --git a/backend/service/events/eventService.go b/backend/service/events/eventService.go index daf1a1c..4e73775 100644 --- a/backend/service/events/eventService.go +++ b/backend/service/events/eventService.go @@ -4,6 +4,7 @@ import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "htwkalender/service/db" + "htwkalender/service/functions" ) func GetModulesForCourseDistinct(app *pocketbase.PocketBase, c echo.Context, course string, semester string) error { @@ -19,9 +20,9 @@ func GetModulesForCourseDistinct(app *pocketbase.PocketBase, c echo.Context, cou } func replaceEmptyEntryInStringArray(modules []string, replacement string) { - //replace empty string with "Sonderveranstaltungen" + //replace empty functions with "Sonderveranstaltungen" for i, module := range modules { - if db.CheckIfOnlyWhitespace(module) { + if functions.CheckIfOnlyWhitespace(module) { modules[i] = replacement } } @@ -31,9 +32,9 @@ func replaceEmptyEntry(modules []struct { Name string Course string }, replacement string) { - //replace empty string with "Sonderveranstaltungen" + //replace empty functions with "Sonderveranstaltungen" for i, module := range modules { - if db.CheckIfOnlyWhitespace(module.Name) { + if functions.CheckIfOnlyWhitespace(module.Name) { modules[i].Name = replacement } } diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go index a756120..17a6125 100644 --- a/backend/service/fetch/fetchSeminarEventService.go +++ b/backend/service/fetch/fetchSeminarEventService.go @@ -118,7 +118,7 @@ func convertWeeksToDates(events []model.Event, semester string, year string) []m func addTimeToDate(date time.Time, timeString string) time.Time { europeTime, _ := time.LoadLocation("Europe/Berlin") - //convert time string to time + //convert time functions to time timeParts := strings.Split(timeString, ":") hour, _ := strconv.Atoi(timeParts[0]) minute, _ := strconv.Atoi(timeParts[1]) diff --git a/backend/service/functions/string.go b/backend/service/functions/string.go new file mode 100644 index 0000000..dea7f7b --- /dev/null +++ b/backend/service/functions/string.go @@ -0,0 +1,22 @@ +package functions + +import "unicode" + +// check if course is empty or contains only whitespaces +func CheckIfOnlyWhitespace(word string) bool { + for _, letter := range word { + if !unicode.IsSpace(letter) || !(letter == int32(160)) { + return false + } + } + return true +} + +func Contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/backend/service/ical/ical.go b/backend/service/ical/ical.go index 1677416..17f66f5 100644 --- a/backend/service/ical/ical.go +++ b/backend/service/ical/ical.go @@ -7,8 +7,8 @@ import ( "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" - model "htwkalender/model" - db "htwkalender/service/db" + "htwkalender/model" + "htwkalender/service/db" "io" "net/http" "time" @@ -28,10 +28,7 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error { created, _ := time.Parse(layout, feed.Created) - var modules []struct { - Name string `db:"Name" json:"Name"` - Course string `db:"course" json:"Course"` - } + var modules []model.FeedCollection _ = json.Unmarshal([]byte(feed.Modules), &modules) if created.Add(time.Hour * 265).Before(time.Now()) { newFeed, err := createFeedForToken(app, modules) @@ -50,30 +47,26 @@ func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error { return nil } -func createFeedForToken(app *pocketbase.PocketBase, modules []struct { - Name string `db:"Name" json:"Name"` - Course string `db:"course" json:"Course"` -}) (*model.FeedModel, error) { +func createFeedForToken(app *pocketbase.PocketBase, modules []model.FeedCollection) (*model.FeedModel, error) { res := db.GetPlanForModules(app, modules) b := bytes.Buffer{} - goics.NewICalEncode(&b).Encode(res) + goics.NewICalEncode(&b).Encode(IcalModel{Events: res, Mapping: modules}) feed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)} return feed, nil } func writeSuccess(message string, w http.ResponseWriter) { w.WriteHeader(http.StatusOK) - w.Write([]byte(message)) + _, err := w.Write([]byte(message)) + if err != nil { + return + } } func CreateIndividualFeed(c echo.Context, app *pocketbase.PocketBase) error { // read json from request body - var modules []struct { - Name string `db:"Name" json:"Name"` - Course string `db:"course" json:"Course"` - } - + var modules []model.FeedCollection requestBodyBytes, err := io.ReadAll(c.Request().Body) if err != nil { return apis.NewApiError(400, "Could not bind request body", err) diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go new file mode 100644 index 0000000..ea250c8 --- /dev/null +++ b/backend/service/ical/icalFileGeneration.go @@ -0,0 +1,67 @@ +package ical + +import ( + "github.com/jordic/goics" + "htwkalender/model" + "htwkalender/service/functions" + "time" +) + +// local type for EmitICal function +type IcalModel struct { + Events model.Events + Mapping []model.FeedCollection +} + +// 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") + c.AddProperty("CALSCAL", "GREGORIAN") + c.AddProperty("TZID", "Europe/Berlin") + c.AddProperty("X-WR-CALNAME", "HTWK Kalender") + c.AddProperty("X-WR-TIMEZONE", "Europe/Berlin") + c.AddProperty("X-LIC-LOCATION", "Europe/Berlin") + 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) + s.AddProperty(k, v) + k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", timeStart) + s.AddProperty(k, v) + s.AddProperty("SUMMARY", replaceNameIfUserDefined(event.Name, icalModel.Mapping)) + s.AddProperty("DESCRIPTION", generateDescription(event)) + s.AddProperty("LOCATION", event.Rooms) + c.AddComponent(s) + } + return c +} + +func replaceNameIfUserDefined(name string, mapping []model.FeedCollection) string { + for _, mapEntry := range mapping { + if mapEntry.Name == name { + return mapEntry.UserDefinedName + } + } + return name +} + +func generateDescription(event *model.Event) string { + var description string + + if !functions.CheckIfOnlyWhitespace(event.Notes) { + description += "Notizen: " + event.Notes + "\n" + } + if !functions.CheckIfOnlyWhitespace(event.Prof) { + description += "Prof: " + event.Prof + "\n" + } + if !functions.CheckIfOnlyWhitespace(event.Course) { + description += "Gruppe: " + event.Course + "\n" + } + + return description +} diff --git a/frontend/src/api/fetchCourse.ts b/frontend/src/api/fetchCourse.ts index ca572e7..75bc575 100644 --- a/frontend/src/api/fetchCourse.ts +++ b/frontend/src/api/fetchCourse.ts @@ -25,7 +25,7 @@ export async function fetchModulesByCourseAndSemester( }) .then((modulesResponse) => { modulesResponse.forEach((module: string) => - modules.push(new Module(module, course)), + modules.push(new Module(module, course, module)), ); }); return modules; @@ -38,7 +38,9 @@ export async function fetchAllModules(): Promise { return response.json() as Promise; }) .then((responseModules: Module[]) => { - modules = responseModules as Module[]; + responseModules.forEach((module: Module) => { + modules.push(new Module(module.Name, module.Course, module.Name)); + }); }); return modules; diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/components/AdditionalModules.vue index 487b7b9..c9ef74b 100644 --- a/frontend/src/components/AdditionalModules.vue +++ b/frontend/src/components/AdditionalModules.vue @@ -3,9 +3,8 @@ import { ref, Ref } from "vue"; import { Module } from "../model/module.ts"; import { fetchAllModules } from "../api/fetchCourse.ts"; import moduleStore from "../store/moduleStore.ts"; -import { createIndividualFeed } from "../api/createFeed.ts"; import { MultiSelectAllChangeEvent } from "primevue/multiselect"; -import tokenStore from "../store/tokenStore.ts"; + import router from "../router"; const fetchedModules = async () => { @@ -23,14 +22,12 @@ fetchedModules().then( })), ); -async function finalStep() { +async function nextStep() { selectedModules.value.forEach((module: Module) => { moduleStore().addModule(module); }); - const token: string = await createIndividualFeed(moduleStore().modules); - tokenStore().setToken(token); - await router.push("/calendar-link"); + await router.push("/rename-modules"); } const display = (module: Module) => module.Name + " (" + module.Course + ")"; @@ -90,7 +87,7 @@ function selectChange() {
- +
diff --git a/frontend/src/components/RenameModules.vue b/frontend/src/components/RenameModules.vue new file mode 100644 index 0000000..1d46a96 --- /dev/null +++ b/frontend/src/components/RenameModules.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 6e12613..bbcebbf 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -21,6 +21,8 @@ import ToastService from "primevue/toastservice"; import Toast from "primevue/toast"; import Accordion from 'primevue/accordion'; import AccordionTab from 'primevue/accordiontab'; +import DataTable from "primevue/datatable"; +import Column from "primevue/column"; const app = createApp(App); const pinia = createPinia(); @@ -42,4 +44,6 @@ app.component("MultiSelect", MultiSelect); app.component("Toast", Toast); app.component("Accordion", Accordion); app.component("AccordionTab", AccordionTab); +app.component("DataTable", DataTable); +app.component("Column", Column); app.mount("#app"); diff --git a/frontend/src/model/module.ts b/frontend/src/model/module.ts index c3bece8..eff8f67 100644 --- a/frontend/src/model/module.ts +++ b/frontend/src/model/module.ts @@ -2,5 +2,6 @@ export class Module { constructor( public Name: string, public Course: string, + public UserDefinedName: string, ) {} } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 27eac80..196b70c 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -5,6 +5,7 @@ import AdditionalModules from "../components/AdditionalModules.vue"; import CalendarLink from "../components/CalendarLink.vue"; import Impress from "../components/Impress.vue"; import PrivacyPolicy from "../components/PrivacyPolicy.vue"; +import RenameModules from "../components/RenameModules.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -39,6 +40,11 @@ const router = createRouter({ name: "impress", component: Impress, }, + { + path: "/rename-modules", + name: "rename-modules", + component: RenameModules, + }, { path: "/:catchAll(.*)", redirect: "/",