mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-08-02 17:59:14 +02:00
added frontend and updated backend with docker, wrote some initial instructions
This commit is contained in:
161
backend/service/addRoute.go
Normal file
161
backend/service/addRoute.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
events2 "htwk-planner/service/events"
|
||||
fetch2 "htwk-planner/service/fetch"
|
||||
"htwk-planner/service/ical"
|
||||
"htwk-planner/service/room"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func AddRoutes(app *pocketbase.PocketBase) {
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false))
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/fetchPlans",
|
||||
Handler: func(c echo.Context) error {
|
||||
return fetch2.GetSeminarEvents(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/fetchGroups",
|
||||
Handler: func(c echo.Context) error {
|
||||
return fetch2.SeminarGroups(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/rooms",
|
||||
Handler: func(c echo.Context) error {
|
||||
return room.GetRooms(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.MethodPost,
|
||||
Path: "/api/createFeed",
|
||||
Handler: func(c echo.Context) error {
|
||||
return ical.CreateIndividualFeed(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 {
|
||||
token := c.QueryParam("token")
|
||||
return ical.Feed(c, app, token)
|
||||
},
|
||||
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/course/modules",
|
||||
Handler: func(c echo.Context) error {
|
||||
course := c.QueryParam("course")
|
||||
semester := c.QueryParam("semester")
|
||||
return events2.GetModulesForCourseDistinct(app, c, course, semester)
|
||||
},
|
||||
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/modules",
|
||||
Handler: func(c echo.Context) error {
|
||||
return events2.GetAllModulesDistinct(app, c)
|
||||
},
|
||||
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/courses",
|
||||
Handler: func(c echo.Context) error {
|
||||
return events2.GetAllCourses(app, c)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
47
backend/service/date/dateFormat.go
Normal file
47
backend/service/date/dateFormat.go
Normal 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
|
||||
}
|
64
backend/service/date/dateFormat_test.go
Normal file
64
backend/service/date/dateFormat_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
233
backend/service/db/dbEvents.go
Normal file
233
backend/service/db/dbEvents.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/jordic/goics"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwk-planner/model"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SaveEvents(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) ([]*models.Record, error) {
|
||||
var toBeSavedEvents []struct {
|
||||
model.Event
|
||||
string
|
||||
}
|
||||
var savedRecords []*models.Record
|
||||
var insertRecords []*models.Record
|
||||
|
||||
// check if event is already in database and add to toBeSavedEvents if not
|
||||
for _, seminarGroup := range seminarGroup {
|
||||
for _, event := range seminarGroup.Events {
|
||||
dbGroup, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
||||
|
||||
if dbGroup == nil && err.Error() == "sql: no rows in result set" {
|
||||
toBeSavedEvents = append(toBeSavedEvents, struct {
|
||||
model.Event
|
||||
string
|
||||
}{event, seminarGroup.Course})
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create record for each event that's not already in the database
|
||||
for _, event := range toBeSavedEvents {
|
||||
record := models.NewRecord(collection)
|
||||
record.Set("Day", event.Day)
|
||||
record.Set("Week", event.Week)
|
||||
record.Set("Start", event.Start)
|
||||
record.Set("End", event.End)
|
||||
record.Set("Name", event.Name)
|
||||
record.Set("EventType", event.EventType)
|
||||
record.Set("Prof", event.Prof)
|
||||
record.Set("Rooms", event.Rooms)
|
||||
record.Set("Notes", event.Notes)
|
||||
record.Set("BookedAt", event.BookedAt)
|
||||
record.Set("course", event.string)
|
||||
record.Set("semester", event.Semester)
|
||||
insertRecords = append(insertRecords, record)
|
||||
}
|
||||
|
||||
// save all records
|
||||
for _, record := range insertRecords {
|
||||
if record != nil {
|
||||
err := app.Dao().SaveRecord(record)
|
||||
if err == nil {
|
||||
savedRecords = append(savedRecords, record)
|
||||
} else {
|
||||
log.Println("Error while saving record: ", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return savedRecords, nil
|
||||
}
|
||||
|
||||
func findEventByDayWeekStartEndNameCourse(event model.Event, course string, app *pocketbase.PocketBase) (*model.Event, error) {
|
||||
err := app.Dao().DB().Select("*").From("events").Where(
|
||||
dbx.NewExp("Day = {:day} AND Week = {:week} AND Start = {:start} AND End = {:end} AND Name = {:name} AND course = {:course} AND Prof = {:prof} AND Rooms = {:rooms} AND EventType = {:eventType}",
|
||||
dbx.Params{"day": event.Day, "week": event.Week, "start": event.Start, "end": event.End, "name": event.Name, "course": course, "prof": event.Prof, "rooms": event.Rooms, "eventType": event.EventType}),
|
||||
).One(&event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
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.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", "Notizen: "+event.Notes+"\n Prof: "+event.Prof)
|
||||
s.AddProperty("LOCATION", event.Rooms)
|
||||
c.AddComponent(s)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
func GetPlanForModules(app *pocketbase.PocketBase, modules []struct {
|
||||
Name string `db:"Name" json:"Name"`
|
||||
Course string `db:"course" json:"Course"`
|
||||
}) Events {
|
||||
|
||||
// build query string with name equals elements in modules for dbx query
|
||||
|
||||
var queryString string
|
||||
for i, module := range modules {
|
||||
if i == 0 {
|
||||
queryString = "Name = '" + module.Name + "' AND course = '" + module.Course + "'"
|
||||
} else {
|
||||
queryString = queryString + " OR Name = '" + module.Name + "' AND course = '" + module.Course + "'"
|
||||
}
|
||||
}
|
||||
|
||||
var events 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 {
|
||||
print("Error while getting events from database: ", err)
|
||||
return nil
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) ([]string, error) {
|
||||
var events []struct {
|
||||
Name string `db:"Name" json:"Name"`
|
||||
}
|
||||
|
||||
var eventArray []string
|
||||
|
||||
// get all events from event records in the events collection
|
||||
err := app.Dao().DB().Select("Name").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Distinct(true).All(&events)
|
||||
if err != nil {
|
||||
print("Error while getting events from database: ", err)
|
||||
return eventArray, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
eventArray = append(eventArray, event.Name)
|
||||
}
|
||||
return eventArray, nil
|
||||
}
|
||||
|
||||
func GetAllModulesDistinct(app *pocketbase.PocketBase) ([]struct {
|
||||
Name string
|
||||
Course string
|
||||
}, error) {
|
||||
var events []struct {
|
||||
Name string `db:"Name" json:"Name"`
|
||||
Course string `db:"course" json:"course"`
|
||||
}
|
||||
|
||||
var eventArray []struct {
|
||||
Name string
|
||||
Course string
|
||||
}
|
||||
|
||||
err := app.Dao().DB().Select("Name", "course").From("events").Distinct(true).All(&events)
|
||||
if err != nil {
|
||||
print("Error while getting events from database: ", err)
|
||||
return eventArray, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
eventArray = append(eventArray, struct {
|
||||
Name string
|
||||
Course string
|
||||
}{event.Name, event.Course})
|
||||
}
|
||||
return eventArray, nil
|
||||
}
|
28
backend/service/db/dbFeeds.go
Normal file
28
backend/service/db/dbFeeds.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwk-planner/model"
|
||||
)
|
||||
|
||||
func SaveFeed(feed model.Feed, collection *models.Collection, app *pocketbase.PocketBase) (*models.Record, error) {
|
||||
record := models.NewRecord(collection)
|
||||
record.Set("modules", feed.Modules)
|
||||
err := app.Dao().SaveRecord(record)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func FindFeedByToken(token string, app *pocketbase.PocketBase) (*model.Feed, error) {
|
||||
var feed model.Feed
|
||||
err := app.Dao().DB().Select("*").From("feeds").Where(dbx.NewExp("id = {:id}", dbx.Params{"id": token})).One(&feed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &feed, err
|
||||
}
|
11
backend/service/db/dbFunctions.go
Normal file
11
backend/service/db/dbFunctions.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func FindCollection(app *pocketbase.PocketBase, collectionName string) (*models.Collection, error) {
|
||||
collection, dbError := app.Dao().FindCollectionByNameOrId(collectionName)
|
||||
return collection, dbError
|
||||
}
|
81
backend/service/db/dbGroups.go
Normal file
81
backend/service/db/dbGroups.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwk-planner/model"
|
||||
)
|
||||
|
||||
func SaveGroups(seminarGroup []model.SeminarGroup, collection *models.Collection, app *pocketbase.PocketBase) ([]*models.Record, error) {
|
||||
var savedRecords []*models.Record
|
||||
var tobeSavedGroups []model.SeminarGroup
|
||||
var insertRecords []*models.Record
|
||||
|
||||
for _, group := range seminarGroup {
|
||||
dbGroup, err := FindGroupByCourse(group.Course, app)
|
||||
|
||||
if dbGroup == nil && err.Error() == "sql: no rows in result set" {
|
||||
tobeSavedGroups = append(tobeSavedGroups, group)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create record for each group that's not already in the database
|
||||
for _, group := range tobeSavedGroups {
|
||||
record := models.NewRecord(collection)
|
||||
record.Set("university", group.University)
|
||||
record.Set("shortcut", group.GroupShortcut)
|
||||
record.Set("groupId", group.GroupId)
|
||||
record.Set("course", group.Course)
|
||||
record.Set("faculty", group.Faculty)
|
||||
record.Set("facultyId", group.FacultyId)
|
||||
insertRecords = append(insertRecords, record)
|
||||
}
|
||||
|
||||
// save all records
|
||||
for _, record := range insertRecords {
|
||||
if record != nil {
|
||||
err := app.Dao().SaveRecord(record)
|
||||
if err == nil {
|
||||
savedRecords = append(savedRecords, record)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return savedRecords, nil
|
||||
}
|
||||
|
||||
func FindGroupByCourse(course string, app *pocketbase.PocketBase) (*model.SeminarGroup, error) {
|
||||
var group model.SeminarGroup
|
||||
err := app.Dao().DB().Select("*").From("groups").Where(dbx.NewExp("course = {:course}", dbx.Params{"course": course})).One(&group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &group, err
|
||||
}
|
||||
|
||||
func GetAllCourses(app *pocketbase.PocketBase) []string {
|
||||
|
||||
var courses []struct {
|
||||
CourseShortcut string `db:"course" json:"course"`
|
||||
}
|
||||
|
||||
// get all rooms from event records in the events collection
|
||||
err := app.Dao().DB().Select("course").From("groups").All(&courses)
|
||||
if err != nil {
|
||||
print("Error while getting groups from database: ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var courseArray []string
|
||||
|
||||
for _, course := range courses {
|
||||
courseArray = append(courseArray, course.CourseShortcut)
|
||||
}
|
||||
|
||||
return courseArray
|
||||
}
|
12
backend/service/events/courseService.go
Normal file
12
backend/service/events/courseService.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"htwk-planner/service/db"
|
||||
)
|
||||
|
||||
func GetAllCourses(app *pocketbase.PocketBase, c echo.Context) error {
|
||||
courses := db.GetAllCourses(app)
|
||||
return c.JSON(200, courses)
|
||||
}
|
63
backend/service/events/eventService.go
Normal file
63
backend/service/events/eventService.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"htwk-planner/service/db"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func GetModulesForCourseDistinct(app *pocketbase.PocketBase, c echo.Context, course string, semester string) error {
|
||||
|
||||
modules, err := db.GetAllModulesForCourse(app, course, semester)
|
||||
replaceEmptyEntryInStringArray(modules, "Sonderveranstaltungen")
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(400, err)
|
||||
} else {
|
||||
return c.JSON(200, modules)
|
||||
}
|
||||
}
|
||||
|
||||
func replaceEmptyEntryInStringArray(modules []string, replacement string) {
|
||||
//replace empty string with "Sonderveranstaltungen"
|
||||
for i, module := range modules {
|
||||
if checkIfOnlyWhitespace(module) {
|
||||
modules[i] = replacement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replaceEmptyEntry(modules []struct {
|
||||
Name string
|
||||
Course string
|
||||
}, replacement string) {
|
||||
//replace empty string with "Sonderveranstaltungen"
|
||||
for i, module := range modules {
|
||||
if checkIfOnlyWhitespace(module.Name) {
|
||||
modules[i].Name = replacement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 GetAllModulesDistinct(app *pocketbase.PocketBase, c echo.Context) error {
|
||||
modules, err := db.GetAllModulesDistinct(app)
|
||||
|
||||
replaceEmptyEntry(modules, "Sonderveranstaltungen")
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(400, err)
|
||||
} else {
|
||||
return c.JSON(200, modules)
|
||||
}
|
||||
}
|
277
backend/service/fetch/fetchSeminarEventService.go
Normal file
277
backend/service/fetch/fetchSeminarEventService.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"golang.org/x/net/html"
|
||||
"htwk-planner/model"
|
||||
"htwk-planner/service/date"
|
||||
"htwk-planner/service/db"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSeminarEvents(c echo.Context, app *pocketbase.PocketBase) error {
|
||||
|
||||
seminarGroupsLabel := db.GetAllCourses(app)
|
||||
|
||||
seminarGroups := GetSeminarGroupsEventsFromHTML(seminarGroupsLabel)
|
||||
|
||||
collection, dbError := db.FindCollection(app, "events")
|
||||
if dbError != nil {
|
||||
return apis.NewNotFoundError("Collection not found", dbError)
|
||||
}
|
||||
|
||||
seminarGroups = clearEmptySeminarGroups(seminarGroups)
|
||||
|
||||
savedRecords, dbError := db.SaveEvents(seminarGroups, collection, app)
|
||||
|
||||
if dbError != nil {
|
||||
return apis.NewNotFoundError("Events could not be saved", dbError)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, savedRecords)
|
||||
}
|
||||
|
||||
func clearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup {
|
||||
var newSeminarGroups []model.SeminarGroup
|
||||
for _, seminarGroup := range seminarGroups {
|
||||
if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" {
|
||||
newSeminarGroups = append(newSeminarGroups, seminarGroup)
|
||||
}
|
||||
}
|
||||
return newSeminarGroups
|
||||
}
|
||||
|
||||
func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.SeminarGroup {
|
||||
var seminarGroups []model.SeminarGroup
|
||||
for _, seminarGroupLabel := range seminarGroupsLabel {
|
||||
|
||||
result, getError := getPlanHTML("ss", seminarGroupLabel)
|
||||
if getError == nil {
|
||||
seminarGroup := parseSeminarGroup(result)
|
||||
seminarGroups = append(seminarGroups, seminarGroup)
|
||||
}
|
||||
|
||||
result, getError = getPlanHTML("ws", seminarGroupLabel)
|
||||
if getError == nil {
|
||||
seminarGroup := parseSeminarGroup(result)
|
||||
seminarGroups = append(seminarGroups, seminarGroup)
|
||||
}
|
||||
}
|
||||
return seminarGroups
|
||||
}
|
||||
|
||||
func parseSeminarGroup(result string) model.SeminarGroup {
|
||||
doc, err := html.Parse(strings.NewReader(result))
|
||||
if err != nil {
|
||||
fmt.Printf("Error occurred while parsing the HTML document: %s\n", err.Error())
|
||||
return model.SeminarGroup{}
|
||||
}
|
||||
|
||||
table := findFirstTable(doc)
|
||||
eventTables := getEventTables(doc)
|
||||
allDayLabels := getAllDayLabels(doc)
|
||||
|
||||
if eventTables == nil || allDayLabels == nil {
|
||||
return 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 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.Event{
|
||||
Day: days[table],
|
||||
Week: getTextContent(tableData[0]),
|
||||
Start: getTextContent(tableData[1]),
|
||||
End: getTextContent(tableData[2]),
|
||||
Name: getTextContent(tableData[3]),
|
||||
EventType: getTextContent(tableData[4]),
|
||||
Prof: getTextContent(tableData[5]),
|
||||
Rooms: getTextContent(tableData[6]),
|
||||
Notes: getTextContent(tableData[7]),
|
||||
BookedAt: getTextContent(tableData[8]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
func splitEventsByWeek(events []model.Event) []model.Event {
|
||||
var newEvents []model.Event
|
||||
|
||||
for _, event := range events {
|
||||
weeks := strings.Split(event.Week, ",")
|
||||
for _, week := range weeks {
|
||||
newEvent := event
|
||||
newEvent.Week = strings.TrimSpace(week)
|
||||
newEvents = append(newEvents, newEvent)
|
||||
}
|
||||
}
|
||||
return newEvents
|
||||
}
|
||||
|
||||
func splitEventsBySingleWeek(events []model.Event) []model.Event {
|
||||
var newEvents []model.Event
|
||||
|
||||
for _, event := range events {
|
||||
if strings.Contains(event.Week, "-") {
|
||||
weeks := splitWeekRange(event.Week)
|
||||
for _, week := range weeks {
|
||||
newEvent := event
|
||||
newEvent.Week = week
|
||||
newEvents = append(newEvents, newEvent)
|
||||
}
|
||||
} else {
|
||||
newEvents = append(newEvents, event)
|
||||
}
|
||||
}
|
||||
return newEvents
|
||||
}
|
||||
|
||||
func splitWeekRange(weekRange string) []string {
|
||||
parts := strings.Split(weekRange, "-")
|
||||
if len(parts) != 2 {
|
||||
return nil // Invalid format
|
||||
}
|
||||
|
||||
start, errStart := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
end, errEnd := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
|
||||
if errStart != nil || errEnd != nil {
|
||||
return nil // Error converting to integers
|
||||
}
|
||||
|
||||
var weeks []string
|
||||
for i := start; i <= end; i++ {
|
||||
weeks = append(weeks, strconv.Itoa(i))
|
||||
}
|
||||
|
||||
return weeks
|
||||
}
|
||||
|
||||
func toUtf8(iso88591Buf []byte) string {
|
||||
buf := make([]rune, len(iso88591Buf))
|
||||
for i, b := range iso88591Buf {
|
||||
buf[i] = rune(b)
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// getPlanHTML Get the HTML document from the specified URL
|
||||
func getPlanHTML(semester string, matrikel string) (string, error) {
|
||||
url := "https://stundenplan.htwk-leipzig.de/" + string(semester) + "/Berichte/Text-Listen;Studenten-Sets;name;" + matrikel + "?template=sws_semgrp&weeks=1-65"
|
||||
|
||||
// Send GET request
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("Error occurred while making the request: %s\n", err.Error())
|
||||
return "", err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
|
||||
// Read the response body
|
||||
body, err := io.ReadAll(response.Body)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error occurred while reading the response: %s\n", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
return toUtf8(body), err
|
||||
|
||||
}
|
52
backend/service/fetch/fetchSeminarEventService_test.go
Normal file
52
backend/service/fetch/fetchSeminarEventService_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
114
backend/service/fetch/fetchSeminarGroupService.go
Normal file
114
backend/service/fetch/fetchSeminarGroupService.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwk-planner/model"
|
||||
"htwk-planner/service/db"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func getSeminarHTML(semester string) (string, error) {
|
||||
url := "https://stundenplan.htwk-leipzig.de/stundenplan/xml/public/semgrp_" + semester + ".xml"
|
||||
|
||||
// Send GET request
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("Error occurred while making the request: %s\n", err.Error())
|
||||
return "", err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
|
||||
// Read the response body
|
||||
body, err := io.ReadAll(response.Body)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error occurred while reading the response: %s\n", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), err
|
||||
|
||||
}
|
||||
|
||||
func SeminarGroups(c echo.Context, app *pocketbase.PocketBase) error {
|
||||
var groups []model.SeminarGroup
|
||||
|
||||
resultSummer, _ := getSeminarHTML("ss")
|
||||
resultWinter, _ := getSeminarHTML("ws")
|
||||
|
||||
groups = parseSeminarGroups(resultSummer)
|
||||
groups = append(groups, parseSeminarGroups(resultWinter)...)
|
||||
|
||||
// filter duplicates
|
||||
groups = removeDuplicates(groups)
|
||||
|
||||
collection, dbError := db.FindCollection(app, "groups")
|
||||
if dbError != nil {
|
||||
return apis.NewNotFoundError("Collection not found", dbError)
|
||||
}
|
||||
var insertedGroups []*models.Record
|
||||
|
||||
insertedGroups, dbError = db.SaveGroups(groups, collection, app)
|
||||
if dbError != nil {
|
||||
return apis.NewNotFoundError("Records could not be saved", dbError)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, insertedGroups)
|
||||
}
|
||||
|
||||
func removeDuplicates(groups []model.SeminarGroup) []model.SeminarGroup {
|
||||
var uniqueGroups []model.SeminarGroup
|
||||
for _, group := range groups {
|
||||
if !contains(uniqueGroups, group) {
|
||||
uniqueGroups = append(uniqueGroups, group)
|
||||
}
|
||||
}
|
||||
return uniqueGroups
|
||||
}
|
||||
|
||||
func contains(groups []model.SeminarGroup, group model.SeminarGroup) bool {
|
||||
for _, a := range groups {
|
||||
if a.Course == group.Course {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseSeminarGroups(result string) []model.SeminarGroup {
|
||||
|
||||
var studium model.Studium
|
||||
err := xml.Unmarshal([]byte(result), &studium)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var seminarGroups []model.SeminarGroup
|
||||
for _, Fakultaet := range studium.Fakultaet {
|
||||
for _, Studiengang := range Fakultaet.Studiengang {
|
||||
for _, Studienrichtung := range Studiengang.Semgrp {
|
||||
seminarGroup := model.SeminarGroup{
|
||||
University: "HTWK-Leipzig",
|
||||
GroupShortcut: Studiengang.Name,
|
||||
GroupId: Studiengang.ID,
|
||||
Course: Studienrichtung.Name,
|
||||
Faculty: Fakultaet.Name,
|
||||
FacultyId: Fakultaet.ID,
|
||||
}
|
||||
seminarGroups = append(seminarGroups, seminarGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
return seminarGroups
|
||||
}
|
202
backend/service/fetch/htmlParsingFunctions.go
Normal file
202
backend/service/fetch/htmlParsingFunctions.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"golang.org/x/net/html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Find the first <table> element in the HTML document
|
||||
func findFirstTable(node *html.Node) *html.Node {
|
||||
if node.Type == html.ElementNode && node.Data == "table" {
|
||||
return node
|
||||
}
|
||||
// Traverse child nodes recursively
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
found := findFirstTable(child)
|
||||
if found != nil {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the first <span> element with the specified class attribute value
|
||||
func findFirstSpanWithClass(node *html.Node, classValue string) *html.Node {
|
||||
|
||||
// Check if the current node is a <span> element with the specified class attribute value
|
||||
if node.Type == html.ElementNode && node.Data == "span" {
|
||||
if hasClassAttribute(node, classValue) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse child nodes recursively
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
found := findFirstSpanWithClass(child, classValue)
|
||||
if found != nil {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the specified element has the specified class attribute value
|
||||
func hasClassAttribute(node *html.Node, classValue string) bool {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == "class" && strings.Contains(attr.Val, classValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get Tables with days
|
||||
func getEventTables(node *html.Node) [][]*html.Node {
|
||||
var eventTables [][]*html.Node
|
||||
tables := findTables(node)
|
||||
// get all tables with events
|
||||
for events := range tables {
|
||||
rows := findTableRows(tables[events])
|
||||
// check that a first row exists
|
||||
if len(rows) > 0 {
|
||||
rows = rows[1:]
|
||||
eventTables = append(eventTables, rows)
|
||||
}
|
||||
}
|
||||
return eventTables
|
||||
}
|
||||
|
||||
// Get Tables with days
|
||||
func getAllDayLabels(node *html.Node) []string {
|
||||
paragraphs := findParagraphs(node)
|
||||
var dayArray []string
|
||||
|
||||
for _, p := range paragraphs {
|
||||
label := getDayLabel(p)
|
||||
if label != "" {
|
||||
dayArray = append(dayArray, label)
|
||||
}
|
||||
}
|
||||
return dayArray
|
||||
}
|
||||
|
||||
// Find all <p> elements in the HTML document
|
||||
func findParagraphs(node *html.Node) []*html.Node {
|
||||
var paragraphs []*html.Node
|
||||
|
||||
if node.Type == html.ElementNode && node.Data == "p" {
|
||||
paragraphs = append(paragraphs, node)
|
||||
}
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
paragraphs = append(paragraphs, findParagraphs(child)...)
|
||||
}
|
||||
|
||||
return paragraphs
|
||||
}
|
||||
|
||||
// Find all <tr> elements in <tbody>, excluding the first one
|
||||
func findTableRows(node *html.Node) []*html.Node {
|
||||
var tableRows []*html.Node
|
||||
|
||||
if node.Type == html.ElementNode && node.Data == "tbody" {
|
||||
child := node.FirstChild
|
||||
for child != nil {
|
||||
if child.Type == html.ElementNode && child.Data == "tr" {
|
||||
tableRows = append(tableRows, child)
|
||||
}
|
||||
child = child.NextSibling
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse child nodes recursively
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
var tableRowElement = findTableRows(child)
|
||||
if tableRowElement != nil {
|
||||
tableRows = append(tableRows, tableRowElement...)
|
||||
}
|
||||
}
|
||||
|
||||
// check if tableRows is nil
|
||||
if tableRows == nil {
|
||||
return []*html.Node{}
|
||||
} else {
|
||||
return tableRows
|
||||
}
|
||||
}
|
||||
|
||||
// Find all <p> elements in the HTML document
|
||||
func findTables(node *html.Node) []*html.Node {
|
||||
var tables []*html.Node
|
||||
|
||||
if node.Type == html.ElementNode && node.Data == "table" {
|
||||
tables = append(tables, node)
|
||||
}
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
tables = append(tables, findDayTables(child)...)
|
||||
}
|
||||
|
||||
return tables
|
||||
}
|
||||
|
||||
// Find all <p> elements in the HTML document
|
||||
func findDayTables(node *html.Node) []*html.Node {
|
||||
var tables []*html.Node
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
tables = append(tables, findDayTables(child)...)
|
||||
}
|
||||
|
||||
if node.Type == html.ElementNode && node.Data == "table" && hasClassAttribute(node, "spreadsheet") {
|
||||
tables = append(tables, node)
|
||||
}
|
||||
|
||||
return tables
|
||||
}
|
||||
|
||||
// Get the text content of the specified node and its descendants
|
||||
func getDayLabel(node *html.Node) string {
|
||||
|
||||
child := node.FirstChild
|
||||
if child != nil {
|
||||
if child.Type == html.ElementNode && child.Data == "span" {
|
||||
if child.FirstChild != nil {
|
||||
return child.FirstChild.Data
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find all <td> elements in the current <tr>
|
||||
func findTableData(node *html.Node) []*html.Node {
|
||||
var tableData []*html.Node
|
||||
|
||||
if node.Type == html.ElementNode && node.Data == "tr" {
|
||||
child := node.FirstChild
|
||||
for child != nil {
|
||||
if child.Type == html.ElementNode && child.Data == "td" {
|
||||
tableData = append(tableData, child)
|
||||
}
|
||||
child = child.NextSibling
|
||||
}
|
||||
}
|
||||
|
||||
return tableData
|
||||
}
|
||||
|
||||
// Get the text content of the specified node and its descendants
|
||||
func getTextContent(node *html.Node) string {
|
||||
var textContent string
|
||||
|
||||
if node.Type == html.TextNode {
|
||||
textContent = node.Data
|
||||
}
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
textContent += getTextContent(child)
|
||||
}
|
||||
|
||||
return textContent
|
||||
}
|
102
backend/service/ical/ical.go
Normal file
102
backend/service/ical/ical.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package ical
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/jordic/goics"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
model "htwk-planner/model"
|
||||
db "htwk-planner/service/db"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const expirationTime = 5 * time.Minute
|
||||
|
||||
func Feed(c echo.Context, app *pocketbase.PocketBase, token string) error {
|
||||
layout := "2006-01-02 15:04:05 -0700 MST"
|
||||
|
||||
var result string
|
||||
var responseWriter = c.Response().Writer
|
||||
feed, err := db.FindFeedByToken(token, app)
|
||||
if feed == nil && err != nil {
|
||||
return c.JSON(http.StatusNotFound, err)
|
||||
}
|
||||
|
||||
created, _ := time.Parse(layout, feed.Created)
|
||||
|
||||
var modules []struct {
|
||||
Name string `db:"Name" json:"Name"`
|
||||
Course string `db:"course" json:"Course"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(feed.Modules), &modules)
|
||||
if created.Add(time.Hour * 265).Before(time.Now()) {
|
||||
newFeed, err := createFeedForToken(app, modules)
|
||||
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, modules []struct {
|
||||
Name string `db:"Name" json:"Name"`
|
||||
Course string `db:"course" json:"Course"`
|
||||
}) (*model.FeedModel, error) {
|
||||
res := db.GetPlanForModules(app, modules)
|
||||
b := bytes.Buffer{}
|
||||
goics.NewICalEncode(&b).Encode(res)
|
||||
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))
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
requestBodyBytes, err := io.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
return apis.NewApiError(400, "Could not bind request body", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(requestBodyBytes, &modules)
|
||||
if err != nil {
|
||||
return apis.NewApiError(400, "Could not bind request body", err)
|
||||
}
|
||||
|
||||
var feed model.Feed
|
||||
jsonModules, _ := json.Marshal(modules)
|
||||
feed.Modules = string(jsonModules)
|
||||
|
||||
collection, dbError := db.FindCollection(app, "feeds")
|
||||
if dbError != nil {
|
||||
return apis.NewNotFoundError("Collection not found", dbError)
|
||||
}
|
||||
|
||||
record, err := db.SaveFeed(feed, collection, app)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("Feed could not be saved", dbError)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, record.Id)
|
||||
}
|
13
backend/service/room/roomService.go
Normal file
13
backend/service/room/roomService.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package room
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"htwk-planner/service/db"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetRooms(c echo.Context, app *pocketbase.PocketBase) error {
|
||||
rooms := db.GetRooms(app)
|
||||
return c.JSON(http.StatusOK, rooms)
|
||||
}
|
Reference in New Issue
Block a user