Merge pull request #116 from HTWK-Leipzig/104-exams-in-calendar

104 exams in calendar
This commit is contained in:
masterElmar
2023-12-29 03:31:18 +01:00
committed by GitHub
13 changed files with 190 additions and 26 deletions

View File

@@ -5,9 +5,13 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/cron"
"htwkalender/service/course"
"htwkalender/service/events"
"htwkalender/service/feed"
"htwkalender/service/fetch/sport"
v2 "htwkalender/service/fetch/v2"
"htwkalender/service/functions/time"
"log"
"strconv"
)
func AddSchedules(app *pocketbase.PocketBase) {
@@ -22,17 +26,33 @@ func AddSchedules(app *pocketbase.PocketBase) {
course.UpdateCourse(app)
})
// Every sunday at 3am clean all courses (5 segments - minute, hour, day, month, weekday) "0 3 * * 0"
scheduler.MustAdd("cleanFeeds", "0 3 * * 0", func() {
// Every sunday at 1am clean all courses (5 segments - minute, hour, day, month, weekday) "0 3 * * 0"
scheduler.MustAdd("cleanFeeds", "0 1 * * 0", func() {
// clean feeds older than 6 months
feed.ClearFeeds(app.Dao(), 6, time.RealClock{})
})
// Every sunday at 2am fetch all sport events (5 segments - minute, hour, day, month, weekday) "0 2 * * 0"
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
// Every sunday at 3am fetch all sport events (5 segments - minute, hour, day, month, weekday) "0 2 * * 0"
scheduler.MustAdd("fetchSportEvents", "0 3 * * 0", func() {
sport.FetchAndUpdateSportEvents(app)
})
//delete all events and then fetch all events from remote this should be done every sunday at 2am
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
err := events.DeleteAllEvents(app)
if err != nil {
log.Println(err)
}
err, savedEvents := v2.FetchAllEventsAndSave(app)
if err != nil {
log.Println(err)
} else {
log.Println("Successfully saved: " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events")
}
})
scheduler.Start()
return nil
})

View File

@@ -125,20 +125,14 @@ func buildIcalQueryForModules(modules []model.FeedCollection) dbx.Expression {
//second check if modules has only one element
if len(modules) == 1 {
return dbx.And(
dbx.HashExp{"Name": modules[0].Name},
dbx.HashExp{"course": modules[0].Course},
)
return dbx.HashExp{"uuid": modules[0].UUID}
}
//third check if modules has more than one element
var wheres []dbx.Expression
for _, module := range modules {
where := dbx.And(
dbx.HashExp{"Name": module.Name},
dbx.HashExp{"course": module.Course},
)
where := dbx.HashExp{"uuid": module.UUID}
wheres = append(wheres, where)
}

View File

@@ -23,13 +23,13 @@ func Test_buildIcalQueryForModules(t *testing.T) {
},
{
name: "one module",
args: args{modules: []model.FeedCollection{{Name: "test", Course: "test"}}},
want: dbx.And(dbx.HashExp{"Name": "test"}, dbx.HashExp{"course": "test"}),
args: args{modules: []model.FeedCollection{{Name: "test", Course: "test", UUID: "test"}}},
want: dbx.HashExp{"uuid": "test"},
},
{
name: "two modules",
args: args{modules: []model.FeedCollection{{Name: "test", Course: "test"}, {Name: "test2", Course: "test2"}}},
want: dbx.Or(dbx.And(dbx.HashExp{"Name": "test"}, dbx.HashExp{"course": "test"}), dbx.And(dbx.HashExp{"Name": "test2"}, dbx.HashExp{"course": "test2"})),
args: args{modules: []model.FeedCollection{{Name: "test", Course: "test", UUID: "test"}, {Name: "test2", Course: "test2", UUID: "test2"}}},
want: dbx.Or(dbx.HashExp{"uuid": "test"}, dbx.HashExp{"uuid": "test2"}),
},
}
for _, tt := range tests {

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

@@ -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

@@ -30,20 +30,45 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase) (error, []model.Event) {
var savedRecords []model.Event
var events []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 (time.Now().Month() >= 3) && (time.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)
savedEvents, dbError := db.SaveEvents(events, app)
err = dbError
savedRecords = append(savedEvents, events...)
savedRecords = append(savedRecords, savedEvents...)
}
if (time.Now().Month() >= 9) || (time.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)
savedEvents, dbError := db.SaveEvents(events, app)
err = dbError
savedRecords = append(savedEvents, events...)
savedRecords = append(savedRecords, savedEvents...)
}
return err, savedRecords
}
@@ -89,6 +114,8 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
events = generateUUIDs(events)
events = splitEventType(events)
events = switchNameAndNotesForExam(events)
var seminarGroup = model.SeminarGroup{
University: findFirstSpanWithClass(table, "header-1-0-0").FirstChild.Data,
Events: events,
@@ -101,6 +128,19 @@ func parseEventForOneSemester(url string) ([]model.Event, error) {
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(err error, webpage string) (*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)
}
})
}
}

View File

@@ -36,6 +36,7 @@ export async function fetchModulesByCourseAndSemester(
module.uuid,
module.name,
course,
module.eventType,
module.name,
module.prof,
semester,
@@ -61,6 +62,7 @@ export async function fetchAllModules(): Promise<Module[]> {
module.uuid,
module.name,
module.course,
module.eventType,
module.name,
module.prof,
module.semester,

View File

@@ -14,6 +14,7 @@ export async function fetchModule(module: Module): Promise<Module> {
module.uuid,
module.name,
module.course,
module.eventType,
module.name,
module.prof,
module.semester,

View File

@@ -33,6 +33,10 @@ const filters = ref({
value: null,
matchMode: FilterMatchMode.CONTAINS,
},
eventType: {
value: null,
matchMode: FilterMatchMode.CONTAINS,
},
prof: {
value: null,
matchMode: FilterMatchMode.CONTAINS,
@@ -146,6 +150,21 @@ function unselectModule(event: DataTableRowUnselectEvent) {
/>
</template>
</Column>
<Column
field="eventType"
:header="$t('additionalModules.eventType')"
:show-clear-button="false"
:show-filter-menu="false"
>
<template #filter="{ filterModel, filterCallback }">
<InputText
v-model="filterModel.value"
type="text"
class="p-column-filter max-w-10rem"
@input="filterCallback()"
/>
</template>
</Column>
<Column
field="prof"
:header="$t('additionalModules.professor')"

View File

@@ -93,7 +93,8 @@
"from": "",
"to": " bis ",
"of": " von insgesamt "
}
},
"eventType": "Ereignistyp"
},
"renameModules": {
"reminder": "Erinnerung",

View File

@@ -93,7 +93,8 @@
"from": "",
"to": " to ",
"of": " of "
}
},
"eventType": "event type"
},
"renameModules": {
"reminder": "reminder",

View File

@@ -5,6 +5,7 @@ export class Module {
public uuid: string,
public name: string,
public course: string,
public eventType: string,
public userDefinedName: string,
public prof: string,
public semester: string,