mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 09:38:51 +02:00
369 lines
12 KiB
Go
369 lines
12 KiB
Go
//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 db
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/pocketbase/pocketbase/tools/types"
|
|
"htwkalender/model"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase"
|
|
)
|
|
|
|
func SaveSeminarGroupEvents(seminarGroups []model.SeminarGroup, app *pocketbase.PocketBase) ([]model.Event, error) {
|
|
var toBeSavedEvents model.Events
|
|
var savedRecords model.Events
|
|
|
|
// check if event is already in database and add to toBeSavedEvents if not
|
|
for _, seminarGroup := range seminarGroups {
|
|
for _, event := range seminarGroup.Events {
|
|
event = event.SetCourse(seminarGroup.Course)
|
|
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, seminarGroup.Course, app)
|
|
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !existsInDatabase && !alreadyAddedToSave {
|
|
toBeSavedEvents = append(toBeSavedEvents, event)
|
|
}
|
|
}
|
|
}
|
|
|
|
// create record for each event that's not already in the database
|
|
for _, event := range toBeSavedEvents {
|
|
event.MarkAsNew()
|
|
// auto mapping for event fields to record fields
|
|
err := app.Dao().Save(&event)
|
|
if err != nil {
|
|
return nil, err
|
|
} else {
|
|
savedRecords = append(savedRecords, event)
|
|
}
|
|
}
|
|
|
|
return savedRecords, nil
|
|
}
|
|
|
|
func SaveEvents(events []model.Event, app *pocketbase.PocketBase) ([]model.Event, error) {
|
|
var toBeSavedEvents model.Events
|
|
var savedRecords model.Events
|
|
|
|
// check if event is already in database and add to toBeSavedEvents if not
|
|
for _, event := range events {
|
|
existsInDatabase, err := findEventByDayWeekStartEndNameCourse(event, event.Course, app)
|
|
alreadyAddedToSave := toBeSavedEvents.Contains(event)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !existsInDatabase && !alreadyAddedToSave {
|
|
toBeSavedEvents = append(toBeSavedEvents, event)
|
|
}
|
|
}
|
|
|
|
// create record for each event that's not already in the database
|
|
for _, event := range toBeSavedEvents {
|
|
event.MarkAsNew()
|
|
// auto mapping for event fields to record fields
|
|
err := app.Dao().Save(&event)
|
|
if err != nil {
|
|
return nil, err
|
|
} else {
|
|
savedRecords = append(savedRecords, event)
|
|
}
|
|
}
|
|
return savedRecords, nil
|
|
}
|
|
|
|
// check if event is already in database and return true if it is and false if it's not
|
|
func findEventByDayWeekStartEndNameCourse(event model.Event, course string, app *pocketbase.PocketBase) (bool, error) {
|
|
|
|
var dbEvent model.Event
|
|
|
|
err := app.Dao().DB().Select("*").From("events").
|
|
Where(dbx.NewExp(
|
|
"Day = {:day} AND "+
|
|
"Week = {:week} AND "+
|
|
"Start = {:start} AND "+
|
|
"End = {:end} AND "+
|
|
"Name = {:name} AND "+
|
|
"course = {:course} AND "+
|
|
"Prof = {:prof} AND "+
|
|
"Rooms = {:rooms} AND "+
|
|
"EventType = {:eventType}",
|
|
dbx.Params{
|
|
"day": event.Day,
|
|
"week": event.Week,
|
|
"start": event.Start,
|
|
"end": event.End,
|
|
"name": event.Name,
|
|
"course": course,
|
|
"prof": event.Prof,
|
|
"rooms": event.Rooms,
|
|
"eventType": event.EventType}),
|
|
).One(&dbEvent)
|
|
|
|
if err != nil {
|
|
if err.Error() == "sql: no rows in result set" {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
} else {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
func buildIcalQueryForModules(modules []model.FeedCollection) dbx.Expression {
|
|
|
|
// build where conditions for each module
|
|
|
|
//first check if modules is empty
|
|
if len(modules) == 0 {
|
|
return dbx.HashExp{}
|
|
}
|
|
|
|
//second check if modules has only one element
|
|
if len(modules) == 1 {
|
|
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.HashExp{"uuid": module.UUID}
|
|
wheres = append(wheres, where)
|
|
}
|
|
|
|
// Use dbx.And or dbx.Or to combine the where conditions as needed
|
|
where := dbx.Or(wheres...)
|
|
|
|
return where
|
|
|
|
}
|
|
|
|
// GetPlanForModules returns all events for the given modules with the given course
|
|
// used for the ical feed
|
|
func GetPlanForModules(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (model.Events, error) {
|
|
|
|
var events model.Events
|
|
|
|
modulesArray := make([]model.FeedCollection, 0, len(modules))
|
|
for _, value := range modules {
|
|
modulesArray = append(modulesArray, value)
|
|
}
|
|
|
|
// iterate over modules in 100 batch sizes
|
|
for i := 0; i < len(modules); i += 100 {
|
|
var moduleBatch []model.FeedCollection
|
|
|
|
if i+100 > len(modules) {
|
|
moduleBatch = modulesArray[i:]
|
|
} else {
|
|
moduleBatch = modulesArray[i : i+100]
|
|
}
|
|
|
|
var selectedModulesQuery = buildIcalQueryForModules(moduleBatch)
|
|
// get all events from event records in the events collection
|
|
err := app.Dao().DB().Select("*").From("events").Where(selectedModulesQuery).OrderBy("start").All(&events)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester string) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
// get all events from event records in the events collection
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).GroupBy("Name").Distinct(true).All(&events)
|
|
if err != nil {
|
|
slog.Error("Error while getting events from database: ", "error", err)
|
|
return nil, fmt.Errorf("error while getting events from database for course %s and semester %s", course, semester)
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetAllModulesDistinctByNameAndCourse(app *pocketbase.PocketBase) ([]model.ModuleDTO, error) {
|
|
var modules []model.ModuleDTO
|
|
|
|
err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules)
|
|
if err != nil {
|
|
slog.Error("Error while getting events from database: ", "error", err)
|
|
return nil, fmt.Errorf("error while getting events distinct by name and course from data")
|
|
}
|
|
|
|
return modules, nil
|
|
}
|
|
|
|
func DeleteAllEventsByCourse(app *pocketbase.PocketBase, course string, semester string) error {
|
|
_, err := app.Dao().DB().Delete("events", dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Execute()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeleteAllEventsBySemesterWithoutCourse(app *pocketbase.PocketBase, course string, semester string) error {
|
|
_, err := app.Dao().DB().Delete("events", dbx.NewExp("course != {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).Execute()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeleteAllEvents(app *pocketbase.PocketBase) error {
|
|
|
|
_, err := app.Dao().DB().Delete("events", dbx.NewExp("1=1")).Execute()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func FindModuleByUUID(app *pocketbase.PocketBase, uuid string) (model.Module, error) {
|
|
var module model.Module
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("uuid = {:uuid}", dbx.Params{"uuid": uuid})).One(&module)
|
|
if err != nil {
|
|
return model.Module{}, err
|
|
}
|
|
|
|
return module, nil
|
|
}
|
|
|
|
func FindAllEventsByModule(app *pocketbase.PocketBase, module model.Module) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:moduleName} AND course = {:course}", dbx.Params{"moduleName": module.Name, "course": module.Course})).All(&events)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetAllModulesByNameAndDateRange(app *pocketbase.PocketBase, name string, startDate time.Time, endDate time.Time) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND Start >= {:startDate} AND End <= {:endDate}", dbx.Params{"name": name, "startDate": startDate, "endDate": endDate})).All(&events)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
// GetEventsThatCollideWithTimeRange returns all events that collide with the given time range
|
|
// we have events with start and end in the database, we want to get all events that collide with the given time range
|
|
// we have 4 cases:
|
|
// 1. event starts before the given time range and ends after the given time range
|
|
// 2. event starts after the given time range and ends before the given time range
|
|
// 3. event starts before the given time range and ends before the given time range
|
|
// 4. event starts after the given time range and ends after the given time range
|
|
func GetEventsThatCollideWithTimeRange(app *pocketbase.PocketBase, from time.Time, to time.Time) (model.Events, error) {
|
|
var fromTypeTime, _ = types.ParseDateTime(from)
|
|
var toTypeTime, _ = types.ParseDateTime(to)
|
|
|
|
events1, err := GetEventsThatStartBeforeAndEndAfter(app, fromTypeTime, toTypeTime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
events2, err := GetEventsThatStartAfterAndEndBefore(app, fromTypeTime, toTypeTime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
events3, err := GetEventsThatStartBeforeAndEndBefore(app, fromTypeTime, toTypeTime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
events4, err := GetEventsThatStartAfterAndEndAfter(app, fromTypeTime, toTypeTime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var events model.Events
|
|
events = append(events, events1...)
|
|
events = append(events, events2...)
|
|
events = append(events, events3...)
|
|
events = append(events, events4...)
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetEventsThatStartBeforeAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start <= {:startDate} AND End >= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).Distinct(true).All(&events)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetEventsThatStartAfterAndEndBefore(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End <= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetEventsThatStartBeforeAndEndBefore(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
|
var events model.Events
|
|
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start <= {:startDate} AND End <= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|
|
|
|
func GetEventsThatStartAfterAndEndAfter(app *pocketbase.PocketBase, from types.DateTime, to types.DateTime) (model.Events, error) {
|
|
var events model.Events
|
|
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Start >= {:startDate} AND End >= {:endDate} AND Start <= {:endDate} AND End >= {:startDate}", dbx.Params{"startDate": from, "endDate": to})).All(&events)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return events, nil
|
|
}
|