Merge branch 'main' into 150-fix-error-response

# Conflicts:
#	backend/service/addSchedule.go
#	backend/service/fetch/v2/fetcher.go
#	frontend/package-lock.json
This commit is contained in:
Elmar Kresse
2024-01-21 17:59:08 +01:00
58 changed files with 6719 additions and 3348 deletions

View File

@@ -18,8 +18,9 @@ import (
"github.com/PuerkitoBio/goquery"
)
// @TODO: add tests
// @TODO: make it like a cron job to fetch the sport courses once a week
// FetchAndUpdateSportEvents fetches all sport events from the HTWK sport website
// it deletes them first and then saves them to the database
// It returns all saved events
func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event {
var sportCourseLinks = fetchAllAvailableSportCourses()
@@ -56,6 +57,7 @@ func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) []model.Event {
}
}
// @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.DeleteAllEventsForCourse(app, "Sport", functions.GetCurrentSemesterString())
if err != nil {
return nil
@@ -89,7 +91,7 @@ func formatEntriesToEvents(entries []model.SportEntry) []model.Event {
Week: strconv.Itoa(23),
Start: start,
End: end,
Name: entry.Title + " " + entry.Details.Type + " (" + entry.ID + ")",
Name: entry.Title + " (" + entry.ID + ")",
EventType: entry.Details.Type,
Prof: entry.Details.CourseLead.Name,
Rooms: entry.Details.Location.Name,

View File

@@ -267,7 +267,6 @@ func Test_generateUUIDs(t *testing.T) {
}
func Test_createTimeFromHourAndMinuteString(t *testing.T) {
europeTime, _ := time.LoadLocation("Europe/Berlin")
type args struct {
tableTime string
}
@@ -281,21 +280,21 @@ func Test_createTimeFromHourAndMinuteString(t *testing.T) {
args: args{
tableTime: "08:00",
},
want: time.Date(0, 0, 0, 8, 0, 0, 0, europeTime),
want: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC),
},
{
name: "Test 2",
args: args{
tableTime: "08:15",
},
want: time.Date(0, 0, 0, 8, 15, 0, 0, europeTime),
want: time.Date(0, 0, 0, 8, 15, 0, 0, time.UTC),
},
{
name: "Test 3",
args: args{
tableTime: "08:30",
},
want: time.Date(0, 0, 0, 8, 30, 0, 0, europeTime),
want: time.Date(0, 0, 0, 8, 30, 0, 0, time.UTC),
},
}
for _, tt := range tests {

View File

@@ -35,7 +35,7 @@ func toEvents(tables [][]*html.Node, days []string) []model.Event {
Prof: getTextContent(tableData[6]),
Rooms: getTextContent(tableData[8]),
BookedAt: getTextContent(tableData[10]),
Course: course,
Course: strings.TrimSpace(course),
})
}
}

View File

@@ -23,8 +23,33 @@ func ParseEventsFromRemote(app *pocketbase.PocketBase) (model.Events, error) {
func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([]model.Event, error) {
var savedRecords []model.Event
var stubUrl = [2]string{
"https://stundenplan.htwk-leipzig.de/",
"/Berichte/Text-Listen;Veranstaltungsarten;name;" +
"Vp%0A" +
"Vw%0A" +
"V%0A" +
"Sp%0A" +
"Sw%0A" +
"S%0A" +
"Pp%0A" +
"Pw%0A" +
"P%0A" +
"ZV%0A" +
"Tut%0A" +
"Sperr%0A" +
"pf%0A" +
"wpf%0A" +
"fak%0A" +
"Pruefung%0A" +
"Vertretung%0A" +
"Fremdveranst.%0A" +
"Buchen%0A" +
"%0A?&template=sws_modul&weeks=1-65&combined=yes",
}
if (clock.Now().Month() >= 3) && (clock.Now().Month() <= 10) {
url := "https://stundenplan.htwk-leipzig.de/ss/Berichte/Text-Listen;Veranstaltungsarten;name;Vp%0AVw%0AV%0ASp%0ASw%0AS%0APp%0APw%0AP%0AZV%0ATut%0ASperr%0Apf%0Awpf%0Afak%0A%0A?&template=sws_modul&weeks=1-65&combined=yes"
url := stubUrl[0] + "ss" + stubUrl[1]
events, err := parseEventForOneSemester(url)
if err != nil {
return nil, fmt.Errorf("failed to parse events for summmer semester: %w", err)
@@ -37,7 +62,7 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([
}
if (clock.Now().Month() >= 9) || (clock.Now().Month() <= 4) {
url := "https://stundenplan.htwk-leipzig.de/ws/Berichte/Text-Listen;Veranstaltungsarten;name;Vp%0AVw%0AV%0ASp%0ASw%0AS%0APp%0APw%0AP%0AZV%0ATut%0ASperr%0Apf%0Awpf%0Afak%0A%0A?&template=sws_modul&weeks=1-65&combined=yes"
url := stubUrl[0] + "ws" + stubUrl[1]
events, err := parseEventForOneSemester(url)
if err != nil {
return nil, fmt.Errorf("failed to parse events for winter semester: %w", err)
@@ -46,7 +71,7 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([
if dbError != nil {
return nil, fmt.Errorf("failed to save events: %w", dbError)
}
savedRecords = append(savedEvents, events...)
savedRecords = append(savedRecords, savedEvents...)
}
return savedRecords, nil
}
@@ -87,21 +112,26 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
semesterString := findFirstSpanWithClass(table, "header-0-2-0").FirstChild.Data
semester, year := extractSemesterAndYear(semesterString)
events = convertWeeksToDates(events, semester, year)
events = generateUUIDs(events)
events = splitEventType(events)
var seminarGroup = model.SeminarGroup{
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
Events: events,
}
if seminarGroup.Events == nil && seminarGroup.University == "" {
return nil, err
}
events = switchNameAndNotesForExam(events)
events = generateUUIDs(events)
return events, nil
}
// switch name and notes for Pruefung events when Note is not empty and Name starts with "Prüfungen" and contains email
func switchNameAndNotesForExam(events []model.Event) []model.Event {
for i, event := range events {
if event.EventType == "Pruefung" {
if event.Notes != "" && strings.HasPrefix(event.Name, "Prüfungen") && strings.Contains(event.Name, "@") {
events[i].Name = event.Notes
events[i].Notes = event.Name
}
}
}
return events
}
func parseHTML(webpage string, err error) (*html.Node, error) {
doc, err := html.Parse(strings.NewReader(webpage))
if err != nil {

View File

@@ -0,0 +1,83 @@
package v2
import (
"htwkalender/model"
"reflect"
"testing"
)
func Test_switchNameAndNotesForExam(t *testing.T) {
type args struct {
events []model.Event
}
tests := []struct {
name string
args args
want []model.Event
}{
{
name: "switch name and notes for exam",
args: args{
events: []model.Event{
{
EventType: "Pruefung",
Name: "Prüfungen FING/EIT WiSe (pruefungsamt.fing-eit@htwk-leipzig.de)",
Notes: "Computer Vision II - Räume/Zeit unter Vorbehalt- (Raum W111.1)",
},
},
},
want: []model.Event{
{
EventType: "Pruefung",
Name: "Computer Vision II - Räume/Zeit unter Vorbehalt- (Raum W111.1)",
Notes: "Prüfungen FING/EIT WiSe (pruefungsamt.fing-eit@htwk-leipzig.de)",
},
},
},
{
name: "dont switch name and notes for exam",
args: args{
events: []model.Event{
{
EventType: "Pruefung",
Name: "i054 Umweltschutz und Recycling DPB & VNB 7.FS (wpf)",
Notes: "Prüfung",
},
},
},
want: []model.Event{
{
EventType: "Pruefung",
Notes: "Prüfung",
Name: "i054 Umweltschutz und Recycling DPB & VNB 7.FS (wpf)",
},
},
},
{
name: "dont switch name and notes for exam",
args: args{
events: []model.Event{
{
EventType: "Pruefung",
Name: "Prüfungen FING/ME WiSe (pruefungsamt.fing-me@htwk-leipzig.de)",
Notes: "",
},
},
},
want: []model.Event{
{
EventType: "Pruefung",
Notes: "",
Name: "Prüfungen FING/ME WiSe (pruefungsamt.fing-me@htwk-leipzig.de)",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := switchNameAndNotesForExam(tt.args.events); !reflect.DeepEqual(got, tt.want) {
t.Errorf("switchNameAndNotesForExam() = %v, want %v", got, tt.want)
}
})
}
}