mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-08-09 21:27:45 +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)
|
service.AddSchedules(app)
|
||||||
|
|
||||||
if err := app.Start(); err != nil {
|
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
|
models.BaseModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventType struct {
|
||||||
|
EventType string `db:"EventType" json:"eventType"`
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Event) Equals(event Event) bool {
|
func (e *Event) Equals(event Event) bool {
|
||||||
return e.Day == event.Day &&
|
return e.Day == event.Day &&
|
||||||
e.Week == event.Week &&
|
e.Week == event.Week &&
|
||||||
@@ -79,7 +83,7 @@ func (e *Event) SetCourse(course string) Event {
|
|||||||
return *e
|
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 {
|
func (e *Event) AnonymizeEvent() AnonymizedEventDTO {
|
||||||
return AnonymizedEventDTO{
|
return AnonymizedEventDTO{
|
||||||
Day: e.Day,
|
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
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"htwkalender/service/course"
|
||||||
"htwkalender/service/events"
|
"htwkalender/service/events"
|
||||||
"htwkalender/service/fetch/sport"
|
"htwkalender/service/fetch/sport"
|
||||||
v1 "htwkalender/service/fetch/v1"
|
v1 "htwkalender/service/fetch/v1"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
"htwkalender/service/functions/time"
|
"htwkalender/service/functions/time"
|
||||||
"htwkalender/service/ical"
|
|
||||||
"htwkalender/service/room"
|
"htwkalender/service/room"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -42,7 +42,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
savedEvents, err := v2.ParseEventsFromRemote(app)
|
savedEvents, err := v2.ParseEventsFromRemote(app)
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to parse events from remote")
|
||||||
} else {
|
} else {
|
||||||
return c.JSON(http.StatusOK, savedEvents)
|
return c.JSON(http.StatusOK, savedEvents)
|
||||||
@@ -59,6 +59,25 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
return nil
|
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 {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
_, err := e.Router.AddRoute(echo.Route{
|
_, err := e.Router.AddRoute(echo.Route{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
@@ -157,7 +176,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
date := c.QueryParam("date")
|
date := c.QueryParam("date")
|
||||||
roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date)
|
roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date)
|
||||||
if err != nil {
|
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.StatusBadRequest, "Failed to get room schedule for day")
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, roomSchedule)
|
return c.JSON(http.StatusOK, roomSchedule)
|
||||||
@@ -183,7 +202,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
from := c.QueryParam("from")
|
from := c.QueryParam("from")
|
||||||
roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to)
|
roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to)
|
||||||
if err != nil {
|
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.StatusBadRequest, "Failed to get room schedule")
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, roomSchedule)
|
return c.JSON(http.StatusOK, roomSchedule)
|
||||||
@@ -206,17 +225,17 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
from, err := time.ParseTime(c.QueryParam("from"))
|
from, err := time.ParseTime(c.QueryParam("from"))
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||||
}
|
}
|
||||||
to, err := time.ParseTime(c.QueryParam("to"))
|
to, err := time.ParseTime(c.QueryParam("to"))
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to parse time")
|
||||||
}
|
}
|
||||||
rooms, err := room.GetFreeRooms(app, from, to)
|
rooms, err := room.GetFreeRooms(app, from, to)
|
||||||
if err != nil {
|
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.StatusBadRequest, "Failed to get free rooms")
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, rooms)
|
return c.JSON(http.StatusOK, rooms)
|
||||||
@@ -238,12 +257,14 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Path: "/api/course/modules",
|
Path: "/api/course/modules",
|
||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
course := c.QueryParam("course")
|
modules, err := events.GetModulesForCourseDistinct(
|
||||||
semester := c.QueryParam("semester")
|
app,
|
||||||
modules, err := events.GetModulesForCourseDistinct(app, course, semester)
|
c.QueryParam("course"),
|
||||||
|
c.QueryParam("semester"),
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester")
|
||||||
} else {
|
} else {
|
||||||
return c.JSON(http.StatusOK, modules)
|
return c.JSON(http.StatusOK, modules)
|
||||||
@@ -266,7 +287,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
modules, err := events.GetAllModulesDistinct(app)
|
modules, err := events.GetAllModulesDistinct(app)
|
||||||
if err != nil {
|
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.StatusBadRequest, "Failed to get modules")
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, modules)
|
return c.JSON(http.StatusOK, modules)
|
||||||
@@ -289,7 +310,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
requestModule := c.QueryParam("uuid")
|
requestModule := c.QueryParam("uuid")
|
||||||
module, err := events.GetModuleByUUID(app, requestModule)
|
module, err := events.GetModuleByUUID(app, requestModule)
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to get module")
|
||||||
} else {
|
} else {
|
||||||
return c.JSON(http.StatusOK, module)
|
return c.JSON(http.StatusOK, module)
|
||||||
@@ -339,10 +360,34 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester)
|
courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester)
|
||||||
|
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events")
|
||||||
} else {
|
} 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{
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
@@ -360,39 +405,16 @@ func AddRoutes(app *pocketbase.PocketBase) {
|
|||||||
Method: http.MethodDelete,
|
Method: http.MethodDelete,
|
||||||
Path: "/api/events",
|
Path: "/api/events",
|
||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
course := c.QueryParam("course")
|
err := events.DeleteAllEventsByCourseAndSemester(
|
||||||
semester := c.QueryParam("semester")
|
app,
|
||||||
err := events.DeleteAllEventsByCourseAndSemester(app, course, semester)
|
c.QueryParam("course"),
|
||||||
|
c.QueryParam("semester"),
|
||||||
|
)
|
||||||
if err != nil {
|
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")
|
return c.JSON(http.StatusBadRequest, "Failed to delete events")
|
||||||
} else {
|
} else {
|
||||||
return c.JSON(http.StatusBadRequest, "Events deleted")
|
return c.JSON(http.StatusOK, "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")
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Middlewares: []echo.MiddlewareFunc{
|
Middlewares: []echo.MiddlewareFunc{
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"htwkalender/service/course"
|
"htwkalender/service/course"
|
||||||
"htwkalender/service/feed"
|
"htwkalender/service/feed"
|
||||||
"htwkalender/service/fetch/sport"
|
"htwkalender/service/fetch/sport"
|
||||||
|
v1 "htwkalender/service/fetch/v1"
|
||||||
v2 "htwkalender/service/fetch/v2"
|
v2 "htwkalender/service/fetch/v2"
|
||||||
"htwkalender/service/functions/time"
|
"htwkalender/service/functions/time"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -34,10 +35,21 @@ func AddSchedules(app *pocketbase.PocketBase) {
|
|||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
scheduler := cron.New()
|
scheduler := cron.New()
|
||||||
|
|
||||||
// Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *"
|
// !! IMPORTANT !! CRON is based on UTC time zone so in Germany it is UTC+2 in summer and UTC+1 in winter
|
||||||
// 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 * * * *"
|
// Every sunday at 10pm update all courses (5 segments - minute, hour, day, month, weekday) "0 22 * * 0"
|
||||||
scheduler.MustAdd("updateCourse", "0 */3 * * *", func() {
|
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")
|
slog.Info("Started updating courses schedule")
|
||||||
course.UpdateCourse(app)
|
course.UpdateCourse(app)
|
||||||
})
|
})
|
||||||
@@ -54,16 +66,16 @@ func AddSchedules(app *pocketbase.PocketBase) {
|
|||||||
slog.Info("Started fetching sport events schedule")
|
slog.Info("Started fetching sport events schedule")
|
||||||
sportEvents, err := sport.FetchAndUpdateSportEvents(app)
|
sportEvents, err := sport.FetchAndUpdateSportEvents(app)
|
||||||
if err != nil {
|
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")
|
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
|
//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{})
|
savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to fetch and save events: %v", err)
|
slog.Error("Failed to fetch and save events: ", "error", err)
|
||||||
} else {
|
} else {
|
||||||
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
|
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
|
||||||
}
|
}
|
||||||
|
@@ -20,17 +20,14 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"htwkalender/service/events"
|
"htwkalender/service/events"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateCourse(app *pocketbase.PocketBase) {
|
func UpdateCourse(app *pocketbase.PocketBase) {
|
||||||
courses := events.GetAllCourses(app)
|
courses := events.GetAllCourses(app)
|
||||||
for _, course := range courses {
|
for _, course := range courses {
|
||||||
savedEvents, err := events.UpdateModulesForCourse(app, course)
|
_, err := events.UpdateModulesForCourse(app, course)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Update Course: "+course+" failed: %v", err)
|
slog.Warn("Update Course: "+course+" failed:", "error", err)
|
||||||
} else {
|
|
||||||
slog.Info("Updated Course: " + course + " with " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ func GetDateFromWeekNumber(year int, weekNumber int, dayName string) (time.Time,
|
|||||||
europeTime, err := time.LoadLocation("Europe/Berlin")
|
europeTime, err := time.LoadLocation("Europe/Berlin")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to load location: ", err)
|
slog.Error("Failed to load location: ", "error", err)
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,24 +27,22 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase"
|
"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 toBeSavedEvents model.Events
|
||||||
var savedRecords model.Events
|
var savedRecords model.Events
|
||||||
|
|
||||||
// check if event is already in database and add to toBeSavedEvents if not
|
// check if event is already in database and add to toBeSavedEvents if not
|
||||||
for _, seminarGroup := range seminarGroups {
|
for _, event := range seminarGroup.Events {
|
||||||
for _, event := range seminarGroup.Events {
|
event = event.SetCourse(seminarGroup.Course)
|
||||||
event = event.SetCourse(seminarGroup.Course)
|
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
||||||
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
||||||
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !existsInDatabase && !alreadyAddedToSave {
|
if !existsInDatabase && !alreadyAddedToSave {
|
||||||
toBeSavedEvents = append(toBeSavedEvents, event)
|
toBeSavedEvents = append(toBeSavedEvents, event)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,13 +192,26 @@ func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.Feed
|
|||||||
return events, nil
|
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) {
|
func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
|
||||||
var events model.Events
|
var events model.Events
|
||||||
|
|
||||||
// get all events from event records in the events collection
|
// 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)
|
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 {
|
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)
|
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)
|
err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules)
|
||||||
if err != nil {
|
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")
|
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
|
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) {
|
func GetEventsThatStartAfterAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
||||||
var events model.Events
|
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)
|
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
|
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
|
// get all rooms from event records in the events collection
|
||||||
err := app.Dao().DB().Select("course").From("groups").All(&courses)
|
err := app.Dao().DB().Select("course").From("groups").All(&courses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error while getting groups from database: ", err)
|
slog.Error("Error while getting groups from database: ", "error", err)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []str
|
|||||||
// get all courses for a specific semester
|
// 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)
|
err := app.Dao().DB().Select("course").From("groups").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).All(&courses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error while getting groups from database: ", err)
|
slog.Error("Error while getting groups from database: ", "error", err)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester str
|
|||||||
// get all courses from events distinct for a specific semester
|
// 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)
|
err := app.Dao().DB().Select("course").From("events").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).Distinct(true).All(&courses)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,6 +22,8 @@ import (
|
|||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"htwkalender/service/fetch/v1"
|
"htwkalender/service/fetch/v1"
|
||||||
"htwkalender/service/functions"
|
"htwkalender/service/functions"
|
||||||
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetModulesForCourseDistinct(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
|
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
|
// If the update was not successful, an error is returned
|
||||||
func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) (model.Events, error) {
|
func UpdateModulesForCourse(app *pocketbase.PocketBase, course string) (model.Events, error) {
|
||||||
|
|
||||||
//new string array with one element (course)
|
seminarGroup := v1.GetSeminarGroupEventsFromHTML(course)
|
||||||
var courses []string
|
|
||||||
courses = append(courses, course)
|
|
||||||
|
|
||||||
seminarGroups := v1.GetSeminarGroupsEventsFromHTML(courses)
|
seminarGroup = v1.ClearEmptySeminarGroups(seminarGroup)
|
||||||
|
|
||||||
seminarGroups = v1.ClearEmptySeminarGroups(seminarGroups)
|
seminarGroup = v1.ReplaceEmptyEventNames(seminarGroup)
|
||||||
|
|
||||||
seminarGroups = v1.ReplaceEmptyEventNames(seminarGroups)
|
|
||||||
|
|
||||||
//check if events in the seminarGroups Events are already in the database
|
//check if events in the seminarGroups Events are already in the database
|
||||||
//if yes, keep the database as it is
|
//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
|
//if there are no events in the database, save the new events
|
||||||
|
|
||||||
//get all events for the course and the semester
|
//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 {
|
if err != nil {
|
||||||
return nil, err
|
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 there are no events in the database, save the new events
|
||||||
if len(events) == 0 {
|
if len(dbEvents) == 0 {
|
||||||
events, dbError := db.SaveSeminarGroupEvents(seminarGroups, app)
|
events, dbError := db.SaveSeminarGroupEvents(seminarGroup, app)
|
||||||
if dbError != nil {
|
if dbError != nil {
|
||||||
return nil, dbError
|
return nil, dbError
|
||||||
}
|
}
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if events in the seminarGroups Events are already in the database
|
// Create partial update list and delete list for the events
|
||||||
//if yes, keep the database as it is
|
var insertList model.Events
|
||||||
//if no, delete all events for the course and the semester and save the new events
|
var deleteList model.Events
|
||||||
|
|
||||||
var savedEvents model.Events
|
// check which events are not already in the database and need to be inserted/saved
|
||||||
|
for _, event := range seminarGroup.Events {
|
||||||
for _, seminarGroup := range seminarGroups {
|
if !ContainsEvent(dbEvents, event) {
|
||||||
for _, event := range seminarGroup.Events {
|
insertList = append(insertList, event)
|
||||||
// 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 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
|
return savedEvents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,3 +198,18 @@ func ContainsEvent(events model.Events, event model.Event) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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) {
|
func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
|
||||||
feeds, err := database.GetAllFeeds(db)
|
feeds, err := database.GetAllFeeds(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("CleanFeeds: failed to get all feeds", err)
|
slog.Error("CleanFeeds: failed to get all feeds", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, feed := range feeds {
|
for _, feed := range feeds {
|
||||||
@@ -44,8 +44,8 @@ func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) {
|
|||||||
var sqlResult sql.Result
|
var sqlResult sql.Result
|
||||||
sqlResult, err = db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute()
|
sqlResult, err = db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", err)
|
slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", "error", err)
|
||||||
slog.Error("SQL Result: ", sqlResult)
|
slog.Error("SQL Result: ", "error", sqlResult)
|
||||||
} else {
|
} else {
|
||||||
slog.Info("CleanFeeds: delete feed " + feed.GetId() + " successful")
|
slog.Info("CleanFeeds: delete feed " + feed.GetId() + " successful")
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import (
|
|||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"htwkalender/service/functions"
|
"htwkalender/service/functions"
|
||||||
|
clock "htwkalender/service/functions/time"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"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
|
// @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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -208,7 +209,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
|||||||
for _, day := range days {
|
for _, day := range days {
|
||||||
weekDay, err := getDayInt(day)
|
weekDay, err := getDayInt(day)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error while getting day int: "+day+" ", err)
|
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||||
} else {
|
} else {
|
||||||
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||||
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
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])
|
endI, endIErr = getDayInt(days[1])
|
||||||
|
|
||||||
if endIErr != nil || startIErr != nil {
|
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 {
|
} else {
|
||||||
//create a int array with all days from start to end day
|
//create a int array with all days from start to end day
|
||||||
var daysBetween []int
|
var daysBetween []int
|
||||||
@@ -258,7 +260,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
|||||||
|
|
||||||
dayInt, err := getDayInt(day)
|
dayInt, err := getDayInt(day)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error while getting day int: "+day+" ", err)
|
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||||
} else {
|
} else {
|
||||||
dayNumbers = append(dayNumbers, dayInt)
|
dayNumbers = append(dayNumbers, dayInt)
|
||||||
}
|
}
|
||||||
@@ -270,7 +272,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [
|
|||||||
|
|
||||||
weekDay, err := getDayInt(day)
|
weekDay, err := getDayInt(day)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error while getting day int: "+day+" ", err)
|
slog.Error("Error while getting day int: "+day+" ", "error", err)
|
||||||
} else {
|
} else {
|
||||||
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
weekEvents = append(weekEvents, model.SportDayStartEnd{
|
||||||
Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()),
|
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)
|
var doc, err = htmlRequest(url)
|
||||||
|
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,7 +444,7 @@ func htmlRequest(url string) (*goquery.Document, error) {
|
|||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
readErr := Body.Close()
|
readErr := Body.Close()
|
||||||
if readErr != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
@@ -32,50 +32,43 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReplaceEmptyEventNames(groups []model.SeminarGroup) []model.SeminarGroup {
|
func ReplaceEmptyEventNames(group model.SeminarGroup) model.SeminarGroup {
|
||||||
for i, group := range groups {
|
for j, event := range group.Events {
|
||||||
for j, event := range group.Events {
|
if functions.OnlyWhitespace(event.Name) {
|
||||||
if functions.OnlyWhitespace(event.Name) {
|
group.Events[j].Name = "Sonderveranstaltungen"
|
||||||
groups[i].Events[j].Name = "Sonderveranstaltungen"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return groups
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearEmptySeminarGroups(seminarGroups []model.SeminarGroup) []model.SeminarGroup {
|
func ClearEmptySeminarGroups(seminarGroup model.SeminarGroup) model.SeminarGroup {
|
||||||
var newSeminarGroups []model.SeminarGroup
|
var newSeminarGroup = model.SeminarGroup{}
|
||||||
for _, seminarGroup := range seminarGroups {
|
|
||||||
if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" {
|
if len(seminarGroup.Events) > 0 && seminarGroup.Course != "" {
|
||||||
newSeminarGroups = append(newSeminarGroups, seminarGroup)
|
newSeminarGroup = seminarGroup
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return newSeminarGroups
|
return newSeminarGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.SeminarGroup {
|
func GetSeminarGroupEventsFromHTML(seminarGroupLabel string) model.SeminarGroup {
|
||||||
var seminarGroups []model.SeminarGroup
|
var seminarGroup model.SeminarGroup
|
||||||
for _, seminarGroupLabel := range seminarGroupsLabel {
|
|
||||||
|
|
||||||
if (time.Now().Month() >= 3) && (time.Now().Month() <= 10) {
|
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"
|
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)
|
result, getError := fetch.GetHTML(ssUrl)
|
||||||
if getError == nil {
|
if getError == nil {
|
||||||
seminarGroup := parseSeminarGroup(result)
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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) {
|
func SplitEventType(events []model.Event) ([]model.Event, error) {
|
||||||
@@ -110,19 +103,18 @@ func parseSeminarGroup(result string) model.SeminarGroup {
|
|||||||
if eventTables == nil || allDayLabels == nil {
|
if eventTables == nil || allDayLabels == nil {
|
||||||
return model.SeminarGroup{}
|
return model.SeminarGroup{}
|
||||||
}
|
}
|
||||||
|
course := findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data
|
||||||
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels)
|
eventsWithCombinedWeeks := toEvents(eventTables, allDayLabels, course)
|
||||||
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
splitEventsByWeekVal := splitEventsByWeek(eventsWithCombinedWeeks)
|
||||||
events := splitEventsBySingleWeek(splitEventsByWeekVal)
|
events := splitEventsBySingleWeek(splitEventsByWeekVal)
|
||||||
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
|
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
|
||||||
course := findFirstSpanWithClass(table, "header-2-0-1").FirstChild.Data
|
|
||||||
semester, year := extractSemesterAndYear(semesterString)
|
semester, year := extractSemesterAndYear(semesterString)
|
||||||
events = convertWeeksToDates(events, semester, year)
|
events = convertWeeksToDates(events, semester, year)
|
||||||
events = generateUUIDs(events, course)
|
events = generateUUIDs(events, course)
|
||||||
events, err = SplitEventType(events)
|
events, err = SplitEventType(events)
|
||||||
|
|
||||||
if err != nil {
|
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{}
|
return model.SeminarGroup{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +203,7 @@ func extractSemesterAndYear(semesterString string) (string, string) {
|
|||||||
return semesterShortcut, year
|
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
|
var events []model.Event
|
||||||
|
|
||||||
for table := range tables {
|
for table := range tables {
|
||||||
@@ -232,6 +224,7 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event {
|
|||||||
Rooms: getTextContent(tableData[6]),
|
Rooms: getTextContent(tableData[6]),
|
||||||
Notes: getTextContent(tableData[7]),
|
Notes: getTextContent(tableData[7]),
|
||||||
BookedAt: getTextContent(tableData[8]),
|
BookedAt: getTextContent(tableData[8]),
|
||||||
|
Course: course,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -75,28 +75,17 @@ func Test_extractSemesterAndYear(t *testing.T) {
|
|||||||
|
|
||||||
func Test_replaceEmptyEventNames(t *testing.T) {
|
func Test_replaceEmptyEventNames(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
groups []model.SeminarGroup
|
group model.SeminarGroup
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want []model.SeminarGroup
|
want model.SeminarGroup
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test 1",
|
name: "Test 1",
|
||||||
args: args{
|
args: args{
|
||||||
groups: []model.SeminarGroup{
|
group: model.SeminarGroup{
|
||||||
{
|
|
||||||
Events: []model.Event{
|
|
||||||
{
|
|
||||||
Name: "Test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []model.SeminarGroup{
|
|
||||||
{
|
|
||||||
Events: []model.Event{
|
Events: []model.Event{
|
||||||
{
|
{
|
||||||
Name: "Test",
|
Name: "Test",
|
||||||
@@ -104,26 +93,29 @@ func Test_replaceEmptyEventNames(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
want: model.SeminarGroup{
|
||||||
|
Events: []model.Event{
|
||||||
|
{
|
||||||
|
Name: "Test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test 1",
|
name: "Test 1",
|
||||||
args: args{
|
args: args{
|
||||||
groups: []model.SeminarGroup{
|
group: model.SeminarGroup{
|
||||||
{
|
Events: []model.Event{
|
||||||
Events: []model.Event{
|
{
|
||||||
{
|
Name: "",
|
||||||
Name: "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []model.SeminarGroup{
|
want: model.SeminarGroup{
|
||||||
{
|
Events: []model.Event{
|
||||||
Events: []model.Event{
|
{
|
||||||
{
|
Name: "Sonderveranstaltungen",
|
||||||
Name: "Sonderveranstaltungen",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -131,7 +123,7 @@ func Test_replaceEmptyEventNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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)
|
t.Errorf("ReplaceEmptyEventNames() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -23,6 +23,8 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
|
"htwkalender/service/functions"
|
||||||
|
"htwkalender/service/functions/time"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -59,36 +61,32 @@ func getSeminarHTML(semester string) (string, error) {
|
|||||||
func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) {
|
func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) {
|
||||||
var groups []model.SeminarGroup
|
var groups []model.SeminarGroup
|
||||||
|
|
||||||
resultSummer, err := getSeminarHTML("ss")
|
semesterString := functions.CalculateSemesterList(time.RealClock{})
|
||||||
|
var results [2]string
|
||||||
|
var err error
|
||||||
|
|
||||||
if err != nil {
|
for i, semester := range semesterString {
|
||||||
slog.Error("Error while fetching seminar groups for winter semester", err)
|
results[i], err = getSeminarHTML(semester)
|
||||||
return nil, err
|
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
|
// filter duplicates
|
||||||
groups = removeDuplicates(groups)
|
groups = removeDuplicates(groups)
|
||||||
|
|
||||||
collection, dbError := db.FindCollection(app, "groups")
|
collection, dbError := db.FindCollection(app, "groups")
|
||||||
if dbError != nil {
|
if dbError != nil {
|
||||||
slog.Error("Error while searching collection groups", dbError)
|
slog.Error("Error while searching collection groups", "error", dbError)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var insertedGroups []*models.Record
|
var insertedGroups []*models.Record
|
||||||
|
|
||||||
insertedGroups, dbError = db.SaveGroups(groups, collection, app)
|
insertedGroups, dbError = db.SaveGroups(groups, collection, app)
|
||||||
if dbError != nil {
|
if dbError != nil {
|
||||||
slog.Error("Error while saving groups", dbError)
|
slog.Error("Error while saving groups", "error", dbError)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,10 +25,10 @@ import (
|
|||||||
"htwkalender/service/db"
|
"htwkalender/service/db"
|
||||||
"htwkalender/service/fetch"
|
"htwkalender/service/fetch"
|
||||||
v1 "htwkalender/service/fetch/v1"
|
v1 "htwkalender/service/fetch/v1"
|
||||||
|
"htwkalender/service/functions"
|
||||||
localTime "htwkalender/service/functions/time"
|
localTime "htwkalender/service/functions/time"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseEventsFromRemote(app *pocketbase.PocketBase) (model.Events, error) {
|
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
|
// Fetch and save events for all semesters
|
||||||
for _, semester := range calculateSemesterList(clock) {
|
for _, semester := range functions.CalculateSemesterList(clock) {
|
||||||
events, fetchErr := fetchAndSaveAllEventsForSemester(app, semester, stubUrl)
|
events, fetchErr := fetchAndSaveAllEventsForSemester(app, semester, stubUrl)
|
||||||
if fetchErr != nil {
|
if fetchErr != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", err)
|
return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", err)
|
||||||
@@ -104,25 +104,6 @@ func fetchAndSaveAllEventsForSemester(
|
|||||||
return savedRecords, err
|
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) {
|
func parseEventForOneSemester(url string) ([]model.Event, error) {
|
||||||
// Fetch Webpage from URL
|
// Fetch Webpage from URL
|
||||||
webpage, err := fetch.GetHTML(url)
|
webpage, err := fetch.GetHTML(url)
|
||||||
@@ -160,7 +141,7 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
|
|||||||
events = convertWeeksToDates(events, semester, year)
|
events = convertWeeksToDates(events, semester, year)
|
||||||
events, err = v1.SplitEventType(events)
|
events, err = v1.SplitEventType(events)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
events = switchNameAndNotesForExam(events)
|
events = switchNameAndNotesForExam(events)
|
||||||
|
@@ -18,10 +18,8 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"htwkalender/model"
|
"htwkalender/model"
|
||||||
mockTime "htwkalender/service/functions/time"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_switchNameAndNotesForExam(t *testing.T) {
|
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
|
package functions
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
localTime "htwkalender/service/functions/time"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// GetCurrentSemesterString returns the current semester as string
|
// GetCurrentSemesterString returns the current semester as string
|
||||||
// if current month is between 10 and 03 -> winter semester "ws"
|
// if current month is between 10 and 03 -> winter semester "ws"
|
||||||
func GetCurrentSemesterString() string {
|
func GetCurrentSemesterString(localeTime localTime.Clock) string {
|
||||||
|
if localeTime.Now().Month() >= 10 || localeTime.Now().Month() <= 3 {
|
||||||
if time.Now().Month() >= 10 || time.Now().Month() <= 3 {
|
|
||||||
return "ws"
|
return "ws"
|
||||||
} else {
|
} else {
|
||||||
return "ss"
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceEmptyString(word string, replacement string) string {
|
|
||||||
if OnlyWhitespace(word) {
|
|
||||||
return replacement
|
|
||||||
}
|
|
||||||
return word
|
|
||||||
}
|
|
||||||
|
|
||||||
func HashString(s string) string {
|
func HashString(s string) string {
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
hash.Write([]byte(s))
|
hash.Write([]byte(s))
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package functions
|
package functions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"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 {
|
func ParseAsTypesDatetime(time time.Time) types.DateTime {
|
||||||
dateTime, err := types.ParseDateTime(time)
|
dateTime, err := types.ParseDateTime(time)
|
||||||
if err != nil {
|
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 types.DateTime{}
|
||||||
}
|
}
|
||||||
return 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
|
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 {
|
func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string {
|
||||||
var freeRooms []string
|
var freeRooms []string
|
||||||
for _, room := range rooms {
|
for _, room := range rooms {
|
||||||
@@ -84,6 +85,7 @@ func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string
|
|||||||
return freeRooms
|
return freeRooms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a room is in the schedule
|
||||||
func isRoomInSchedule(room string, schedule []model.Event) bool {
|
func isRoomInSchedule(room string, schedule []model.Event) bool {
|
||||||
for _, event := range schedule {
|
for _, event := range schedule {
|
||||||
if event.Course != "Sport" {
|
if event.Course != "Sport" {
|
||||||
|
@@ -191,6 +191,50 @@ func Test_isRoomInSchedule(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: false,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@@ -23,8 +23,8 @@ services:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
target: dev # prod
|
target: dev # prod
|
||||||
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
|
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
|
||||||
#ports:
|
ports:
|
||||||
# - "8090:8090"
|
- "8090:8090"
|
||||||
volumes:
|
volumes:
|
||||||
- pb_data:/htwkalender/data # for production with volume
|
- pb_data:/htwkalender/data # for production with volume
|
||||||
# - ./backend:/htwkalender/data # for development with bind mount from project directory
|
# - ./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 router from "../router";
|
||||||
import { fetchModule } from "../api/fetchModule.ts";
|
import { fetchModule } from "../api/fetchModule.ts";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { fetchEventTypes } from "../api/fetchEvents.ts";
|
||||||
|
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
@@ -39,6 +40,9 @@ if (store.isEmpty()) {
|
|||||||
router.replace("/");
|
router.replace("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const eventTypes: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
|
|
||||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
course: {
|
course: {
|
||||||
@@ -51,7 +55,7 @@ const filters = ref({
|
|||||||
},
|
},
|
||||||
eventType: {
|
eventType: {
|
||||||
value: null,
|
value: null,
|
||||||
matchMode: FilterMatchMode.CONTAINS,
|
matchMode: FilterMatchMode.IN,
|
||||||
},
|
},
|
||||||
prof: {
|
prof: {
|
||||||
value: null,
|
value: null,
|
||||||
@@ -63,7 +67,7 @@ const loadedModules: Ref<Module[]> = ref(new Array(10));
|
|||||||
|
|
||||||
const loadingData = ref(true);
|
const loadingData = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted( () => {
|
||||||
fetchAllModules()
|
fetchAllModules()
|
||||||
.then(
|
.then(
|
||||||
(data) =>
|
(data) =>
|
||||||
@@ -74,6 +78,10 @@ onMounted(() => {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
loadingData.value = false;
|
loadingData.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fetchEventTypes().then((data) => {
|
||||||
|
eventTypes.value = data;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const ModuleInformation = defineAsyncComponent(
|
const ModuleInformation = defineAsyncComponent(
|
||||||
@@ -184,16 +192,20 @@ function unselectModule(event: DataTableRowUnselectEvent) {
|
|||||||
</Column>
|
</Column>
|
||||||
<Column
|
<Column
|
||||||
field="eventType"
|
field="eventType"
|
||||||
|
filter-field="eventType"
|
||||||
|
:filter-menu-style="{ width: '10rem' }"
|
||||||
|
style="min-width: 10rem"
|
||||||
:header="$t('additionalModules.eventType')"
|
:header="$t('additionalModules.eventType')"
|
||||||
:show-clear-button="false"
|
:show-clear-button="false"
|
||||||
:show-filter-menu="false"
|
:show-filter-menu="false"
|
||||||
>
|
>
|
||||||
<template #filter="{ filterModel, filterCallback }">
|
<template #filter="{ filterModel, filterCallback }">
|
||||||
<InputText
|
<MultiSelect
|
||||||
v-model="filterModel.value"
|
v-model="filterModel.value"
|
||||||
type="text"
|
:options="eventTypes"
|
||||||
class="p-column-filter max-w-10rem"
|
class="p-column-filter max-w-10rem"
|
||||||
@input="filterCallback()"
|
style="min-width: 10rem"
|
||||||
|
@change="filterCallback()"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="loadingData" #body>
|
<template v-if="loadingData" #body>
|
||||||
|
@@ -196,6 +196,24 @@ http {
|
|||||||
limit_req zone=modules burst=5 nodelay;
|
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 {
|
location /api/rooms {
|
||||||
proxy_pass http://htwkalender-backend:8090;
|
proxy_pass http://htwkalender-backend:8090;
|
||||||
client_max_body_size 20m;
|
client_max_body_size 20m;
|
||||||
|
Reference in New Issue
Block a user