mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-08-07 04:09:17 +02:00
Merge branch 'development' into 'main'
Update Main See merge request htwk-software/htwkalender!5
This commit is contained in:
@@ -41,6 +41,6 @@ func main() {
|
||||
service.AddSchedules(app)
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
slog.Error("Failed to start app: %v", err)
|
||||
slog.Error("Failed to start app: ", "error", err)
|
||||
}
|
||||
}
|
||||
|
@@ -57,6 +57,10 @@ type Event struct {
|
||||
models.BaseModel
|
||||
}
|
||||
|
||||
type EventType struct {
|
||||
EventType string `db:"EventType" json:"eventType"`
|
||||
}
|
||||
|
||||
func (e *Event) Equals(event Event) bool {
|
||||
return e.Day == event.Day &&
|
||||
e.Week == event.Week &&
|
||||
@@ -79,7 +83,7 @@ func (e *Event) SetCourse(course string) Event {
|
||||
return *e
|
||||
}
|
||||
|
||||
// Creates an AnonymizedEventDTO from an Event hiding all sensitive data
|
||||
// AnonymizeEvent Creates an AnonymizedEventDTO from an Event hiding all sensitive data
|
||||
func (e *Event) AnonymizeEvent() AnonymizedEventDTO {
|
||||
return AnonymizedEventDTO{
|
||||
Day: e.Day,
|
||||
|
@@ -217,3 +217,264 @@ func TestEvent_AnonymizeEvent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_GetName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Day string
|
||||
Week string
|
||||
Start types.DateTime
|
||||
End types.DateTime
|
||||
Name string
|
||||
EventType string
|
||||
Compulsory string
|
||||
Prof string
|
||||
Rooms string
|
||||
Notes string
|
||||
BookedAt string
|
||||
Course string
|
||||
Semester string
|
||||
BaseModel models.BaseModel
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty event",
|
||||
fields: fields{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "one event",
|
||||
fields: fields{Name: "Event"},
|
||||
want: "Event",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &Event{
|
||||
UUID: tt.fields.UUID,
|
||||
Day: tt.fields.Day,
|
||||
Week: tt.fields.Week,
|
||||
Start: tt.fields.Start,
|
||||
End: tt.fields.End,
|
||||
Name: tt.fields.Name,
|
||||
EventType: tt.fields.EventType,
|
||||
Compulsory: tt.fields.Compulsory,
|
||||
Prof: tt.fields.Prof,
|
||||
Rooms: tt.fields.Rooms,
|
||||
Notes: tt.fields.Notes,
|
||||
BookedAt: tt.fields.BookedAt,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
BaseModel: tt.fields.BaseModel,
|
||||
}
|
||||
if got := e.GetName(); got != tt.want {
|
||||
t.Errorf("GetName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_SetCourse(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Day string
|
||||
Week string
|
||||
Start types.DateTime
|
||||
End types.DateTime
|
||||
Name string
|
||||
EventType string
|
||||
Compulsory string
|
||||
Prof string
|
||||
Rooms string
|
||||
Notes string
|
||||
BookedAt string
|
||||
Course string
|
||||
Semester string
|
||||
BaseModel models.BaseModel
|
||||
}
|
||||
type args struct {
|
||||
course string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want Event
|
||||
}{
|
||||
{
|
||||
name: "set course",
|
||||
fields: fields{},
|
||||
args: args{course: "test"},
|
||||
want: Event{Course: "test"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &Event{
|
||||
UUID: tt.fields.UUID,
|
||||
Day: tt.fields.Day,
|
||||
Week: tt.fields.Week,
|
||||
Start: tt.fields.Start,
|
||||
End: tt.fields.End,
|
||||
Name: tt.fields.Name,
|
||||
EventType: tt.fields.EventType,
|
||||
Compulsory: tt.fields.Compulsory,
|
||||
Prof: tt.fields.Prof,
|
||||
Rooms: tt.fields.Rooms,
|
||||
Notes: tt.fields.Notes,
|
||||
BookedAt: tt.fields.BookedAt,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
BaseModel: tt.fields.BaseModel,
|
||||
}
|
||||
if got := e.SetCourse(tt.args.course); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SetCourse() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_SetName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Day string
|
||||
Week string
|
||||
Start types.DateTime
|
||||
End types.DateTime
|
||||
Name string
|
||||
EventType string
|
||||
Compulsory string
|
||||
Prof string
|
||||
Rooms string
|
||||
Notes string
|
||||
BookedAt string
|
||||
Course string
|
||||
Semester string
|
||||
BaseModel models.BaseModel
|
||||
}
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "set name",
|
||||
fields: fields{
|
||||
Name: "name",
|
||||
},
|
||||
args: args{
|
||||
name: "name",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &Event{
|
||||
UUID: tt.fields.UUID,
|
||||
Day: tt.fields.Day,
|
||||
Week: tt.fields.Week,
|
||||
Start: tt.fields.Start,
|
||||
End: tt.fields.End,
|
||||
Name: tt.fields.Name,
|
||||
EventType: tt.fields.EventType,
|
||||
Compulsory: tt.fields.Compulsory,
|
||||
Prof: tt.fields.Prof,
|
||||
Rooms: tt.fields.Rooms,
|
||||
Notes: tt.fields.Notes,
|
||||
BookedAt: tt.fields.BookedAt,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
BaseModel: tt.fields.BaseModel,
|
||||
}
|
||||
e.SetName(tt.args.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvent_TableName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Day string
|
||||
Week string
|
||||
Start types.DateTime
|
||||
End types.DateTime
|
||||
Name string
|
||||
EventType string
|
||||
Compulsory string
|
||||
Prof string
|
||||
Rooms string
|
||||
Notes string
|
||||
BookedAt string
|
||||
Course string
|
||||
Semester string
|
||||
BaseModel models.BaseModel
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "table name",
|
||||
fields: fields{},
|
||||
want: "events",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &Event{
|
||||
UUID: tt.fields.UUID,
|
||||
Day: tt.fields.Day,
|
||||
Week: tt.fields.Week,
|
||||
Start: tt.fields.Start,
|
||||
End: tt.fields.End,
|
||||
Name: tt.fields.Name,
|
||||
EventType: tt.fields.EventType,
|
||||
Compulsory: tt.fields.Compulsory,
|
||||
Prof: tt.fields.Prof,
|
||||
Rooms: tt.fields.Rooms,
|
||||
Notes: tt.fields.Notes,
|
||||
BookedAt: tt.fields.BookedAt,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
BaseModel: tt.fields.BaseModel,
|
||||
}
|
||||
if got := e.TableName(); got != tt.want {
|
||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvents_Contains1(t *testing.T) {
|
||||
type args struct {
|
||||
event Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
m Events
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty events",
|
||||
m: Events{},
|
||||
args: args{event: Event{}},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.m.Contains(tt.args.event); got != tt.want {
|
||||
t.Errorf("Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
45
backend/model/feedModel_test.go
Normal file
45
backend/model/feedModel_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFeed_SetModules(t *testing.T) {
|
||||
type fields struct {
|
||||
Modules string
|
||||
Retrieved types.DateTime
|
||||
BaseModel models.BaseModel
|
||||
}
|
||||
type args struct {
|
||||
modules string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "set modules",
|
||||
fields: fields{
|
||||
Modules: "",
|
||||
Retrieved: types.DateTime{},
|
||||
BaseModel: models.BaseModel{},
|
||||
},
|
||||
args: args{
|
||||
modules: "modules",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := &Feed{
|
||||
Modules: tt.fields.Modules,
|
||||
Retrieved: tt.fields.Retrieved,
|
||||
BaseModel: tt.fields.BaseModel,
|
||||
}
|
||||
f.SetModules(tt.args.modules)
|
||||
})
|
||||
}
|
||||
}
|
126
backend/model/moduleModel_test.go
Normal file
126
backend/model/moduleModel_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestModuleDTO_GetName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Name string
|
||||
Prof string
|
||||
Course string
|
||||
Semester string
|
||||
EventType string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "get name",
|
||||
fields: fields{
|
||||
Name: "name",
|
||||
},
|
||||
want: "name",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &ModuleDTO{
|
||||
UUID: tt.fields.UUID,
|
||||
Name: tt.fields.Name,
|
||||
Prof: tt.fields.Prof,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
EventType: tt.fields.EventType,
|
||||
}
|
||||
if got := m.GetName(); got != tt.want {
|
||||
t.Errorf("GetName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleDTO_SetName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Name string
|
||||
Prof string
|
||||
Course string
|
||||
Semester string
|
||||
EventType string
|
||||
}
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "set name",
|
||||
fields: fields{
|
||||
Name: "name",
|
||||
},
|
||||
args: args{
|
||||
name: "name",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &ModuleDTO{
|
||||
UUID: tt.fields.UUID,
|
||||
Name: tt.fields.Name,
|
||||
Prof: tt.fields.Prof,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
EventType: tt.fields.EventType,
|
||||
}
|
||||
m.SetName(tt.args.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModule_SetName(t *testing.T) {
|
||||
type fields struct {
|
||||
UUID string
|
||||
Name string
|
||||
Prof string
|
||||
Course string
|
||||
Semester string
|
||||
Events Events
|
||||
}
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "set name",
|
||||
fields: fields{
|
||||
Name: "name",
|
||||
},
|
||||
args: args{
|
||||
name: "name",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &Module{
|
||||
UUID: tt.fields.UUID,
|
||||
Name: tt.fields.Name,
|
||||
Prof: tt.fields.Prof,
|
||||
Course: tt.fields.Course,
|
||||
Semester: tt.fields.Semester,
|
||||
Events: tt.fields.Events,
|
||||
}
|
||||
m.SetName(tt.args.name)
|
||||
})
|
||||
}
|
||||
}
|
@@ -17,12 +17,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"htwkalender/service/course"
|
||||
"htwkalender/service/events"
|
||||
"htwkalender/service/fetch/sport"
|
||||
v1 "htwkalender/service/fetch/v1"
|
||||
v2 "htwkalender/service/fetch/v2"
|
||||
"htwkalender/service/functions/time"
|
||||
"htwkalender/service/ical"
|
||||
"htwkalender/service/room"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -42,7 +42,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
Handler: func(c echo.Context) error {
|
||||
savedEvents, err := v2.ParseEventsFromRemote(app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to parse events from remote: %v", err)
|
||||
slog.Error("Failed to parse events from remote: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to parse events from remote")
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, savedEvents)
|
||||
@@ -59,6 +59,25 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/fetch/daily/events",
|
||||
Handler: func(c echo.Context) error {
|
||||
course.UpdateCourse(app)
|
||||
return c.JSON(http.StatusOK, "Daily events fetched")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
apis.RequireAdminAuth(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
@@ -157,7 +176,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
date := c.QueryParam("date")
|
||||
roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get room schedule for day: %v", err)
|
||||
slog.Error("Failed to get room schedule for day: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day")
|
||||
}
|
||||
return c.JSON(http.StatusOK, roomSchedule)
|
||||
@@ -183,7 +202,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
from := c.QueryParam("from")
|
||||
roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get room schedule: %v", err)
|
||||
slog.Error("Failed to get room schedule:", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get room schedule")
|
||||
}
|
||||
return c.JSON(http.StatusOK, roomSchedule)
|
||||
@@ -206,17 +225,17 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
Handler: func(c echo.Context) error {
|
||||
from, err := time.ParseTime(c.QueryParam("from"))
|
||||
if err != nil {
|
||||
slog.Error("Failed to parse time: %v", err)
|
||||
slog.Error("Failed to parse time: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||
}
|
||||
to, err := time.ParseTime(c.QueryParam("to"))
|
||||
if err != nil {
|
||||
slog.Error("Failed to parse time: %v", err)
|
||||
slog.Error("Failed to parse time: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||
}
|
||||
rooms, err := room.GetFreeRooms(app, from, to)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get free rooms: %v", err)
|
||||
slog.Error("Failed to get free rooms: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get free rooms")
|
||||
}
|
||||
return c.JSON(http.StatusOK, rooms)
|
||||
@@ -238,12 +257,14 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/course/modules",
|
||||
Handler: func(c echo.Context) error {
|
||||
course := c.QueryParam("course")
|
||||
semester := c.QueryParam("semester")
|
||||
modules, err := events.GetModulesForCourseDistinct(app, course, semester)
|
||||
modules, err := events.GetModulesForCourseDistinct(
|
||||
app,
|
||||
c.QueryParam("course"),
|
||||
c.QueryParam("semester"),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to get modules for course and semester: %v", err)
|
||||
slog.Error("Failed to get modules for course and semester: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester")
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, modules)
|
||||
@@ -266,7 +287,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
Handler: func(c echo.Context) error {
|
||||
modules, err := events.GetAllModulesDistinct(app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get modules: %v", err)
|
||||
slog.Error("Failed to get modules: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get modules")
|
||||
}
|
||||
return c.JSON(http.StatusOK, modules)
|
||||
@@ -289,7 +310,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
requestModule := c.QueryParam("uuid")
|
||||
module, err := events.GetModuleByUUID(app, requestModule)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get module: %v", err)
|
||||
slog.Error("Failed to get module: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get module")
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, module)
|
||||
@@ -339,10 +360,34 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to get courses for semester with events: %v", err)
|
||||
slog.Error("Failed to get courses for semester with events: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events")
|
||||
} else {
|
||||
return c.JSON(200, courses)
|
||||
return c.JSON(http.StatusOK, courses)
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// API Endpoint to get all eventTypes from the database distinct
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/events/types",
|
||||
Handler: func(c echo.Context) error {
|
||||
eventTypes, err := events.GetEventTypes(app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to get event types", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to get event types")
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, eventTypes)
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
@@ -360,39 +405,16 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
||||
Method: http.MethodDelete,
|
||||
Path: "/api/events",
|
||||
Handler: func(c echo.Context) error {
|
||||
course := c.QueryParam("course")
|
||||
semester := c.QueryParam("semester")
|
||||
err := events.DeleteAllEventsByCourseAndSemester(app, course, semester)
|
||||
err := events.DeleteAllEventsByCourseAndSemester(
|
||||
app,
|
||||
c.QueryParam("course"),
|
||||
c.QueryParam("semester"),
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("Failed to delete events: %v", err)
|
||||
slog.Error("Failed to delete events: ", "error", err)
|
||||
return c.JSON(http.StatusBadRequest, "Failed to delete events")
|
||||
} else {
|
||||
return c.JSON(http.StatusBadRequest, "Events deleted")
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
apis.RequireAdminAuth(),
|
||||
},
|
||||
})
|
||||
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/feeds/migrate",
|
||||
Handler: func(c echo.Context) error {
|
||||
err := ical.MigrateFeedJson(app)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to migrate feeds: %v", err)
|
||||
return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds")
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, "Migrated")
|
||||
return c.JSON(http.StatusOK, "Events deleted")
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
"htwkalender/service/course"
|
||||
"htwkalender/service/feed"
|
||||
"htwkalender/service/fetch/sport"
|
||||
v1 "htwkalender/service/fetch/v1"
|
||||
v2 "htwkalender/service/fetch/v2"
|
||||
"htwkalender/service/functions/time"
|
||||
"log/slog"
|
||||
@@ -34,10 +35,21 @@ func AddSchedules(app *pocketbase.PocketBase) {
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
scheduler := cron.New()
|
||||
|
||||
// Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *"
|
||||
// Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *"
|
||||
// Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *"
|
||||
scheduler.MustAdd("updateCourse", "0 */3 * * *", func() {
|
||||
// !! IMPORTANT !! CRON is based on UTC time zone so in Germany it is UTC+2 in summer and UTC+1 in winter
|
||||
|
||||
// Every sunday at 10pm update all courses (5 segments - minute, hour, day, month, weekday) "0 22 * * 0"
|
||||
scheduler.MustAdd("updateCourses", "0 22 * * 0", func() {
|
||||
slog.Info("Started updating courses schedule")
|
||||
groups, err := v1.FetchSeminarGroups(app)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to fetch seminar groups: ", "error", err)
|
||||
}
|
||||
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(groups)), 10) + " seminar groups")
|
||||
})
|
||||
|
||||
// Every day at 5am and 5pm update all courses (5 segments - minute, hour, day, month, weekday) "0 5,17 * * *"
|
||||
// In Germany it is 7am and 7pm, syllabus gets updated twice a day at German 5:00 Uhr and 17:00 Uhr
|
||||
scheduler.MustAdd("updateEventsByCourse", "0 5,17 * * *", func() {
|
||||
slog.Info("Started updating courses schedule")
|
||||
course.UpdateCourse(app)
|
||||
})
|
||||
@@ -54,16 +66,16 @@ func AddSchedules(app *pocketbase.PocketBase) {
|
||||
slog.Info("Started fetching sport events schedule")
|
||||
sportEvents, err := sport.FetchAndUpdateSportEvents(app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch and save sport events: %v", err)
|
||||
slog.Error("Failed to fetch and save sport events:", "error", err)
|
||||
}
|
||||
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(sportEvents)), 10) + " sport events")
|
||||
})
|
||||
|
||||
//fetch all events for semester and delete from remote this should be done every sunday at 2am
|
||||
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
|
||||
scheduler.MustAdd("fetchEvents", "0 22 * * 6", func() {
|
||||
savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{})
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch and save events: %v", err)
|
||||
slog.Error("Failed to fetch and save events: ", "error", err)
|
||||
} else {
|
||||
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
|
||||
}
|
||||
|
@@ -20,17 +20,14 @@ import (
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"htwkalender/service/events"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func UpdateCourse(app *pocketbase.PocketBase) {
|
||||
courses := events.GetAllCourses(app)
|
||||
for _, course := range courses {
|
||||
savedEvents, err := events.UpdateModulesForCourse(app, course)
|
||||
_, err := events.UpdateModulesForCourse(app, course)
|
||||
if err != nil {
|
||||
slog.Warn("Update Course: "+course+" failed: %v", err)
|
||||
} else {
|
||||
slog.Info("Updated Course: " + course + " with " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
|
||||
slog.Warn("Update Course: "+course+" failed:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ func GetDateFromWeekNumber(year int, weekNumber int, dayName string) (time.Time,
|
||||
europeTime, err := time.LoadLocation("Europe/Berlin")
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed to load location: ", err)
|
||||
slog.Error("Failed to load location: ", "error", err)
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
|
@@ -27,24 +27,22 @@ import (
|
||||
"github.com/pocketbase/pocketbase"
|
||||
)
|
||||
|
||||
func SaveSeminarGroupEvents(seminarGroups []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) {
|
||||
func SaveSeminarGroupEvents(seminarGroup model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) {
|
||||
var toBeSavedEvents model.Events
|
||||
var savedRecords model.Events
|
||||
|
||||
// check if event is already in database and add to toBeSavedEvents if not
|
||||
for _, seminarGroup := range seminarGroups {
|
||||
for _, event := range seminarGroup.Events {
|
||||
event = event.SetCourse(seminarGroup.Course)
|
||||
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
||||
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
||||
for _, event := range seminarGroup.Events {
|
||||
event = event.SetCourse(seminarGroup.Course)
|
||||
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
||||
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existsInDatabase && !alreadyAddedToSave {
|
||||
toBeSavedEvents = append(toBeSavedEvents, event)
|
||||
}
|
||||
if !existsInDatabase && !alreadyAddedToSave {
|
||||
toBeSavedEvents = append(toBeSavedEvents, event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +192,26 @@ func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.Feed
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func GetAllEventsForCourse(app *pocketbase.PocketBase, course string) (model.Events, error) {
|
||||
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}", dbx.Params{"course": course})).All(&events)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting events from database: ", "error", err)
|
||||
return nil, fmt.Errorf("error while getting events from database for course %s", course)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
|
||||
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})).GroupBy("Name").Distinct(true).All(&events)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting events from database: ", err)
|
||||
slog.Error("Error while getting events from database: ", "error", err)
|
||||
return nil, fmt.Errorf("error while getting events from database for course %s and semester %s", course, semester)
|
||||
}
|
||||
|
||||
@@ -212,7 +223,7 @@ func GetAllModulesDistinctByNameAndCourse(app *pocketbase.PocketBase) ([]model.M
|
||||
|
||||
err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting events from database: ", err)
|
||||
slog.Error("Error while getting events from database: ", "error", err)
|
||||
return nil, fmt.Errorf("error while getting events distinct by name and course from data")
|
||||
}
|
||||
|
||||
@@ -356,6 +367,17 @@ func GetEventsThatStartBeforeAndEndBefore(app *pocketbase.PocketBase, from types
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func GetAllEventTypes(app *pocketbase.PocketBase) ([]model.EventType, error) {
|
||||
var eventTypes []model.EventType
|
||||
|
||||
err := app.Dao().DB().Select("EventType").From("events").GroupBy("EventType").Distinct(true).All(&eventTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eventTypes, nil
|
||||
}
|
||||
|
||||
func GetEventsThatStartAfterAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||
var events model.Events
|
||||
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End >= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
||||
@@ -366,3 +388,13 @@ func GetEventsThatStartAfterAndEndAfter(app *pocketbase.PocketBase, from types.D
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func DeleteEvents(list model.Events, app *pocketbase.PocketBase) error {
|
||||
for _, event := range list {
|
||||
err := app.Dao().Delete(&event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ func GetAllCourses(app *pocketbase.PocketBase) []string {
|
||||
// get all rooms from event records in the events collection
|
||||
err := app.Dao().DB().Select("course").From("groups").All(&courses)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting groups from database: ", err)
|
||||
slog.Error("Error while getting groups from database: ", "error", err)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []str
|
||||
// get all courses for a specific semester
|
||||
err := app.Dao().DB().Select("course").From("groups").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).All(&courses)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting groups from database: ", err)
|
||||
slog.Error("Error while getting groups from database: ", "error", err)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester str
|
||||
// get all courses from events distinct for a specific semester
|
||||
err := app.Dao().DB().Select("course").From("events").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).Distinct(true).All(&courses)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting groups from database: ", err)
|
||||
slog.Error("Error while getting groups from database: ", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@@ -22,6 +22,8 @@ import (
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/fetch/v1"
|
||||
"htwkalender/service/functions"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
|
||||
@@ -120,15 +122,11 @@ func DeleteAllEvents(app *pocketbase.PocketBase) error {
|
||||
// If the update was not successful, an error is returned
|
||||
func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) (model.Events, error) {
|
||||
|
||||
//new string array with one element (course)
|
||||
var courses []string
|
||||
courses = append(courses, course)
|
||||
seminarGroup := v1.GetSeminarGroupEventsFromHTML(course)
|
||||
|
||||
seminarGroups := v1.GetSeminarGroupsEventsFromHTML(courses)
|
||||
seminarGroup = v1.ClearEmptySeminarGroups(seminarGroup)
|
||||
|
||||
seminarGroups = v1.ClearEmptySeminarGroups(seminarGroups)
|
||||
|
||||
seminarGroups = v1.ReplaceEmptyEventNames(seminarGroups)
|
||||
seminarGroup = v1.ReplaceEmptyEventNames(seminarGroup)
|
||||
|
||||
//check if events in the seminarGroups Events are already in the database
|
||||
//if yes, keep the database as it is
|
||||
@@ -136,58 +134,53 @@ func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) (model.Ev
|
||||
//if there are no events in the database, save the new events
|
||||
|
||||
//get all events for the course and the semester
|
||||
events, err := db.GetAllModulesForCourse(app, course, "ws")
|
||||
dbEvents, err := db.GetAllEventsForCourse(app, course)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// append all events for the course and the semester to the events array for ss
|
||||
summerEvents, err := db.GetAllModulesForCourse(app, course, "ss")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events = append(events, summerEvents...)
|
||||
|
||||
//if there are no events in the database, save the new events
|
||||
if len(events) == 0 {
|
||||
events, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
|
||||
if len(dbEvents) == 0 {
|
||||
events, dbError := db.SaveSeminarGroupEvents(seminarGroup, app)
|
||||
if dbError != nil {
|
||||
return nil, dbError
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
//check if events in the seminarGroups Events are already in the database
|
||||
//if yes, keep the database as it is
|
||||
//if no, delete all events for the course and the semester and save the new events
|
||||
// Create partial update list and delete list for the events
|
||||
var insertList model.Events
|
||||
var deleteList model.Events
|
||||
|
||||
var savedEvents model.Events
|
||||
|
||||
for _, seminarGroup := range seminarGroups {
|
||||
for _, event := range seminarGroup.Events {
|
||||
// if the event is not in the database, delete all events for the course and the semester and save the new events
|
||||
if !ContainsEvent(events, event) {
|
||||
|
||||
err = DeleteAllEventsByCourseAndSemester(app, course, "ws")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = DeleteAllEventsByCourseAndSemester(app, course, "ss")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//save the new events
|
||||
savedEvent, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
|
||||
if dbError != nil {
|
||||
return nil, dbError
|
||||
}
|
||||
savedEvents = append(savedEvents, savedEvent...)
|
||||
}
|
||||
// check which events are not already in the database and need to be inserted/saved
|
||||
for _, event := range seminarGroup.Events {
|
||||
if !ContainsEvent(dbEvents, event) {
|
||||
insertList = append(insertList, event)
|
||||
}
|
||||
}
|
||||
|
||||
// check which events are in the database but not in the seminarGroup and need to be deleted
|
||||
for _, dbEvent := range dbEvents {
|
||||
if !ContainsEvent(seminarGroup.Events, dbEvent) {
|
||||
deleteList = append(deleteList, dbEvent)
|
||||
}
|
||||
}
|
||||
|
||||
// delete all events that are in the deleteList
|
||||
err = db.DeleteEvents(deleteList, app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to delete events:", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// save all events that are in the insertList
|
||||
savedEvents, err := db.SaveEvents(insertList, app)
|
||||
if err != nil {
|
||||
slog.Error("Failed to save events: ", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Info("Course: " + course + " - Event changes: " + strconv.FormatInt(int64(len(insertList)), 10) + " new events, " + strconv.FormatInt(int64(len(deleteList)), 10) + " deleted events")
|
||||
return savedEvents, nil
|
||||
}
|
||||
|
||||
@@ -205,3 +198,18 @@ func ContainsEvent(events model.Events, event model.Event) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetEventTypes(app *pocketbase.PocketBase) ([]string, error) {
|
||||
dbEventTypes, err := db.GetAllEventTypes(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert the []model.EventType to []string
|
||||
var eventTypes []string
|
||||
for _, eventType := range dbEventTypes {
|
||||
eventTypes = append(eventTypes, eventType.EventType)
|
||||
}
|
||||
|
||||
return eventTypes, nil
|
||||
}
|
||||
|
58
backend/service/events/eventService_test.go
Normal file
58
backend/service/events/eventService_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"htwkalender/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContainsEvent(t *testing.T) {
|
||||
type args struct {
|
||||
events model.Events
|
||||
event model.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "contains event",
|
||||
args: args{
|
||||
events: model.Events{
|
||||
{
|
||||
UUID: "934807509832475",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
event: model.Event{
|
||||
UUID: "934807509832475",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "contains no event",
|
||||
args: args{
|
||||
events: model.Events{
|
||||
{
|
||||
UUID: "9991929292921912343534",
|
||||
Name: "Name1",
|
||||
},
|
||||
},
|
||||
event: model.Event{
|
||||
UUID: "1111112312312312",
|
||||
Name: "Name2",
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ContainsEvent(tt.args.events, tt.args.event); got != tt.want {
|
||||
t.Errorf("ContainsEvent() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -30,7 +30,7 @@ import (
|
||||
func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
|
||||
feeds, err := database.GetAllFeeds(db)
|
||||
if err != nil {
|
||||
slog.Error("CleanFeeds: failed to get all feeds", err)
|
||||
slog.Error("CleanFeeds: failed to get all feeds", "error", err)
|
||||
return
|
||||
}
|
||||
for _, feed := range feeds {
|
||||
@@ -44,8 +44,8 @@ func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
|
||||
var sqlResult sql.Result
|
||||
sqlResult, err = db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute()
|
||||
if err != nil {
|
||||
slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", err)
|
||||
slog.Error("SQL Result: ", sqlResult)
|
||||
slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", "error", err)
|
||||
slog.Error("SQL Result: ", "error", sqlResult)
|
||||
} else {
|
||||
slog.Info("CleanFeeds: delete feed " + feed.GetId() + " successful")
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
"htwkalender/model"
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/functions"
|
||||
clock "htwkalender/service/functions/time"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -81,7 +82,7 @@ func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) ([]model.Event, error
|
||||
}
|
||||
|
||||
// @TODO: delete and save events in one transaction and it only should delete events that are not in the new events list and save events that are not in the database
|
||||
err = db.DeleteAllEventsByCourse(app, "Sport", functions.GetCurrentSemesterString())
|
||||
err = db.DeleteAllEventsByCourse(app, "Sport", functions.GetCurrentSemesterString(clock.RealClock{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -208,7 +209,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
||||
for _, day := range days {
|
||||
weekDay, err := getDayInt(day)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting day int: "+day+" ", err)
|
||||
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||
} else {
|
||||
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
||||
@@ -233,7 +234,8 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
||||
endI, endIErr = getDayInt(days[1])
|
||||
|
||||
if endIErr != nil || startIErr != nil {
|
||||
slog.Error("Error while getting day int: "+days[0]+" - "+days[1]+" :", startIErr, endIErr)
|
||||
slog.Error("StartError while getting day int: "+days[0]+" - "+days[1]+" :", "error", startIErr)
|
||||
slog.Error("EndError while getting day int: "+days[0]+" - "+days[1]+" :", "error", endIErr)
|
||||
} else {
|
||||
//create a int array with all days from start to end day
|
||||
var daysBetween []int
|
||||
@@ -258,7 +260,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
||||
|
||||
dayInt, err := getDayInt(day)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting day int: "+day+" ", err)
|
||||
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||
} else {
|
||||
dayNumbers = append(dayNumbers, dayInt)
|
||||
}
|
||||
@@ -270,7 +272,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
||||
|
||||
weekDay, err := getDayInt(day)
|
||||
if err != nil {
|
||||
slog.Error("Error while getting day int: "+day+" ", err)
|
||||
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||
} else {
|
||||
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
||||
@@ -377,7 +379,7 @@ func fetchAllAvailableSportCourses() ([]string, error) {
|
||||
var doc, err = htmlRequest(url)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Error while fetching sport courses from webpage", err)
|
||||
slog.Error("Error while fetching sport courses from webpage", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -442,7 +444,7 @@ func htmlRequest(url string) (*goquery.Document, error) {
|
||||
defer func(Body io.ReadCloser) {
|
||||
readErr := Body.Close()
|
||||
if readErr != nil {
|
||||
slog.Error("Error while closing response body from html request", readErr)
|
||||
slog.Error("Error while closing response body from html request", "error", readErr)
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
@@ -32,50 +32,43 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func ReplaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup {
|
||||
for i, group := range groups {
|
||||
for j, event := range group.Events {
|
||||
if functions.OnlyWhitespace(event.Name) {
|
||||
groups[i].Events[j].Name = "Sonderveranstaltungen"
|
||||
}
|
||||
func ReplaceEmptyEventNames(group model.SeminarGroup) model.SeminarGroup {
|
||||
for j, event := range group.Events {
|
||||
if functions.OnlyWhitespace(event.Name) {
|
||||
group.Events[j].Name = "Sonderveranstaltungen"
|
||||
}
|
||||
}
|
||||
return groups
|
||||
return group
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
func ClearEmptySeminarGroups(seminarGroup model.SeminarGroup) model.SeminarGroup {
|
||||
var newSeminarGroup = model.SeminarGroup{}
|
||||
|
||||
if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" {
|
||||
newSeminarGroup = seminarGroup
|
||||
}
|
||||
return newSeminarGroups
|
||||
return newSeminarGroup
|
||||
}
|
||||
|
||||
func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.SeminarGroup {
|
||||
var seminarGroups []model.SeminarGroup
|
||||
for _, seminarGroupLabel := range seminarGroupsLabel {
|
||||
func GetSeminarGroupEventsFromHTML(seminarGroupLabel string) model.SeminarGroup {
|
||||
var seminarGroup model.SeminarGroup
|
||||
|
||||
if (time.Now().Month() >= 3) && (time.Now().Month() <= 10) {
|
||||
ssUrl := "https://stundenplan.htwk-leipzig.de/" + string("ss") + "/Berichte/Text-Listen;Studenten-Sets;name;" + seminarGroupLabel + "?template=sws_semgrp&weeks=1-65"
|
||||
result, getError := fetch.GetHTML(ssUrl)
|
||||
if getError == nil {
|
||||
seminarGroup := parseSeminarGroup(result)
|
||||
seminarGroups = append(seminarGroups, seminarGroup)
|
||||
}
|
||||
}
|
||||
|
||||
if (time.Now().Month() >= 9) || (time.Now().Month() <= 4) {
|
||||
wsUrl := "https://stundenplan.htwk-leipzig.de/" + string("ws") + "/Berichte/Text-Listen;Studenten-Sets;name;" + seminarGroupLabel + "?template=sws_semgrp&weeks=1-65"
|
||||
result, getError := fetch.GetHTML(wsUrl)
|
||||
if getError == nil {
|
||||
seminarGroup := parseSeminarGroup(result)
|
||||
seminarGroups = append(seminarGroups, seminarGroup)
|
||||
}
|
||||
if (time.Now().Month() >= 3) && (time.Now().Month() <= 10) {
|
||||
ssUrl := "https://stundenplan.htwk-leipzig.de/" + string("ss") + "/Berichte/Text-Listen;Studenten-Sets;name;" + seminarGroupLabel + "?template=sws_semgrp&weeks=1-65"
|
||||
result, getError := fetch.GetHTML(ssUrl)
|
||||
if getError == nil {
|
||||
seminarGroup = parseSeminarGroup(result)
|
||||
}
|
||||
}
|
||||
return seminarGroups
|
||||
|
||||
if (time.Now().Month() >= 9) || (time.Now().Month() <= 4) {
|
||||
wsUrl := "https://stundenplan.htwk-leipzig.de/" + string("ws") + "/Berichte/Text-Listen;Studenten-Sets;name;" + seminarGroupLabel + "?template=sws_semgrp&weeks=1-65"
|
||||
result, getError := fetch.GetHTML(wsUrl)
|
||||
if getError == nil {
|
||||
seminarGroup = parseSeminarGroup(result)
|
||||
}
|
||||
}
|
||||
return seminarGroup
|
||||
}
|
||||
|
||||
func SplitEventType(events []model.Event) ([]model.Event, error) {
|
||||
@@ -110,19 +103,18 @@ func parseSeminarGroup(result string) model.SeminarGroup {
|
||||
if eventTables == nil || allDayLabels == nil {
|
||||
return model.SeminarGroup{}
|
||||
}
|
||||
|
||||
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
|
||||
course := findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data
|
||||
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels, course)
|
||||
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
||||
events := splitEventsBySingleWeek(splitEventsByWeekVal)
|
||||
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
|
||||
course := findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data
|
||||
semester, year := extractSemesterAndYear(semesterString)
|
||||
events = convertWeeksToDates(events, semester, year)
|
||||
events = generateUUIDs(events, course)
|
||||
events, err = SplitEventType(events)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Error occurred while splitting event types: %s", err)
|
||||
slog.Error("Error occurred while splitting event types:", "error", err)
|
||||
return model.SeminarGroup{}
|
||||
}
|
||||
|
||||
@@ -211,7 +203,7 @@ func extractSemesterAndYear(semesterString string) (string, string) {
|
||||
return semesterShortcut, year
|
||||
}
|
||||
|
||||
func toEvents(tables [][]*html.Node, days []string) []model.Event {
|
||||
func toEvents(tables [][]*html.Node, days []string, course string) []model.Event {
|
||||
var events []model.Event
|
||||
|
||||
for table := range tables {
|
||||
@@ -232,6 +224,7 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event {
|
||||
Rooms: getTextContent(tableData[6]),
|
||||
Notes: getTextContent(tableData[7]),
|
||||
BookedAt: getTextContent(tableData[8]),
|
||||
Course: course,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -75,28 +75,17 @@ func Test_extractSemesterAndYear(t *testing.T) {
|
||||
|
||||
func Test_replaceEmptyEventNames(t *testing.T) {
|
||||
type args struct {
|
||||
groups []model.SeminarGroup
|
||||
group model.SeminarGroup
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []model.SeminarGroup
|
||||
want model.SeminarGroup
|
||||
}{
|
||||
{
|
||||
name: "Test 1",
|
||||
args: args{
|
||||
groups: []model.SeminarGroup{
|
||||
{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "Test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []model.SeminarGroup{
|
||||
{
|
||||
group: model.SeminarGroup{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "Test",
|
||||
@@ -104,26 +93,29 @@ func Test_replaceEmptyEventNames(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
want: model.SeminarGroup{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "Test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test 1",
|
||||
args: args{
|
||||
groups: []model.SeminarGroup{
|
||||
{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "",
|
||||
},
|
||||
group: model.SeminarGroup{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []model.SeminarGroup{
|
||||
{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "Sonderveranstaltungen",
|
||||
},
|
||||
want: model.SeminarGroup{
|
||||
Events: []model.Event{
|
||||
{
|
||||
Name: "Sonderveranstaltungen",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -131,7 +123,7 @@ func Test_replaceEmptyEventNames(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ReplaceEmptyEventNames(tt.args.groups); !reflect.DeepEqual(got, tt.want) {
|
||||
if got := ReplaceEmptyEventNames(tt.args.group); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ReplaceEmptyEventNames() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"htwkalender/model"
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/functions"
|
||||
"htwkalender/service/functions/time"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -59,36 +61,32 @@ func getSeminarHTML(semester string) (string, error) {
|
||||
func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) {
|
||||
var groups []model.SeminarGroup
|
||||
|
||||
resultSummer, err := getSeminarHTML("ss")
|
||||
semesterString := functions.CalculateSemesterList(time.RealClock{})
|
||||
var results [2]string
|
||||
var err error
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Error while fetching seminar groups for winter semester", err)
|
||||
return nil, err
|
||||
for i, semester := range semesterString {
|
||||
results[i], err = getSeminarHTML(semester)
|
||||
if err != nil {
|
||||
slog.Error("Error while fetching seminar groups for: "+semester, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
groups = append(groups, parseSeminarGroups(results[i], semester)...)
|
||||
}
|
||||
|
||||
resultWinter, _ := getSeminarHTML("ws")
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Error while fetching seminar groups for summer semester", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups = parseSeminarGroups(resultSummer, "ss")
|
||||
groups = append(groups, parseSeminarGroups(resultWinter, "ws")...)
|
||||
|
||||
// filter duplicates
|
||||
groups = removeDuplicates(groups)
|
||||
|
||||
collection, dbError := db.FindCollection(app, "groups")
|
||||
if dbError != nil {
|
||||
slog.Error("Error while searching collection groups", dbError)
|
||||
slog.Error("Error while searching collection groups", "error", dbError)
|
||||
return nil, err
|
||||
}
|
||||
var insertedGroups []*models.Record
|
||||
|
||||
insertedGroups, dbError = db.SaveGroups(groups, collection, app)
|
||||
if dbError != nil {
|
||||
slog.Error("Error while saving groups", dbError)
|
||||
slog.Error("Error while saving groups", "error", dbError)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@@ -25,10 +25,10 @@ import (
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/fetch"
|
||||
v1 "htwkalender/service/fetch/v1"
|
||||
"htwkalender/service/functions"
|
||||
localTime "htwkalender/service/functions/time"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ParseEventsFromRemote(app *pocketbase.PocketBase) (model.Events, error) {
|
||||
@@ -70,7 +70,7 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([
|
||||
}
|
||||
|
||||
// Fetch and save events for all semesters
|
||||
for _, semester := range calculateSemesterList(clock) {
|
||||
for _, semester := range functions.CalculateSemesterList(clock) {
|
||||
events, fetchErr := fetchAndSaveAllEventsForSemester(app, semester, stubUrl)
|
||||
if fetchErr != nil {
|
||||
return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", err)
|
||||
@@ -104,25 +104,6 @@ func fetchAndSaveAllEventsForSemester(
|
||||
return savedRecords, err
|
||||
}
|
||||
|
||||
func calculateSemesterList(clock localTime.Clock) []string {
|
||||
summerSemester := clock.Now().Month() >= time.March && clock.Now().Month() <= time.September
|
||||
winterSemester := clock.Now().Month() <= time.March || clock.Now().Month() >= time.September
|
||||
|
||||
if summerSemester && winterSemester {
|
||||
return []string{"ss", "ws"}
|
||||
}
|
||||
|
||||
if summerSemester {
|
||||
return []string{"ss"}
|
||||
}
|
||||
|
||||
if winterSemester {
|
||||
return []string{"ws"}
|
||||
}
|
||||
|
||||
return []string{"ss", "ws"}
|
||||
}
|
||||
|
||||
func parseEventForOneSemester(url string) ([]model.Event, error) {
|
||||
// Fetch Webpage from URL
|
||||
webpage, err := fetch.GetHTML(url)
|
||||
@@ -160,7 +141,7 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
|
||||
events = convertWeeksToDates(events, semester, year)
|
||||
events, err = v1.SplitEventType(events)
|
||||
if err != nil {
|
||||
slog.Error("Error occurred while splitting event types: %s", err)
|
||||
slog.Error("Error occurred while splitting event types: ", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
events = switchNameAndNotesForExam(events)
|
||||
|
@@ -18,10 +18,8 @@ package v2
|
||||
|
||||
import (
|
||||
"htwkalender/model"
|
||||
mockTime "htwkalender/service/functions/time"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_switchNameAndNotesForExam(t *testing.T) {
|
||||
@@ -99,49 +97,3 @@ func Test_switchNameAndNotesForExam(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_calculateSemesterList(t *testing.T) {
|
||||
type args struct {
|
||||
clock mockTime.Clock
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "is summer semester",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ss"},
|
||||
},
|
||||
{
|
||||
name: "is winter semester",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ws"},
|
||||
},
|
||||
{
|
||||
name: "is in both",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 3, 22, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ss", "ws"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := calculateSemesterList(tt.args.clock); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("calculateSemesterList() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -16,15 +16,32 @@
|
||||
|
||||
package functions
|
||||
|
||||
import "time"
|
||||
import (
|
||||
localTime "htwkalender/service/functions/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetCurrentSemesterString returns the current semester as string
|
||||
// if current month is between 10 and 03 -> winter semester "ws"
|
||||
func GetCurrentSemesterString() string {
|
||||
|
||||
if time.Now().Month() >= 10 || time.Now().Month() <= 3 {
|
||||
func GetCurrentSemesterString(localeTime localTime.Clock) string {
|
||||
if localeTime.Now().Month() >= 10 || localeTime.Now().Month() <= 3 {
|
||||
return "ws"
|
||||
} else {
|
||||
return "ss"
|
||||
}
|
||||
}
|
||||
|
||||
func CalculateSemesterList(clock localTime.Clock) []string {
|
||||
summerSemester := clock.Now().Month() >= time.March && clock.Now().Month() <= time.September
|
||||
winterSemester := clock.Now().Month() <= time.March || clock.Now().Month() >= time.September
|
||||
|
||||
if summerSemester && !winterSemester {
|
||||
return []string{"ss"}
|
||||
}
|
||||
|
||||
if !summerSemester && winterSemester {
|
||||
return []string{"ws"}
|
||||
}
|
||||
|
||||
return []string{"ss", "ws"}
|
||||
}
|
||||
|
91
backend/service/functions/semester_test.go
Normal file
91
backend/service/functions/semester_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
mockTime "htwkalender/service/functions/time"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_calculateSemesterList(t *testing.T) {
|
||||
type args struct {
|
||||
clock mockTime.Clock
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "is summer semester",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ss"},
|
||||
},
|
||||
{
|
||||
name: "is winter semester",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ws"},
|
||||
},
|
||||
{
|
||||
name: "is in both",
|
||||
args: args{
|
||||
clock: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 3, 22, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: []string{"ss", "ws"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CalculateSemesterList(tt.args.clock); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("calculateSemesterList() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentSemesterString(t *testing.T) {
|
||||
type args struct {
|
||||
localeTime mockTime.Clock
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "is winter semester",
|
||||
args: args{
|
||||
localeTime: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: "ws",
|
||||
},
|
||||
{
|
||||
name: "is summer semester",
|
||||
args: args{
|
||||
localeTime: mockTime.MockClock{
|
||||
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
want: "ss",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetCurrentSemesterString(tt.args.localeTime); got != tt.want {
|
||||
t.Errorf("GetCurrentSemesterString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -48,13 +48,6 @@ func Contains(s []string, e string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ReplaceEmptyString(word string, replacement string) string {
|
||||
if OnlyWhitespace(word) {
|
||||
return replacement
|
||||
}
|
||||
return word
|
||||
}
|
||||
|
||||
func HashString(s string) string {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(s))
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -96,3 +97,49 @@ func TestIsSeparator(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
type args struct {
|
||||
s []string
|
||||
e string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"empty slice", args{[]string{}, "a"}, false},
|
||||
{"slice with one element equal", args{[]string{"a"}, "a"}, true},
|
||||
{"slice with one element different", args{[]string{"a"}, "b"}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Contains(tt.args.s, tt.args.e); got != tt.want {
|
||||
t.Errorf("Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeperateRoomString(t *testing.T) {
|
||||
type args struct {
|
||||
rooms string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{"empty string", args{""}, []string{}},
|
||||
{"one room", args{"a"}, []string{"a"}},
|
||||
{"two rooms", args{"a,b"}, []string{"a", "b"}},
|
||||
{"two rooms with whitespace", args{"a, b"}, []string{"a", "b"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := SeperateRoomString(tt.args.rooms); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SeperateRoomString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ func ParseTime(timeString string) (time.Time, error) {
|
||||
func ParseAsTypesDatetime(time time.Time) types.DateTime {
|
||||
dateTime, err := types.ParseDateTime(time)
|
||||
if err != nil {
|
||||
slog.Error("Failed to parse time as types.DateTime: %v", err)
|
||||
slog.Error("Failed to parse time as types.DateTime", "error", err)
|
||||
return types.DateTime{}
|
||||
}
|
||||
return dateTime
|
||||
|
@@ -1,74 +0,0 @@
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package ical
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"htwkalender/model"
|
||||
)
|
||||
|
||||
//update ical feed json
|
||||
//add uuid field
|
||||
//remove module name field
|
||||
|
||||
func MigrateFeedJson(app *pocketbase.PocketBase) error {
|
||||
|
||||
records, err := app.Dao().FindRecordsByFilter("feeds", "1=1", "-created", 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, feed := range records {
|
||||
|
||||
var modules []model.FeedCollection
|
||||
|
||||
err := json.Unmarshal([]byte(feed.GetString("modules")), &modules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var uuidFeedCollections []model.FeedCollection
|
||||
|
||||
for _, module := range modules {
|
||||
uuid := searchUUIDForModule(app, module)
|
||||
|
||||
if uuid != "" {
|
||||
uuidFeedCollections = append(uuidFeedCollections, model.FeedCollection{UUID: uuid, Name: module.Name, Course: module.Course, UserDefinedName: module.UserDefinedName})
|
||||
}
|
||||
}
|
||||
|
||||
jsonModules, _ := json.Marshal(uuidFeedCollections)
|
||||
feed.Set("modules", string(jsonModules))
|
||||
|
||||
err = app.Dao().SaveRecord(feed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchUUIDForModule(app *pocketbase.PocketBase, module model.FeedCollection) string {
|
||||
var event model.Event
|
||||
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND course = {:course}", dbx.Params{"name": module.Name, "course": module.Course})).One(&event)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return event.UUID
|
||||
}
|
@@ -74,6 +74,7 @@ func GetFreeRooms(app *pocketbase.PocketBase, from time.Time, to time.Time) ([]s
|
||||
return freeRooms, nil
|
||||
}
|
||||
|
||||
// Remove all rooms from the list that have events in the given time range
|
||||
func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string {
|
||||
var freeRooms []string
|
||||
for _, room := range rooms {
|
||||
@@ -84,6 +85,7 @@ func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string
|
||||
return freeRooms
|
||||
}
|
||||
|
||||
// Check if a room is in the schedule
|
||||
func isRoomInSchedule(room string, schedule []model.Event) bool {
|
||||
for _, event := range schedule {
|
||||
if event.Course != "Sport" {
|
||||
|
@@ -191,6 +191,50 @@ func Test_isRoomInSchedule(t *testing.T) {
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "schedule event.Course is sport",
|
||||
args: args{
|
||||
room: "Klettergerüst",
|
||||
schedule: []model.Event{
|
||||
{
|
||||
UUID: "903784265784639527",
|
||||
Day: "Montag",
|
||||
Week: "52",
|
||||
Start: types.DateTime{},
|
||||
End: types.DateTime{},
|
||||
Name: "Hampelmann",
|
||||
EventType: "S",
|
||||
Prof: "Prof. Dr. Bewegung",
|
||||
Rooms: "Klettergerüst",
|
||||
Notes: "A apple a day keeps the doctor away",
|
||||
Course: "Sport",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "schedule event.Course is sport with different room",
|
||||
args: args{
|
||||
room: "HTWK Sportplatz",
|
||||
schedule: []model.Event{
|
||||
{
|
||||
UUID: "903784265784639527",
|
||||
Day: "Montag",
|
||||
Week: "52",
|
||||
Start: types.DateTime{},
|
||||
End: types.DateTime{},
|
||||
Name: "Hampelmann",
|
||||
EventType: "S",
|
||||
Prof: "Prof. Dr. Bewegung",
|
||||
Rooms: "Klettergerüst",
|
||||
Notes: "A apple a day keeps the doctor away",
|
||||
Course: "Sport",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@@ -23,8 +23,8 @@ services:
|
||||
context: ./backend
|
||||
target: dev # prod
|
||||
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
|
||||
#ports:
|
||||
# - "8090:8090"
|
||||
ports:
|
||||
- "8090:8090"
|
||||
volumes:
|
||||
- pb_data:/htwkalender/data # for production with volume
|
||||
# - ./backend:/htwkalender/data # for development with bind mount from project directory
|
||||
|
33
frontend/src/api/fetchEvents.ts
Normal file
33
frontend/src/api/fetchEvents.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
||||
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
||||
|
||||
//This program is free software: you can redistribute it and/or modify
|
||||
//it under the terms of the GNU Affero General Public License as published by
|
||||
//the Free Software Foundation, either version 3 of the License, or
|
||||
//(at your option) any later version.
|
||||
|
||||
//This program is distributed in the hope that it will be useful,
|
||||
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//GNU Affero General Public License for more details.
|
||||
|
||||
//You should have received a copy of the GNU Affero General Public License
|
||||
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// function to fetch course data from the API
|
||||
|
||||
export async function fetchEventTypes(): Promise<string[]> {
|
||||
const eventTypes: string[] = [];
|
||||
await fetch("/api/events/types")
|
||||
.then((response) => {
|
||||
return response.json() as Promise<string[]>;
|
||||
})
|
||||
.then((responseModules: string[]) => {
|
||||
responseModules.forEach((eventType: string) => {
|
||||
eventTypes.push(
|
||||
eventType,
|
||||
);
|
||||
});
|
||||
});
|
||||
return eventTypes;
|
||||
}
|
@@ -30,6 +30,7 @@ import { useDialog } from "primevue/usedialog";
|
||||
import router from "../router";
|
||||
import { fetchModule } from "../api/fetchModule.ts";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { fetchEventTypes } from "../api/fetchEvents.ts";
|
||||
|
||||
const dialog = useDialog();
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
@@ -39,6 +40,9 @@ if (store.isEmpty()) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
const eventTypes: Ref<string[]> = ref([]);
|
||||
|
||||
|
||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||
const filters = ref({
|
||||
course: {
|
||||
@@ -51,7 +55,7 @@ const filters = ref({
|
||||
},
|
||||
eventType: {
|
||||
value: null,
|
||||
matchMode: FilterMatchMode.CONTAINS,
|
||||
matchMode: FilterMatchMode.IN,
|
||||
},
|
||||
prof: {
|
||||
value: null,
|
||||
@@ -63,7 +67,7 @@ const loadedModules: Ref<Module[]> = ref(new Array(10));
|
||||
|
||||
const loadingData = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
onMounted( () => {
|
||||
fetchAllModules()
|
||||
.then(
|
||||
(data) =>
|
||||
@@ -74,6 +78,10 @@ onMounted(() => {
|
||||
.finally(() => {
|
||||
loadingData.value = false;
|
||||
});
|
||||
|
||||
fetchEventTypes().then((data) => {
|
||||
eventTypes.value = data;
|
||||
});
|
||||
});
|
||||
|
||||
const ModuleInformation = defineAsyncComponent(
|
||||
@@ -184,16 +192,20 @@ function unselectModule(event: DataTableRowUnselectEvent) {
|
||||
</Column>
|
||||
<Column
|
||||
field="eventType"
|
||||
filter-field="eventType"
|
||||
:filter-menu-style="{ width: '10rem' }"
|
||||
style="min-width: 10rem"
|
||||
:header="$t('additionalModules.eventType')"
|
||||
:show-clear-button="false"
|
||||
:show-filter-menu="false"
|
||||
>
|
||||
<template #filter="{ filterModel, filterCallback }">
|
||||
<InputText
|
||||
<MultiSelect
|
||||
v-model="filterModel.value"
|
||||
type="text"
|
||||
:options="eventTypes"
|
||||
class="p-column-filter max-w-10rem"
|
||||
@input="filterCallback()"
|
||||
style="min-width: 10rem"
|
||||
@change="filterCallback()"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="loadingData" #body>
|
||||
|
@@ -196,6 +196,24 @@ http {
|
||||
limit_req zone=modules burst=5 nodelay;
|
||||
}
|
||||
|
||||
location /api/events/types {
|
||||
proxy_pass http://htwkalender-backend:8090;
|
||||
client_max_body_size 20m;
|
||||
proxy_connect_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
send_timeout 600s;
|
||||
proxy_cache_bypass 0;
|
||||
proxy_no_cache 0;
|
||||
proxy_cache mcache; # mcache=RAM
|
||||
proxy_cache_valid 200 301 302 10m;
|
||||
proxy_cache_valid 403 404 5m;
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_use_stale timeout updating;
|
||||
add_header X-Proxy-Cache $upstream_cache_status;
|
||||
limit_req zone=modules burst=10 nodelay;
|
||||
}
|
||||
|
||||
location /api/rooms {
|
||||
proxy_pass http://htwkalender-backend:8090;
|
||||
client_max_body_size 20m;
|
||||
|
Reference in New Issue
Block a user