Merge remote-tracking branch 'htwk-org/development'

# Conflicts:
#	backend/go.mod
#	frontend/index.html
#	frontend/package-lock.json
#	frontend/package.json
#	frontend/public/themes/lara-dark-blue/theme.css
#	frontend/public/themes/lara-dark-blue/theme.css.map
#	frontend/public/themes/lara-light-blue/theme.css
#	frontend/public/themes/lara-light-blue/theme.css.map
#	frontend/src/App.vue
#	frontend/src/components/DarkModeSwitcher.vue
#	frontend/src/i18n/index.ts
#	frontend/src/main.ts
#	frontend/src/router/index.ts
#	frontend/src/view/CalendarLink.vue
#	frontend/src/view/edit/EditCalendar.vue
#	frontend/vite.config.ts
#	reverseproxy.conf
#	reverseproxy.local.conf
#	services/data-manager/main.go
#	services/data-manager/model/roomOccupancyModel.go
#	services/data-manager/service/addRoute.go
#	services/data-manager/service/addSchedule.go
#	services/data-manager/service/db/dbGroups.go
#	services/data-manager/service/feed/feedFunctions.go
#	services/data-manager/service/fetch/sport/sportFetcher.go
#	services/data-manager/service/fetch/v1/fetchSeminarEventService.go
#	services/data-manager/service/fetch/v1/fetchSeminarGroupService.go
#	services/data-manager/service/fetch/v2/fetcher.go
#	services/data-manager/service/functions/filter.go
#	services/data-manager/service/functions/filter_test.go
#	services/data-manager/service/functions/time/parse.go
#	services/data-manager/service/room/roomService.go
#	services/data-manager/service/room/roomService_test.go
#	services/go.sum
#	services/ical/service/connector/grpc/client.go
This commit is contained in:
Elmar Kresse
2024-07-24 12:16:51 +02:00
188 changed files with 9639 additions and 26900 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
.DS_Store
workspace.xml
**/.idea/
.sass-cache/
.sass-cache/
/services/pb_data/

View File

@ -18,6 +18,7 @@ stages:
- lint
- build
- test
- sonarqube-check
- oci-build
- deploy
- deploy-dev # New stage for development deployment
@ -33,33 +34,82 @@ lint-frontend:
- npm i
- npm run lint-no-fix
lint-backend:
lint-data-manager:
stage: lint
image: golangci/golangci-lint:latest
rules:
- changes:
- backend/**/*
- services/data-manager/**/*
script:
- cd backend
- cd services/data-manager
- go mod download
- golangci-lint --version
- golangci-lint run -v --skip-dirs=migrations --timeout=5m
lint-ical:
stage: lint
image: golangci/golangci-lint:latest
rules:
- changes:
- services/ical/**/*
script:
- cd services/ical
- go mod download
- golangci-lint --version
- golangci-lint run -v --skip-dirs=migrations --timeout=5m
build-backend:
build-data-manager:
image: golang:alpine
stage: build
rules:
- changes:
- backend/**/*
- services/data-manager/**/*
script:
- cd backend
- cd services/data-manager
- go build -o htwkalender
artifacts:
paths:
- backend/htwkalender
- backend/go.sum
- backend/go.mod
- data-manager/htwkalender
- data-manager/go.sum
- data-manager/go.mod
build-ical:
image: golang:alpine
stage: build
rules:
- changes:
- services/ical/**/*
script:
- cd services/ical
- go build -o htwkalender-ical
artifacts:
paths:
- data-manager/htwkalender-ical
- data-manager/go.sum
- data-manager/go.mod
sonarqube-data-manager:
stage: sonarqube-check
image:
name: sonarsource/sonar-scanner-cli:5.0
entrypoint: [""]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- cd services/data-manager
- sonar-scanner
allow_failure: true
only:
- merge_requests
- master
- main
- develop
build-frontend:
image: node:lts
@ -75,17 +125,29 @@ build-frontend:
paths:
- frontend/build
test-backend:
test-data-manager:
image: golang:alpine
stage: test
rules:
- changes:
- backend/**/*
- services/data-manager/**/*
script:
- cd backend
- cd services/data-manager
- go test -v ./...
dependencies:
- build-backend
- build-data-manager
test-ical:
image: golang:alpine
stage: test
rules:
- changes:
- services/ical/**/*
script:
- cd services/ical
- go test -v ./...
dependencies:
- build-ical
test-frontend:
image: node:lts
@ -100,7 +162,7 @@ test-frontend:
dependencies:
- lint-frontend
build-backend-image:
build-data-manager-image:
stage: oci-build
image: docker:latest
services:
@ -108,7 +170,7 @@ build-backend-image:
tags:
- image
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-backend
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-data-manager
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
@ -116,12 +178,35 @@ build-backend-image:
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --pull -t $IMAGE_TAG -f ./backend/Dockerfile --target prod ./backend
- docker build --pull -t $IMAGE_TAG -f ./services/data-manager/Dockerfile --target prod ./services
- docker push $IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
changes:
- backend/**/*
- services/data-manager/**/*
build-ical-image:
stage: oci-build
image: docker:latest
services:
- docker:dind
tags:
- image
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-ical
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "/certs/client"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --pull -t $IMAGE_TAG -f ./services/ical/Dockerfile --target prod ./services
- docker push $IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
changes:
- services/ical/**/*
build-frontend-image:
stage: oci-build
@ -191,6 +276,7 @@ deploy-all:
ssh -p $CI_SSH_PORT -o StrictHostKeyChecking=no -o LogLevel=ERROR $CI_SSH_USER@$CI_SSH_HOST
"cd /home/$CI_SSH_USER/docker/htwkalender/ &&
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY &&
docker compose -f ./docker-compose.prod.yml down && docker compose -f ./docker-compose.prod.yml up -d --remove-orphans && docker logout"
docker compose -f ./docker-compose.prod.yml down && docker compose -f ./docker-compose.prod.yml up -d --remove-orphans && docker logout &&
docker exec --user root htwkalender-htwkalender-frontend-1 /bin/sh -c \"echo 'google-site-verification: $GOOGLE_VERIFICATION.html' > ./$GOOGLE_VERIFICATION.html\" "
rules:
- if: $CI_COMMIT_BRANCH == "main"

View File

@ -1,38 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
**Additional context**
Add any other context about the problem here.
/label ~bug

View File

@ -1,20 +1,13 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
**Additional context**
Add any other context or screenshots about the feature request here.
/label ~feature

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,101 +0,0 @@
module htwkalender
go 1.21
require (
github.com/PuerkitoBio/goquery v1.8.1
github.com/google/uuid v1.5.0
github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.20.5
golang.org/x/net v0.20.0
)
require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.49.20 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/ganigeorgiev/fexpr v0.4.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect
go.opencensus.io v0.24.0 // indirect
gocloud.dev v0.36.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.153.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.37.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.27.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)

View File

@ -1,199 +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 service
import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"htwkalender/service/db"
"htwkalender/service/ical"
"io"
"net/http"
)
func addFeedRoutes(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/createFeed",
Handler: func(c echo.Context) error {
requestBody, _ := io.ReadAll(c.Request().Body)
result, err := ical.CreateIndividualFeed(requestBody, app)
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to create individual feed")
}
return c.JSON(http.StatusOK, result)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
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/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
result, err := ical.Feed(app, token)
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to get feed")
}
var responseWriter = c.Response().Writer
responseWriter.Header().Set("Content-type", "text/calendar")
responseWriter.Header().Set("charset", "utf-8")
responseWriter.Header().Set("Content-Disposition", "inline")
responseWriter.Header().Set("filename", "calendar.ics")
responseWriter.WriteHeader(http.StatusOK)
_, err = responseWriter.Write([]byte(result))
if err != nil {
return c.JSON(http.StatusInternalServerError, "Failed to write feed")
}
c.Response().Writer = responseWriter
return nil
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
err := db.DeleteFeed(app.Dao(), token)
if err != nil {
return c.JSON(http.StatusNotFound, err)
} else {
return c.JSON(http.StatusOK, "Feed deleted")
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodPut,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
return c.JSON(http.StatusOK, "token: "+token)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
// Route for Thunderbird to get calendar server information
// Response is a 200 OK without additional content
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: "PROPFIND",
Path: "/",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
// Route for Thunderbird to get calendar server information
// Response is a 200 OK without additional content
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: "PROPFIND",
Path: "/.well-known/caldav",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: "PROPFIND",
Path: "/api/feed",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodHead,
Path: "/api/feed",
Handler: func(c echo.Context) error {
return c.JSON(http.StatusOK, "")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
}

View File

@ -1,54 +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 events
import (
"github.com/pocketbase/pocketbase"
"htwkalender/service/db"
"htwkalender/service/functions"
)
func GetAllCourses(app *pocketbase.PocketBase) []string {
return db.GetAllCourses(app)
}
func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []string {
return db.GetAllCoursesForSemester(app, semester)
}
func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester string) ([]string, error) {
courses, err := db.GetAllCoursesForSemesterWithEvents(app, semester)
if err != nil {
return nil, err
} else {
// remove empty courses like " " or ""
courses = removeEmptyCourses(courses)
return courses, nil
}
}
// removeEmptyCourses removes empty courses from the list of courses
func removeEmptyCourses(courses []string) []string {
var filteredCourses []string
for index, course := range courses {
if !functions.OnlyWhitespace(course) || len(course) != 0 {
filteredCourses = append(filteredCourses, courses[index])
}
}
return filteredCourses
}

View File

@ -1,67 +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 v2
import (
"github.com/pocketbase/pocketbase/tools/types"
"golang.org/x/net/html"
"htwkalender/model"
"htwkalender/service/date"
"htwkalender/service/functions"
"strings"
)
func toEvents(tables [][]*html.Node, days []string) []model.Event {
var events []model.Event
for table := range tables {
for row := range tables[table] {
tableData := findTableData(tables[table][row])
if len(tableData) > 0 {
start, _ := types.ParseDateTime(date.CreateTimeFromHourAndMinuteString(getTextContent(tableData[1])))
end, _ := types.ParseDateTime(date.CreateTimeFromHourAndMinuteString(getTextContent(tableData[2])))
courses := getTextContent(tableData[7])
name := getTextContent(tableData[3])
if functions.OnlyWhitespace(name) {
name = "Sonderveranstaltung"
}
if len(courses) > 0 {
for _, course := range strings.Split(courses, " ") {
events = append(events, model.Event{
Day: days[table],
Week: getTextContent(tableData[0]),
Start: start,
End: end,
Name: name,
EventType: getTextContent(tableData[4]),
Notes: getTextContent(tableData[5]),
Prof: getTextContent(tableData[6]),
Rooms: getTextContent(tableData[8]),
BookedAt: getTextContent(tableData[10]),
Course: strings.TrimSpace(course),
})
}
}
}
}
}
return events
}

View File

@ -1,103 +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 functions
import (
"reflect"
"strings"
"testing"
)
func Test_Filter_number(t *testing.T) {
type args struct {
ss []int
test func(int) bool
}
tests := []struct {
name string
args args
wantRet []int
}{
{
"filter even numbers",
args{
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
func(i int) bool {
return i%2 == 0
},
},
[]int{2, 4, 6, 8, 10},
},
{
"filter smaller than 5",
args{
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
func(i int) bool {
return i < 5
},
},
[]int{1, 2, 3, 4},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotRet := Filter(tt.args.ss, tt.args.test); !reflect.DeepEqual(gotRet, tt.wantRet) {
t.Errorf("filter() = %v, want %v", gotRet, tt.wantRet)
}
})
}
}
func Test_Filter_string(t *testing.T) {
type args struct {
ss []string
test func(string) bool
}
tests := []struct {
name string
args args
wantRet []string
}{
{
"filter contains a",
args{
[]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
func(i string) bool {
return strings.Contains(i, "a")
},
},
[]string{"a"},
},
{
"filter starts with prefix 'a'",
args{
[]string{"alpha", "beta", "a", "ab", "ac", "delta"},
func(i string) bool {
return strings.HasPrefix(i, "a")
},
},
[]string{"alpha", "a", "ab", "ac"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotRet := Filter(tt.args.ss, tt.args.test); !reflect.DeepEqual(gotRet, tt.wantRet) {
t.Errorf("filter() = %v, want %v", gotRet, tt.wantRet)
}
})
}
}

View File

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

View File

@ -15,13 +15,22 @@
#along with this program. If not, see <https://www.gnu.org/licenses/>.
services:
htwkalender-backend:
image: DOCKER_REGISTRY_REPO-backend # DOCKER_REGISTRY_REPO will be replaced by CI
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
htwkalender-data-manager:
image: DOCKER_REGISTRY_REPO-data-manager # DOCKER_REGISTRY_REPO will be replaced by CI
command: "--http=0.0.0.0:8090 --dir=/htwkalender-data-manager/data/pb_data"
pull_policy: always
restart: always
volumes:
- pb_data:/htwkalender/data
- pb_data:/htwkalender-data-manager/data
networks:
- "net"
htwkalender-ical:
image: DOCKER_REGISTRY_REPO-ical # DOCKER_REGISTRY_REPO will be replaced by CI
pull_policy: always
restart: always
environment:
- DATA_MANAGER_URL=htwkalender-data-manager
networks:
- "net"
@ -30,7 +39,8 @@ services:
pull_policy: always
restart: always
depends_on:
- htwkalender-backend
- htwkalender-data-manager
- htwkalender-ical
networks:
- "net"
@ -42,8 +52,9 @@ services:
- ./dev_htwkalender_de.pem:/opt/bitnami/nginx/conf/dev_htwkalender_de.pem
- ./dev_htwkalender_de.key.pem:/opt/bitnami/nginx/conf/dev_htwkalender_de.key.pem
depends_on:
- htwkalender-backend
- htwkalender-data-manager
- htwkalender-frontend
- htwkalender-ical
ports:
- "443:443"
- "80:80"

View File

@ -15,13 +15,22 @@
#along with this program. If not, see <https://www.gnu.org/licenses/>.
services:
htwkalender-backend:
image: DOCKER_REGISTRY_REPO-backend # DOCKER_REGISTRY_REPO will be replaced by CI
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
htwkalender-data-manager:
image: DOCKER_REGISTRY_REPO-data-manager # DOCKER_REGISTRY_REPO will be replaced by CI
command: "--http=0.0.0.0:8090 --dir=/htwkalender-data-manager/data/pb_data"
pull_policy: always
restart: always
volumes:
- pb_data:/htwkalender/data
- pb_data:/htwkalender-data-manager/data
networks:
- "net"
htwkalender-ical:
image: DOCKER_REGISTRY_REPO-ical # DOCKER_REGISTRY_REPO will be replaced by CI
pull_policy: always
restart: always
environment:
- DATA_MANAGER_URL=htwkalender-data-manager
networks:
- "net"
@ -30,7 +39,7 @@ services:
pull_policy: always
restart: always
depends_on:
- htwkalender-backend
- htwkalender-data-manager
networks:
- "net"
@ -44,7 +53,7 @@ services:
- ./cal.htwk-leipzig.de.pem:/opt/bitnami/nginx/conf/cal.htwk-leipzig.de.pem
- ./cal.htwk-leipzig.de.key.pem:/opt/bitnami/nginx/conf/cal.htwk-leipzig.de.key.pem
depends_on:
- htwkalender-backend
- htwkalender-data-manager
- htwkalender-frontend
ports:
- "443:443"

View File

@ -15,17 +15,26 @@
#along with this program. If not, see <https://www.gnu.org/licenses/>.
services:
htwkalender-backend:
htwkalender-data-manager:
build:
dockerfile: Dockerfile
context: ./backend
dockerfile: ./data-manager/Dockerfile
context: ./services
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-manager/data/pb_data"
#ports:
# - "8090:8090"
volumes:
- pb_data:/htwkalender/data # for production with volume
# - ./backend:/htwkalender/data # for development with bind mount from project directory
- pb_data:/htwkalender-data-manager/data # for production with volume
# - ./data-manager:/htwkalender/data # for development with bind mount from project directory
user: "ical"
htwkalender-ical:
build:
dockerfile: ./ical/Dockerfile
context: ./services
target: dev # prod
environment:
- DATA_MANAGER_URL=htwkalender-data-manager
htwkalender-frontend:
build:
@ -46,7 +55,7 @@ services:
- ./local-cert/dev_htwkalender_de.pem:/opt/bitnami/nginx/conf/dev_htwkalender_de.pem
- ./local-cert/dev_htwkalender_de.key.pem:/opt/bitnami/nginx/conf/dev_htwkalender_de.key.pem
depends_on:
- htwkalender-backend
- htwkalender-data-manager
- htwkalender-frontend
ports:
- "80:80"

1
frontend/.gitignore vendored
View File

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
.vite-ssg-temp
*.local
# Editor directories and files

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -22,9 +22,10 @@
rel="stylesheet"
href="/themes/lara-light-blue/theme.css"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="app"></div>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -50,7 +50,7 @@ http {
index index.html index.htm;
#necessary to display vue subpage
try_files $uri $uri/ /index.html;
try_files $uri $uri.html $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"build": "vue-tsc && vite-ssg build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"lint-no-fix": "eslint --ext .js,.vue --ignore-path .gitignore src",
@ -20,7 +20,6 @@
"@fullcalendar/timegrid": "^6.1.13",
"@fullcalendar/vue3": "^6.1.13",
"@tanstack/vue-query": "^5.37.1",
"@tanstack/vue-query-devtools": "^5.37.1",
"@types/ical": "^0.8.3",
"@vueuse/core": "^10.9.0",
"bson": "^5.5.1",
@ -39,20 +38,29 @@
"vue-router": "^4.3.2"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@types/node": "^20.12.12",
"@vitejs/plugin-basic-ssl": "^1.1.0",
"@unhead/vue": "^1.9.15",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^12.0.0",
"@tanstack/vue-query-devtools": "^5.28.10",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.26.0",
"prettier": "3.2.1",
"sass": "^1.77.2",
"sass-loader": "^13.3.3",
"terser": "^5.31.0",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"vite-plugin-pwa": "^0.20.0",
"vite-plugin-vue-devtools": "^7.3.1",
"vite-ssg": "^0.23.7",
"vite-ssg-sitemap": "^0.7.1",
"vitest": "^1.6.0",
"vue-router": "^4.4.0",
"vue-tsc": "^1.8.27"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,15 +18,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup>
import MenuBar from "./components/MenuBar.vue";
import { RouteRecordName, RouterView } from "vue-router";
import { RouteRecordName, RouterView, useRoute, useRouter } from "vue-router";
import { useHead, useServerHead, useServerSeoMeta } from "@unhead/vue";
import CalendarPreview from "./components/CalendarPreview.vue";
import moduleStore from "./store/moduleStore.ts";
import { onMounted, provide, ref } from "vue";
import { computed, provide, ref } from "vue";
import { VueQueryDevtools } from "@tanstack/vue-query-devtools";
import settingsStore from "@/store/settingsStore.ts";
import { setTheme } from "@/helpers/theme.ts";
import { usePrimeVue } from "primevue/config";
const primeVue = usePrimeVue();
import { useI18n } from "vue-i18n";
const { t } = useI18n({ useScope: "global" });
const disabledPages = [
"room-finder",
@ -40,6 +44,56 @@ const disabledPages = [
"room-schedule"
];
// Provide canonical link for SEO
const router = useRouter();
const route = useRoute();
const domain = "cal.htwk-leipzig.de"
provide('domain', domain);
const baseUri = "https://" + domain;
const canonical = computed(() => `${baseUri}${router.resolve(route.name ? { name: route.name } : route).path}`);
const title = computed(() => route.meta.label?
`HTWKalender - ${t(String(route.meta.label))}`:
"HTWKalender"
);
const description = computed(() => route.meta.description?
t(String(route.meta.description)):
t("description")
);
useHead({
title: title,
link: [
{ rel: "canonical", href: canonical},
],
meta: [
{ name: "description", content: description},
{ property: "og:description", content: description},
]
});
// SEO optimization
useServerHead({
title: title
});
useServerSeoMeta(
{
title: title,
description: description,
keywords: "HTWK Leipzig, Stundenplan, iCal, freie Räume, Lerngruppen, Sport, Prüfungen",
// openGraph
ogTitle: title,
ogDescription: description,
ogImage: `${baseUri}/img/banner-image.png`,
ogImageType: "image/png",
ogLocale: "de_DE",
ogUrl: canonical,
// twitter
twitterCard: "summary_large_image",
twitterSite: "@HTWKLeipzig",
}
);
const store = moduleStore();
const mobilePage = ref(true);
provide("mobilePage", mobilePage);
@ -49,11 +103,12 @@ const isDisabled = (routeName: RouteRecordName | null | undefined) => {
};
const updateMobile = () => {
if (import.meta.env.SSR) return;
mobilePage.value = window.innerWidth <= 992;
};
updateMobile();
window.addEventListener("resize", updateMobile);
if (!import.meta.env.SSR)window.addEventListener("resize", updateMobile);
const settings = settingsStore;
const emit = defineEmits(["dark-mode-toggled"]);
@ -71,7 +126,7 @@ onMounted(() => {
<template>
<MenuBar />
<RouterView v-slot="{ Component, route }">
<RouterView v-slot="{ Component, route }" class="mb-8">
<transition mode="out-in" name="scale">
<div :key="route.name ?? ''" class="origin-near-top">
<component :is="Component" />

View File

@ -17,8 +17,11 @@
import { Module } from "../model/module.ts";
export async function createIndividualFeed(modules: Module[]): Promise<string> {
if (import.meta.env.SSR) {
return "";
}
try {
const response = await fetch("/api/createFeed", {
const response = await fetch("/api/feed", {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -62,6 +65,9 @@ export async function saveIndividualFeed(
token: string,
modules: Module[],
): Promise<string> {
if (import.meta.env.SSR) {
return "";
}
await fetch("/api/collections/feeds/records/" + token, {
method: "PATCH",
headers: {
@ -81,6 +87,9 @@ export async function saveIndividualFeed(
}
export async function deleteIndividualFeed(token: string): Promise<void> {
if (import.meta.env.SSR) {
return;
}
await fetch("/api/feed?token=" + token, {
method: "DELETE",
})

View File

@ -19,6 +19,9 @@
import { Module } from "../model/module.ts";
export async function fetchCourse(): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const courses: string[] = [];
await fetch("/api/courses")
.then((response) => {
@ -39,6 +42,9 @@ export async function fetchCourse(): Promise<string[]> {
export async function fetchCourseBySemester(
semester: string,
): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const courses: string[] = [];
await fetch("/api/courses/events?semester=" + semester)
.then((response) => {
@ -60,6 +66,9 @@ export async function fetchModulesByCourseAndSemester(
course: string,
semester: string,
): Promise<Module[]> {
if (import.meta.env.SSR) {
return [];
}
const modules: Module[] = [];
await fetch("/api/course/modules?course=" + course + "&semester=" + semester)
.then((response) => {
@ -86,6 +95,9 @@ export async function fetchModulesByCourseAndSemester(
}
export async function fetchAllModules(): Promise<Module[]> {
if (import.meta.env.SSR) {
return [];
}
const modules: Module[] = [];
await fetch("/api/modules")
.then((response) => {

View File

@ -0,0 +1,36 @@
//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[]> {
if (import.meta.env.SSR) {
return [];
}
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;
}

View File

@ -17,7 +17,10 @@
import { Module } from "../model/module";
export async function fetchModule(module: Module): Promise<Module> {
// request to the backend on /api/module with query parameters name as the module name
if (import.meta.env.SSR) {
return new Module("", "", "", "", "", "", "", false, []);
}
// request to the data-manager on /api/module with query parameters name as the module name
const request = new Request("/api/module?uuid=" + module.uuid);
return await fetch(request)

View File

@ -17,6 +17,9 @@
import { AnonymizedEventDTO } from "../model/event.ts";
export async function fetchRoom(): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const rooms: string[] = [];
await fetch("/api/rooms")
.then((response) => {
@ -33,6 +36,9 @@ export async function fetchEventsByRoomAndDuration(
from_date: string,
to_date: string,
): Promise<AnonymizedEventDTO[]> {
if (import.meta.env.SSR) {
return [];
}
const events: AnonymizedEventDTO[] = [];
await fetch(
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,

View File

@ -18,7 +18,10 @@ import { Module } from "../model/module";
import { Calendar } from "../model/calendar";
export async function getCalender(token: string): Promise<Module[]> {
const request = new Request("/api/collections/feeds/records/" + token, {
if (import.meta.env.SSR) {
return [];
}
const request = new Request("/api/feeds/records/" + token, {
method: "GET",
});

View File

@ -14,11 +14,14 @@
//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/>.
// load free rooms as a list of strings form the backend
// load free rooms as a list of strings form the data-manager
export async function requestFreeRooms(
from: string,
to: string,
): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const rooms: string[] = [];
await fetch("/api/rooms/free?from=" + from + "&to=" + to)
.then((response) => {

View File

@ -27,9 +27,10 @@ import {
DataTableRowUnselectEvent,
} from "primevue/datatable";
import { useDialog } from "primevue/usedialog";
import router from "../router";
import { router } from "@/main";
import { fetchModule } from "../api/fetchModule.ts";
import { useI18n } from "vue-i18n";
import { fetchEventTypes } from "../api/fetchEvents.ts";
const dialog = useDialog();
const { t } = useI18n({ useScope: "global" });
@ -39,6 +40,9 @@ if (store.isEmpty()) {
router.replace("/");
}
const eventTypes: Ref<string[]> = ref([]);
const mobilePage = inject("mobilePage") as Ref<boolean>;
const filters = ref({
course: {
@ -51,7 +55,7 @@ const filters = ref({
},
eventType: {
value: null,
matchMode: FilterMatchMode.CONTAINS,
matchMode: FilterMatchMode.IN,
},
prof: {
value: null,
@ -63,7 +67,7 @@ const loadedModules: Ref<Module[]> = ref(new Array(10));
const loadingData = ref(true);
onMounted(() => {
onMounted( () => {
fetchAllModules()
.then(
(data) =>
@ -74,6 +78,10 @@ onMounted(() => {
.finally(() => {
loadingData.value = false;
});
fetchEventTypes().then((data) => {
eventTypes.value = data;
});
});
const ModuleInformation = defineAsyncComponent(
@ -184,16 +192,20 @@ function unselectModule(event: DataTableRowUnselectEvent) {
</Column>
<Column
field="eventType"
filter-field="eventType"
:filter-menu-style="{ width: '10rem' }"
style="min-width: 10rem"
:header="$t('additionalModules.eventType')"
:show-clear-button="false"
:show-filter-menu="false"
>
<template #filter="{ filterModel, filterCallback }">
<InputText
<MultiSelect
v-model="filterModel.value"
type="text"
:options="eventTypes"
class="p-column-filter max-w-10rem"
@input="filterCallback()"
style="min-width: 10rem"
@change="filterCallback()"
/>
</template>
<template v-if="loadingData" #body>

View File

@ -110,13 +110,14 @@ const items = computed(() => [
"
v-bind="props.action"
@click="navigate"
:href="item.route"
>
<span :class="item.icon" />
<span class="ml-2 p-menuitem-label">{{ item.label }}</span>
</a>
</router-link>
<a
v-else
v-else-if="item.url"
:class="
$route.path.includes(item.info)
? 'flex align-items-center active'
@ -128,6 +129,18 @@ const items = computed(() => [
<span :class="item.icon" />
<span class="ml-2 p-menuitem-label">{{ item.label }}</span>
</a>
<span
v-else
:class="
$route.path.includes(item.info)
? 'flex align-items-center active'
: 'flex align-items-center'
"
v-bind="props.action"
>
<span :class="item.icon" />
<span class="ml-2 p-menuitem-label">{{ item.label }}</span>
</span>
</template>
<template #end>
<div class="flex align-items-stretch justify-content-center">

View File

@ -26,7 +26,7 @@ import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts";
import { useI18n } from "vue-i18n";
import allLocales from "@fullcalendar/core/locales-all";
import router from "@/router";
import { router } from "@/main";
import { formatYearMonthDay } from "@/helpers/dates";
import { useQuery } from "@tanstack/vue-query";
import { watch } from "vue";

View File

@ -28,7 +28,7 @@ function setup() {
_i18n = createI18n({
legacy: false,
locale: settingsStore().locale,
fallbackLocale: "en",
fallbackLocale: "de",
messages: {
en,
de,

View File

@ -8,6 +8,7 @@
"faq": "FAQ",
"imprint": "Impressum",
"privacy": "Datenschutz",
"description": "Dein individueller Stundenplan mit Sportevents und Prüfungen. Finde kommende Veranstaltungen oder freie Räume zum Lernen und Arbeiten.",
"english": "Englisch",
"german": "Deutsch",
"japanese": "Japanisch",
@ -29,6 +30,7 @@
"roomSchedule": "Raumbelegung",
"headline": "Raumbelegung",
"detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen",
"description": "Möchtest du schnell checken, ob ein Raum frei ist? Hier kannst du die Belegung der Räume an der HTWK Leipzig einsehen.",
"dropDownSelect": "Bitte wähle einen Raum aus",
"noRoomsAvailable": "Keine Räume verfügbar",
"available": "verfügbar",
@ -38,6 +40,7 @@
"freeRooms": {
"freeRooms": "Freie Räume",
"detail": "Bitte wähle einen Zeitraum aus, um alle Räume ohne Belegung anzuzeigen.",
"description": "Freier Lerngruppenraum gesucht? Hier kannst du alle freien Räume in einem bestimmten Zeitraum an der HTWK Leipzig einsehen.",
"searchByRoom": "Suche nach Räumen",
"pleaseSelectDate": "Bitte wähle ein Datum aus",
"room": "Raum",
@ -54,7 +57,7 @@
},
"moduleInformation": {
"course": "Kurs",
"person": "Dozent",
"person": "Dozent*in",
"semester": "Semester",
"module": "Modul",
"notes": "Hinweis",
@ -76,6 +79,7 @@
}
},
"editCalendarView": {
"description": "Mit deinem Token kannst du deinen HTWKalender jederzeit bearbeiten und sogar ins nächste Semester mitnehmen",
"error": "Fehler",
"invalidToken": "Ungültiger Token",
"headline": "Bearbeite deinen HTWKalender",
@ -114,7 +118,8 @@
"to": " bis ",
"of": " von insgesamt "
},
"eventType": "Ereignistyp"
"eventType": "Ereignistyp",
"scrollToTop": "Nach oben scrollen"
},
"renameModules": {
"reminder": "Erinnerung",
@ -157,17 +162,19 @@
},
"faqView": {
"headline": "Fragen und Antworten",
"description": "Falls du Fragen zum HTWKalender hast, findest du hier Antworten auf häufig gestellte Fragen und unsere Kontaktdaten.",
"firstQuestion": "Wie funktioniert das Kalender erstellen mit dem HTWKalender?",
"firstAnswer": "Die Webseite ermöglicht es deinen HTWK-Stundenplan in eines deiner bevorzugten Kalender-Verwaltungs-Programme (Outlook, Google Kalender, etc.) einzubinden. ",
"secondQuestion": "Wie genau funktioniert das alles?",
"secondAnswer": "Du wählst deinen Studiengang und das gewünschte Semester aus, danach kannst du aus den dazugehörigen Modulen wählen. Dabei kannst du einzelne Module an/abwählen wie \"Feiertage\" oder Wahlpflichtmodule, die du nicht belegst. Im letzten Schritt wird dir der Link mit dem entsprechenden Token für den von dir erstellenten Kalenders angezeigt. Mit diesem Link kannst du den Kalender abonnieren oder herunterladen. ",
"secondAnswer": "Du wählst deinen Studiengang und das gewünschte Semester aus, danach kannst du aus den dazugehörigen Modulen wählen. Dabei kannst du einzelne Module an/abwählen wie \"Feiertage\" oder Wahlpflichtmodule, die du nicht belegst. Im letzten Schritt wird dir der Link mit dem entsprechenden Token für den von dir erstellten Kalenders angezeigt. Mit diesem Link kannst du den Kalender abonnieren oder herunterladen. ",
"thirdQuestion": "Wie kann ich den Kalender abonnieren?",
"thirdAnswer": {
"tabTitle": "Google Calendar",
"google": {
"first": "Erstelle deinen Kalender und kopiere den Link.",
"second": "Bei Google Kalender selbst hast du in der linken Seitenleiste eine Sektion namens \"Weitere Kalender\". Dort klickst du das kleine Pfeil-Icon rechts neben dem Schritzug. Im daraufhin erscheinenden Menü gibt es einen Punkt \"Über URL hinzufügen\", den du anklicken musst. ",
"third": "Füge den kopierten Kalenderlink ein, klicke auf \"Kalender hinzufügen\" und du bist fertig. "
"third": "Füge den kopierten Kalenderlink ein, klicke auf \"Kalender hinzufügen\" und du bist fertig. ",
"fourth": "Der Kalender sollte sich nun in deiner Kalenderübersicht befinden und sich automatisch aktualisieren. Falls nicht, lade die Seite neu um den Kalender zu aktualisieren, oder überprüfe die Einstellungen des Kalenders. "
},
"microsoft_outlook": {
"title": "Mircosoft Outlook",
@ -243,9 +250,23 @@
"seventhQuestion": "Wie lange ist mein Stundenplan bzw. der Link dorthin gültig?",
"seventhAnswer": "Studenpläne sind erstmal nur für die ausgewählten Semester gültig, da durch Wahlpflichtmodule oder deine Planung sich Veränderungen ergeben können. Der Link ist jedoch unbegrenzt gültig und du kannst zu jedem Zeitpunkt deinen Stundenplan aktualisieren.",
"eighthQuestion": "Preis und Entwicklung?",
"eighthAnswer": "Die Entwicklung soll als aktives Git Projekt auch durch die Community verwaltet werden. Eine freie Version des HTWKalenders wird intern auf den Servern des FSR IMN gehostet und ist für alle HTWK-Studenten kostenlos.",
"eighthAnswer": "Die Entwicklung soll als aktives Git Projekt durch die Community verwaltet werden. Eine freie Version des HTWKalenders wird intern auf den Servern des FSR IM gehostet und ist für alle HTWK-Studierenden kostenlos.",
"ninthQuestion": "Wo kann ich den Quellcode einsehen und mitwirken?",
"ninthAnswer": "Wenn du dich für die Entwicklung und den Quelltext interessierst, kannst du jederzeit als HTWK-Student daran mitarbeiten. Quelltext und weitere Informationen findest du im ",
"ninthAnswer": "Wenn du dich für die Entwicklung und den Quelltext interessierst, kannst du jederzeit als HTWK-Student*in daran mitarbeiten. Quelltext und weitere Informationen findest du im ",
"crossPromoQuestion": "Weitere studentische Projekte.",
"crossPromo": {
"teaser": "Der HTWKalender arbeitet mit anderen studentischen Projekten zusammen, um dir das Studium zu erleichtern. Vermutlich gibt es noch mehr, als die hier gelisteten. Schau doch mal dort vorbei!",
"mensa": {
"title": "HTWK Mensa Mate",
"description": "Finde den Sitzplatz deiner Kommilitonen in der HTWK Mensa.",
"link": "https://mensa.heylinus.de/"
},
"htwkarte": {
"title": "HTWKarte",
"description": "Finde dich auf dem Campus zurecht und suche nach Räumen. (in Entwicklung)",
"link": "https://htwkarte.de/"
}
},
"notFound": "Nicht gefunden, wonach du suchst?",
"contact": "Kontakt aufnehmen"
},

View File

@ -8,6 +8,7 @@
"faq": "faq",
"imprint": "imprint",
"privacy": "privacy",
"description": "Your individual timetable with sports events and exams. Find upcoming events or free rooms for studying and working.",
"english": "English",
"german": "German",
"japanese": "Japanese",
@ -29,6 +30,7 @@
"roomSchedule": "room occupancy",
"headline": "room occupancy plan",
"detail": "Please select a room to view the occupancy.",
"description": "Would you like to quickly check whether a room is available? Here you can see the occupancy of the rooms at HTWK Leipzig.",
"dropDownSelect": "please select a room",
"noRoomsAvailable": "no rooms listed",
"available": "available",
@ -38,6 +40,7 @@
"freeRooms": {
"freeRooms": "free rooms",
"detail": "Please select a time period to display rooms that have no occupancy.",
"description": "Looking for a free study group room? Here you can see all available rooms at HTWK Leipzig in a specific period.",
"searchByRoom": "search by room",
"pleaseSelectDate": "please select a date",
"room": "room",
@ -76,6 +79,7 @@
}
},
"editCalendarView": {
"description": "With your token, you can edit your HTW calendar at any time and even take it with you into the next semester",
"error": "error",
"invalidToken": "invalid token",
"headline": "edit your HTWKalender",
@ -114,7 +118,8 @@
"to": " to ",
"of": " of "
},
"eventType": "event type"
"eventType": "event type",
"scrollToTop": "scroll to top"
},
"renameModules": {
"reminder": "reminder",
@ -157,6 +162,7 @@
},
"faqView": {
"headline": "faq",
"description": "If you have any questions about the HTWKalender, you will find answers to frequently asked questions and our contact details here.",
"firstQuestion": "How does calendar creation work with HTWKalender?",
"firstAnswer": "The website allows you to integrate your HTWK timetable into one of your preferred calendar management programs (Outlook, Google Calendar, etc.).",
"secondQuestion": "How does it all work exactly?",
@ -167,7 +173,8 @@
"google": {
"first": "Create your calendar and copy the link.",
"second": "In Google Calendar itself, in the left sidebar, there is a section called 'Other calendars.' There, click the small arrow icon to the right of the text. In the menu that appears, there is an option 'Add by URL' that you need to click.",
"third": "Paste the copied calendar link, click 'Add Calendar,' and you're done."
"third": "Paste the copied calendar link, click 'Add Calendar,' and you're done.",
"fourth": "The calendar should now be in your calendar overview and update automatically. If not, reload the page to refresh the calendar or check the calendar settings."
},
"microsoft_outlook": {
"title": "Microsoft Outlook",
@ -246,6 +253,20 @@
"eighthAnswer": "The development should also be managed by the community as an active Git project. A free version of the HTW calendar is hosted internally on the servers of the FSR IMN and is free of charge for all HTWK students.",
"ninthQuestion": "Where could i find the source code?",
"ninthAnswer": "If you want to contribute, you can do so at any time if you are a HTWK student. The source code is available on ",
"crossPromoQuestion": "More student projects.",
"crossPromo": {
"teaser": "The HTWKalender collaborates with other student projects to make your studies easier. There are probably more than those listed here. Check them out!",
"mensa": {
"title": "HTWK Mensa Mate",
"description": "Find the seating location of your fellow students in the HTWK Mensa Academica.",
"link": "https://mensa.heylinus.de/"
},
"htwkarte": {
"title": "HTWKarte",
"description": "Find your way around the campus and search for rooms. (in development)",
"link": "https://htwkarte.de/"
}
},
"notFound": "Not finding what you're looking for?",
"contact": "Get in touch"
},

View File

@ -16,10 +16,11 @@
import "source-sans/source-sans-3.css";
import { createApp } from "vue";
import { ViteSSG } from "vite-ssg";
import "./style.css";
import App from "./App.vue";
import PrimeVue from "primevue/config";
import Avatar from "primevue/avatar";
import Badge from "primevue/badge";
import Button from "primevue/button";
import Dropdown from "primevue/dropdown";
@ -35,7 +36,7 @@ import OverlayPanel from "primevue/overlaypanel";
import ToggleButton from "primevue/togglebutton";
import "primeicons/primeicons.css";
import "primeflex/primeflex.css";
import router from "./router";
import routes from "./router";
import SpeedDial from "primevue/speeddial";
import TabView from "primevue/tabview";
import TabPanel from "primevue/tabpanel";
@ -56,13 +57,46 @@ import Skeleton from "primevue/skeleton";
import Calendar from "primevue/calendar";
import i18n from "./i18n";
import { VueQueryPlugin } from "@tanstack/vue-query";
import { Router } from "vue-router";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
polyfillCountryFlagEmojis();
const app = createApp(App);
const pinia = createPinia();
var router : Router;
export const createApp = ViteSSG(
App,
{
base: import.meta.env.BASE_URL,
routes: routes.routes,
},
(ctx) => {
const { app } = ctx;
const pinia = createPinia();
app.use(pinia);
router = ctx.router;
router.beforeEach(async (to, from) => {
if (import.meta.env.SSR) {
return;
}
// External redirect
if (to.matched.some((record) => record.meta.redirect)) {
window.location.replace(to.meta.redirect as string);
return;
}
const newLocale = to.params.locale;
const prevLocale = from.params.locale;
// If the locale hasn't changed, do nothing
if (newLocale === prevLocale) {
return;
}
i18n.setLocale(newLocale);
});
pinia.use(piniaPluginPersistedstate);
@ -83,6 +117,7 @@ app.use(pinia);
app.use(DialogService);
i18n.setup();
app.use(i18n.vueI18n);
app.component("Avatar", Avatar);
app.component("Badge", Badge);
app.component("Button", Button);
app.component("Menu", Menu);
@ -111,5 +146,7 @@ app.component("Checkbox", Checkbox);
app.component("Skeleton", Skeleton);
app.component("Calendar", Calendar);
app.component("OverlayPanel", OverlayPanel);
},
)
app.mount("#app");
export {router}

View File

@ -14,7 +14,7 @@
//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/>.
import { createRouter, createWebHistory } from "vue-router";
import { createMemoryHistory, RouterOptions, createWebHistory } from "vue-router";
const Faq = () => import("../components/FaqPage.vue");
const AdditionalModules = () => import("../view/AdditionalModules.vue");
@ -31,13 +31,22 @@ const FreeRooms = () => import("../view/FreeRooms.vue");
const CalenderViewer = () => import("../view/UserCalendar.vue");
const SettingsView = () => import("../view/SettingsView.vue");
const NotFound = () => import("../view/NotFound.vue");
const AdditionalModules = () => import("../view/create/AdditionalModules.vue");
const CalendarLink = () => import("../view/CalendarLink.vue");
const RenameModules = () => import("../view/create/RenameModules.vue");
const RoomFinder = () => import("../view/rooms/RoomFinder.vue");
const FreeRooms = () => import("../view/rooms/FreeRooms.vue");
const EditCalendarView = () => import("../view/edit/EditCalendar.vue");
const EditAdditionalModules = () =>
import("../view/edit/EditAdditionalModules.vue");
const EditModules = () => import("../view/edit/EditModules.vue");
const FaqView = () => import("../view/FaqView.vue");
import i18n from "../i18n";
import settingsStore from "@/store/settingsStore.ts";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
const routes : RouterOptions = {
history: import.meta.env.SSR ? createMemoryHistory(import.meta.env.BASE_URL) : createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
@ -53,11 +62,18 @@ const router = createRouter({
path: "/calendar/create",
name: "calendar-create",
component: CourseSelection,
meta: {
label: "",
},
},
{
path: "/rooms/occupancy",
name: "room-schedule",
component: RoomFinder,
meta: {
label: "roomFinderPage.roomSchedule",
description: "roomFinderPage.description",
},
},
{
path: "/rooms/occupancy/offline",
@ -68,6 +84,10 @@ const router = createRouter({
path: "/rooms/free",
name: "free-rooms",
component: FreeRooms,
meta: {
label: "freeRooms.freeRooms",
description: "freeRooms.description",
},
},
{
path: "/calendar/view",
@ -77,55 +97,80 @@ const router = createRouter({
{
path: "/faq",
name: "faq",
component: Faq,
component: FaqView,
meta: {
label: "faq",
description: "faqView.description",
}
},
{
path: "/additional-modules",
name: "additional-modules",
component: AdditionalModules,
meta: {
label: "createCalendar",
}
},
{
path: "/edit-additional-modules",
name: "edit-additional-modules",
component: EditAdditionalModules,
meta: {
label: "editCalendar",
description: "editCalendarView.description"
}
},
{
path: "/edit-calendar",
name: "edit-calendar",
component: EditModules,
meta: {
label: "editCalendar",
description: "editCalendarView.description"
}
},
{
path: "/calendar-link",
name: "calendar-link",
component: CalendarLink,
meta: {
label: "createCalendar"
}
},
{
path: "/calendar/edit",
name: "edit",
component: EditCalendarView,
meta: {
label: "editCalendar",
description: "editCalendarView.description"
}
},
{
path: "/privacy-policy",
name: "privacy-policy",
component: Faq,
beforeEnter() {
window.location.href =
"https://www.htwk-leipzig.de/hochschule/kontakt/datenschutzerklaerung/";
},
component: {},
meta: {
label: "privacy",
redirect: "https://www.htwk-leipzig.de/hochschule/kontakt/datenschutzerklaerung/",
}
},
{
path: "/imprint",
name: "imprint",
component: Faq,
beforeEnter() {
window.location.href =
"https://www.htwk-leipzig.de/hochschule/kontakt/impressum/";
},
component: {},
meta: {
label: "imprint",
redirect: "https://www.htwk-leipzig.de/hochschule/kontakt/impressum/",
}
},
{
path: "/rename-modules",
name: "rename-modules",
component: RenameModules,
meta: {
label: "createCalendar"
}
},
{
path: "/settings",
@ -138,24 +183,6 @@ const router = createRouter({
component: NotFound, // Replace with your NotFound component
},
],
});
};
router.beforeEach(async (to, from, next) => {
const newLocale = to.params.locale;
const prevLocale = from.params.locale;
// If the locale hasn't changed, do nothing
if (!(newLocale === prevLocale)) {
i18n.setLocale(newLocale);
}
const userSettings = settingsStore();
const defaultPath = userSettings.defaultPage || "/home";
if (to.path === "/") {
next(defaultPath.value);
} else {
next();
}
});
export default router;
export default routes;

View File

@ -17,16 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts" setup>
import tokenStore from "../store/tokenStore.ts";
import tokenStore from "@/store/tokenStore.ts";
import { useToast } from "primevue/usetoast";
import { computed, onMounted } from "vue";
import router from "../router";
import { computed, inject, onMounted } from "vue";
import { router } from "@/main";
import { useI18n } from "vue-i18n";
const { t } = useI18n({ useScope: "global" });
const toast = useToast();
const domain = window.location.hostname;
const domain = import.meta.env.SSR ? inject<string>("domain")! : window.location.hostname;
const getLink = () =>
"https://" + domain + "/api/feed?token=" + tokenStore().token;
@ -36,7 +36,7 @@ const show = () => {
severity: "info",
summary: t("calendarLink.copyToastSummary"),
detail: t("calendarLink.copyToastNotification"),
life: 3000,
life: 3000
});
};
@ -57,7 +57,7 @@ function copyToClipboard() {
severity: "error",
summary: t("calendarLink.copyToastError"),
detail: t("calendarLink.copyToastErrorDetail"),
life: 3000,
life: 3000
});
});
}
@ -65,14 +65,14 @@ function copyToClipboard() {
const forwardToGoogle = () => {
window.open(
"https://calendar.google.com/calendar/u/0/r?cid=" +
encodeURI(getLink().replace("https://", "http://")),
encodeURI(getLink().replace("https://", "http://"))
);
};
const forwardToMicrosoft = () => {
window.open(
"https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&name=HTWK%20Kalender&url=" +
encodeURI(getLink()),
encodeURI(getLink())
);
};
@ -88,22 +88,22 @@ const actions = computed(() => [
{
label: t("calendarLink.copyToClipboard"),
icon: "pi pi-copy",
command: copyToClipboard,
command: copyToClipboard
},
{
label: t("calendarLink.toGoogleCalendar"),
icon: "pi pi-google",
command: forwardToGoogle,
command: forwardToGoogle
},
{
label: t("calendarLink.toMicrosoftCalendar"),
icon: "pi pi-microsoft",
command: forwardToMicrosoft,
command: forwardToMicrosoft
},
{
label: t("calendarLink.toHTWKalendar"),
icon: "pi pi-home",
command: forwardToHTWKalendar,
command: forwardToHTWKalendar
},
]);
</script>

View File

@ -21,13 +21,13 @@ import { computed, ComputedRef, Ref, ref, watch } from "vue";
import {
fetchCourseBySemester,
fetchModulesByCourseAndSemester,
} from "../api/fetchCourse";
import DynamicPage from "./DynamicPage.vue";
import ModuleSelection from "../components/ModuleSelection.vue";
import { Module } from "../model/module.ts";
} from "@/api/fetchCourse";
import DynamicPage from "@/view/DynamicPage.vue";
import ModuleSelection from "@/components/ModuleSelection.vue";
import { Module } from "@/model/module.ts";
import { useI18n } from "vue-i18n";
import moduleStore from "../store/moduleStore";
import router from "../router";
import moduleStore from "@/store/moduleStore";
import { router } from "@/main";
async function getModules() {
modules.value = await fetchModulesByCourseAndSemester(

View File

@ -53,7 +53,7 @@ const hasContent = computed(() => {
<i v-if="icon" :class="icon" style="font-size: 2rem"></i>
</div>
<div v-if="subTitle" class="flex justify-content-center">
<h5 class="text-2xl m-2">{{ subTitle }}</h5>
<p class="subtitle text-2xl m-2">{{ subTitle }}</p>
</div>
<div class="flex flex-wrap mx-0 gap-2 my-4 w-full lg:w-8">
<slot name="selection" flex-specs="flex-1 m-0"></slot>
@ -86,6 +86,18 @@ const hasContent = computed(() => {
]"
>
<slot name="content"></slot>
<div
v-if="button"
class="flex flex-wrap my-3 gap-2 align-items-center justify-content-end"
>
<Button
:disabled="button.disabled"
class="col-12 md:col-4"
:icon="button.icon"
:label="button.label"
@click="button.onClick()"
/>
</div>
</div>
</div>
</template>
@ -94,4 +106,8 @@ const hasContent = computed(() => {
.transition-rolldown {
transition: margin-top 0.5s ease-in-out;
}
.subtitle {
font-weight: 100;
font-size: 1.5rem;
}
</style>

View File

@ -16,7 +16,8 @@ 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/>.
-->
<script lang="ts" setup></script>
<script lang="ts" setup>
</script>
<template>
<div class="flex align-items-center justify-content-center flex-column">
@ -53,6 +54,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<li>
{{ $t("faqView.thirdAnswer.google.third") }}
</li>
<li>
{{ $t("faqView.thirdAnswer.google.fourth") }}
</li>
</ol>
</AccordionTab>
<AccordionTab
@ -230,10 +234,39 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<div class="col">{{ $t("faqView.ninthQuestion") }}</div>
<div class="col">
{{ $t("faqView.ninthAnswer") }}
<a href="https://git.imn.htwk-leipzig.de/ekresse/htwkalender"
<a href="https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender"
>Gitlab</a
>
.
>.
</div>
</div>
<div class="grid my-2">
<div class="col">{{ $t("faqView.crossPromoQuestion") }}</div>
<div class="col">
{{ $t("faqView.crossPromo.teaser") }}
<div class="flex flex-column gap-3 my-3">
<Card v-for="promoPage in new Array('mensa', 'htwkarte')" :key="promoPage">
<template #title>
<div class="flex flex-row align-items-start">
<Avatar :image="'/promo/' + promoPage + '.png'" class="mr-2 flex-shrink-0" size="xlarge" />
<div class="flex flex-column gap-1">
<div>
{{$t("faqView.crossPromo." + promoPage + ".title") }}
</div>
<div class="p-card-subtitle text-base">
<a :href="$t('faqView.crossPromo.' + promoPage + '.link')">
{{$t("faqView.crossPromo." + promoPage + ".link") }}
</a>
</div>
</div>
</div>
</template>
<template #content>
<p class="m-0">
{{ $t("faqView.crossPromo." + promoPage + ".description") }}
</p>
</template>
</Card>
</div>
</div>
</div>
<p>

View File

@ -17,35 +17,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts" setup>
import moduleStore from "../store/moduleStore";
import router from "../router";
import AdditionalModuleTable from "../components/AdditionalModuleTable.vue";
import moduleStore from "@/store/moduleStore";
import {router} from "@/main";
import AdditionalModuleTable from "@/components/AdditionalModuleTable.vue";
const store = moduleStore();
function topFunction() {
window.scrollTo({top: 0, behavior: 'smooth'});
}
async function nextStep() {
await router.push("/rename-modules");
}
</script>
<template>
<div class="flex flex-column align-items-center w-full mb-7">
<div class="flex align-items-center justify-content-center m-2">
<div class="flex flex-column align-items-center w-full mb-8">
<div class="flex align-items-center justify-content-center">
<h3>
{{ $t("additionalModules.subTitle") }}
</h3>
</div>
<AdditionalModuleTable />
<div
class="flex align-items-center justify-content-end h-4rem m-2 w-full lg:w-10"
class="flex align-items-center justify-content-end h-4rem mb-2 w-full lg:w-10"
>
<Button
:disabled="store.isEmpty()"
class="col-12 md:col-4 mb-3 align-self-end"
class="col-12 md:col-4 align-self-end"
icon="pi pi-arrow-right"
:label="$t('additionalModules.nextStep')"
@click="nextStep()"
/>
</div>
<AdditionalModuleTable />
<div
class="flex align-items-center justify-content-end mt-2 w-full lg:w-10"
>
<Button
class="col-12 md:col-4 mb-3 align-self-end"
severity="secondary"
icon="pi pi-arrow-up"
:label="$t('additionalModules.scrollToTop')"
@click="topFunction()"
/>
</div>
</div>
</template>

View File

@ -17,13 +17,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script setup lang="ts">
import moduleStore from "../store/moduleStore.ts";
import { createIndividualFeed } from "../api/createFeed.ts";
import router from "../router";
import tokenStore from "../store/tokenStore.ts";
import moduleStore from "@/store/moduleStore.ts";
import { createIndividualFeed } from "@/api/createFeed.ts";
import { router } from "@/main";
import tokenStore from "@/store/tokenStore.ts";
import { Ref, computed, inject, ref, onMounted } from "vue";
import ModuleTemplateDialog from "./ModuleTemplateDialog.vue";
import { onlyWhitespace } from "../helpers/strings.ts";
import ModuleTemplateDialog from "@/components/ModuleTemplateDialog.vue";
import { onlyWhitespace } from "@/helpers/strings.ts";
import { useI18n } from "vue-i18n";
import { Module } from "@/model/module.ts";
import { useToast } from "primevue/usetoast";

View File

@ -18,12 +18,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup>
import { defineAsyncComponent } from "vue";
import moduleStore from "../../store/moduleStore";
import router from "../../router";
import moduleStore from "@/store/moduleStore";
import { router } from "@/main";
const store = moduleStore();
const AdditionalModuleTable = defineAsyncComponent(
() => import("../../components/AdditionalModuleTable.vue"),
() => import("@/components/AdditionalModuleTable.vue"),
);
async function nextStep() {

View File

@ -18,14 +18,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script setup lang="ts">
import { Ref, ref } from "vue";
import { Module } from "../model/module";
import moduleStore from "../store/moduleStore";
import { getCalender } from "../api/loadCalendar";
import router from "../router";
import tokenStore from "../store/tokenStore";
import { Module } from "@/model/module";
import moduleStore from "@/store/moduleStore";
import { getCalender } from "@/api/loadCalendar";
import { router } from "@/main";
import tokenStore from "@/store/tokenStore";
import { useToast } from "primevue/usetoast";
import { useI18n } from "vue-i18n";
import DynamicPage from "./DynamicPage.vue";
import DynamicPage from "@/view/DynamicPage.vue";
import { extractToken, isToken } from "@/helpers/token.ts";
const { t } = useI18n({ useScope: "global" });

View File

@ -19,12 +19,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup>
import { computed, inject, Ref, ref } from "vue";
import { Module } from "@/model/module.ts";
import moduleStore from "../../store/moduleStore";
import moduleStore from "@/store/moduleStore";
import { fetchAllModules } from "@/api/fetchCourse.ts";
import { deleteIndividualFeed, saveIndividualFeed } from "@/api/createFeed.ts";
import tokenStore from "../../store/tokenStore";
import router from "@/router";
import ModuleTemplateDialog from "../../components/ModuleTemplateDialog.vue";
import tokenStore from "@/store/tokenStore";
import { router } from "@/main";
import ModuleTemplateDialog from "@/components/ModuleTemplateDialog.vue";
import { onlyWhitespace } from "@/helpers/strings.ts";
import { useI18n } from "vue-i18n";
import { useToast } from "primevue/usetoast";

View File

@ -155,7 +155,7 @@ import DynamicPage from "@/view/DynamicPage.vue";
import { requestFreeRooms } from "@/api/requestFreeRooms.ts";
import { FilterMatchMode } from "primevue/api";
import { padStart } from "@fullcalendar/core/internal";
import router from "@/router";
import { router } from "@/main";
import { formatYearMonthDay } from "@/helpers/dates";
const mobilePage = inject("mobilePage") as Ref<boolean>;

View File

@ -18,11 +18,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup>
import { Ref, computed, ref, watch } from "vue";
import { fetchRoom } from "../api/fetchRoom.ts";
import DynamicPage from "./DynamicPage.vue";
import RoomOccupation from "../components/RoomOccupation.vue";
import { fetchRoom } from "@/api/fetchRoom.ts";
import DynamicPage from "@/view/DynamicPage.vue";
import RoomOccupation from "@/components/RoomOccupation.vue";
import { computedAsync } from "@vueuse/core";
import router from "@/router";
import { router } from "@/main";
type Room = {
name: string;

View File

@ -20,7 +20,8 @@
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"primevue/*": ["./node_modules/primevue/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],

View File

@ -19,10 +19,21 @@ import vue from "@vitejs/plugin-vue";
import { fileURLToPath } from "node:url";
import { VitePWA } from "vite-plugin-pwa";
import basicSsl from "@vitejs/plugin-basic-ssl";
import resolve from "@rollup/plugin-node-resolve";
import {resolve as pathResolver} from "path";
import terser from "@rollup/plugin-terser";
import ViteSSGOptions from "vite-ssg";
import generateSitemap from 'vite-ssg-sitemap'
import vueDevTools from 'vite-plugin-vue-devtools'
const hostname = "https://cal.htwk-leipzig.de";
export default defineConfig({
plugins: [
vue(),
resolve(),
terser(),
vueDevTools(),
basicSsl(),
VitePWA({
mode: "development",
@ -120,8 +131,27 @@ export default defineConfig({
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
alias:
{
"@": fileURLToPath(new URL("./src", import.meta.url)),
'primevue' : pathResolver(__dirname, 'node_modules/primevue'),
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.css', '.scss'],
},
ssgOptions: {
script: "async",
formatting: "minify",
format: "esm",
onFinished: () => {
generateSitemap({
hostname: hostname,
exclude: [
'/additional-modules',
'/edit-additional-modules',
'/edit-calendar',
'/rename-modules',
]
})
},
},
server: {
@ -150,4 +180,24 @@ export default defineConfig({
},
},
},
build: {
sourcemap: true,
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
},
},
cssMinify: "esbuild",
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
return id.toString().split("node_modules/")[1].split("/")[0].toString()
}
},
},
}
},
});

View File

@ -26,6 +26,8 @@ events {
http {
include mime.types;
default_type application/octet-stream;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Cloudflare IP Ranges (https://www.cloudflare.com/ips/)
set_real_ip_from 173.245.48.0/20;
@ -91,6 +93,13 @@ http {
proxy_temp_file_write_size 64k;
proxy_max_temp_file_size 1024m;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
geo $admin {
default 1;
10.0.0.0/8 0; # Private Network
@ -105,16 +114,58 @@ http {
1 $binary_remote_addr;
}
# Different rate limits for different request methods
map $request_method $ratelimit_key {
POST $binary_remote_addr;
default "";
}
limit_req_zone $ratelimit_key zone=createFeed:10m rate=1r/m;
# Limit the number of requests per IP
limit_req_zone $limit_key zone=feed:20m rate=20r/m;
limit_req_zone $limit_key zone=createFeed:10m rate=1r/m;
limit_req_zone $limit_key zone=modules:10m rate=30r/m;
server {
listen 80;
listen [::]:80;
http2 on;
server_name pwa.htwk-leipzig.de;
location /api/feed {
limit_req zone=createFeed nodelay;
limit_req zone=feed burst=10 nodelay;
proxy_pass http://htwkalender-ical:8091;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req_status 429;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 80;
listen [::]:80;
http2 on;
server_name pwa.htwkalender.de;
location /api/feed {
limit_req zone=createFeed nodelay;
limit_req zone=feed burst=10 nodelay;
proxy_pass http://htwkalender-ical:8091;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req_status 429;
}
location / {
return 301 https://$host$request_uri;
}
@ -123,23 +174,37 @@ http {
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name pwa.htwkalender.de;
ssl_certificate htwkalender.de.pem;
ssl_certificate_key htwkalender.de.key.pem;
return 301 https://$host$request_uri;
}
location /_ {
proxy_pass http://htwkalender-backend:8090;
# if user is not 0 in admin list, return 404
if ($admin) {
return 404 "Not Found";
}
# Increase upload file size
client_max_body_size 100m;
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name pwa.htwk-leipzig.de;
ssl_certificate cal.htwk-leipzig.de.pem;
ssl_certificate_key cal.htwk-leipzig.de.key.pem;
location /api/feed {
limit_req zone=createFeed nodelay;
limit_req zone=feed burst=10 nodelay;
proxy_pass http://htwkalender-ical:8091;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req_status 429;
}
location /api {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -149,7 +214,7 @@ http {
# Cache only specific URI
location /api/modules {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -166,8 +231,26 @@ http {
limit_req zone=modules burst=5 nodelay;
}
location /api/events/types {
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
proxy_cache_bypass 0;
proxy_no_cache 0;
proxy_cache mcache; # mcache=RAM
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 403 404 5m;
proxy_cache_lock on;
proxy_cache_use_stale timeout updating;
add_header X-Proxy-Cache $upstream_cache_status;
limit_req zone=modules burst=10 nodelay;
}
location /api/rooms {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -185,7 +268,7 @@ http {
}
location /api/schedule {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -203,7 +286,7 @@ http {
}
location /api/courses {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -220,26 +303,14 @@ http {
limit_req zone=modules burst=5 nodelay;
}
location /api/feed {
proxy_pass http://htwkalender-backend:8090;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req zone=feed burst=10 nodelay;
}
location /api/createFeed {
limit_req zone=createFeed nodelay;
# return limit request error
limit_req_status 429;
proxy_pass http://htwkalender-backend:8090;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location /_ {
proxy_pass http://htwkalender-data-manager:8090;
# if user is not 0 in admin list, return 404
if ($admin) {
return 404 "Not Found";
}
# Increase upload file size
client_max_body_size 100m;
}
location / {

View File

@ -27,6 +27,9 @@ http {
include mime.types;
default_type application/octet-stream;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Cloudflare IP Ranges (https://www.cloudflare.com/ips/)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
@ -91,6 +94,13 @@ http {
proxy_temp_file_write_size 64k;
proxy_max_temp_file_size 1024m;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
geo $admin {
default 1;
10.0.0.0/8 0; # Private Network
@ -105,14 +115,22 @@ http {
1 $binary_remote_addr;
}
# Different rate limits for different request methods
map $request_method $ratelimit_key {
POST $binary_remote_addr;
default "";
}
limit_req_zone $ratelimit_key zone=createFeed:10m rate=1r/m;
# Limit the number of requests per IP
limit_req_zone $limit_key zone=feed:20m rate=20r/m;
limit_req_zone $limit_key zone=createFeed:10m rate=1r/m;
limit_req_zone $limit_key zone=modules:10m rate=30r/m;
server {
listen 80;
listen [::]:80;
http2 on;
server_name dev.htwkalender.de;
return 301 https://$host$request_uri;
}
@ -120,22 +138,25 @@ http {
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name dev.htwkalender.de;
ssl_certificate dev_htwkalender_de.pem;
ssl_certificate_key dev_htwkalender_de.key.pem;
location /api/feed {
proxy_pass http://htwkalender-backend:8090;
limit_req zone=createFeed nodelay;
limit_req zone=feed burst=10 nodelay;
proxy_pass http://htwkalender-ical:8091;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req zone=feed burst=10 nodelay;
limit_req_status 429;
}
location /api {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -145,7 +166,7 @@ http {
# Cache only specific URI
location /api/modules {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -163,7 +184,7 @@ http {
}
location /api/rooms {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -181,7 +202,7 @@ http {
}
location /api/schedule {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -199,7 +220,7 @@ http {
}
location /api/courses {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -216,20 +237,8 @@ http {
limit_req zone=modules burst=5 nodelay;
}
location /api/createFeed {
limit_req zone=createFeed nodelay;
# return limit request error
limit_req_status 429;
proxy_pass http://htwkalender-backend:8090;
client_max_body_size 2m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
}
location /_ {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
# if user is not 0 in admin list, return 404
if ($admin) {
return 404 "Not Found";

View File

@ -1,22 +1,6 @@
#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/>.
worker_processes 4;
error_log /opt/bitnami/nginx/logs/error.log;
error_log /opt/bitnami/nginx/logs/error.log debug;
pid /opt/bitnami/nginx/tmp/nginx.pid;
events {
@ -27,43 +11,31 @@ http {
include mime.types;
default_type application/octet-stream;
access_log /opt/bitnami/nginx/logs/proxy_access.log;
error_log /opt/bitnami/nginx/logs/proxy_error.log;
sendfile on;
keepalive_timeout 180s;
send_timeout 180s;
client_body_temp_path /opt/bitnami/nginx/tmp/client_temp;
proxy_temp_path /opt/bitnami/nginx/tmp/proxy_temp_path;
fastcgi_temp_path /opt/bitnami/nginx/tmp/fastcgi_temp;
uwsgi_temp_path /opt/bitnami/nginx/tmp/uwsgi_temp;
scgi_temp_path /opt/bitnami/nginx/tmp/scgi_temp;
proxy_buffering on;
proxy_buffers 8 16k;
proxy_buffer_size 16k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_max_temp_file_size 1024m;
map $request_method $ratelimit_key {
POST $binary_remote_addr;
default "";
}
limit_req_zone $ratelimit_key zone=createFeed:10m rate=1r/m;
server {
listen 80;
listen [::]:80;
server_name dev.htwkalender.de;
return 301 https://$host$request_uri;
}
http2 on;
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /opt/bitnami/nginx/conf/dev_htwkalender_de.pem;
ssl_certificate_key /opt/bitnami/nginx/conf/dev_htwkalender_de.key.pem;
location /api/feed {
limit_req zone=createFeed nodelay;
proxy_pass http://htwkalender-ical:8091;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
limit_req_status 429;
}
location /api {
proxy_pass http://htwkalender-backend:8090;
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 20m;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
@ -72,13 +44,16 @@ http {
}
location /_ {
proxy_pass http://htwkalender-backend:8090;
# Increase upload file size
proxy_pass http://htwkalender-data-manager:8090;
client_max_body_size 100m;
}
location / {
proxy_pass https://htwkalender-frontend:8000;
}
location /__devtools__ {
proxy_pass https://htwkalender-frontend:8000;
}
}
}

16
services/Makefile Normal file
View File

@ -0,0 +1,16 @@
run-data-manager:
@go run data-manager/main.go serve
run-ical:
@go run ical/main.go
build-ical:
@go build ical/main.go
gen:
@protoc \
--proto_path=protobuf "protobuf/*" \
--go_out="common/genproto/modules" \
--go_opt=paths=source_relative \
--go-grpc_out="common/genproto/modules" \
--go-grpc_opt=paths=source_relative

6
services/README.md Normal file
View File

@ -0,0 +1,6 @@
To execute the protobuf gen script, install the protobuf compiler and the python protobuf library. Then run the following command:
```bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
```

View File

@ -0,0 +1,322 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v5.27.1
// source: feeds.proto
package modules
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Feed struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Modules string `protobuf:"bytes,2,opt,name=modules,proto3" json:"modules,omitempty"`
Retrieved string `protobuf:"bytes,3,opt,name=retrieved,proto3" json:"retrieved,omitempty"`
Created string `protobuf:"bytes,4,opt,name=created,proto3" json:"created,omitempty"`
Updated string `protobuf:"bytes,5,opt,name=updated,proto3" json:"updated,omitempty"`
Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"`
}
func (x *Feed) Reset() {
*x = Feed{}
if protoimpl.UnsafeEnabled {
mi := &file_feeds_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Feed) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Feed) ProtoMessage() {}
func (x *Feed) ProtoReflect() protoreflect.Message {
mi := &file_feeds_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Feed.ProtoReflect.Descriptor instead.
func (*Feed) Descriptor() ([]byte, []int) {
return file_feeds_proto_rawDescGZIP(), []int{0}
}
func (x *Feed) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Feed) GetModules() string {
if x != nil {
return x.Modules
}
return ""
}
func (x *Feed) GetRetrieved() string {
if x != nil {
return x.Retrieved
}
return ""
}
func (x *Feed) GetCreated() string {
if x != nil {
return x.Created
}
return ""
}
func (x *Feed) GetUpdated() string {
if x != nil {
return x.Updated
}
return ""
}
func (x *Feed) GetDeleted() bool {
if x != nil {
return x.Deleted
}
return false
}
type GetFeedRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *GetFeedRequest) Reset() {
*x = GetFeedRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_feeds_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetFeedRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetFeedRequest) ProtoMessage() {}
func (x *GetFeedRequest) ProtoReflect() protoreflect.Message {
mi := &file_feeds_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetFeedRequest.ProtoReflect.Descriptor instead.
func (*GetFeedRequest) Descriptor() ([]byte, []int) {
return file_feeds_proto_rawDescGZIP(), []int{1}
}
func (x *GetFeedRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type GetFeedResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Feed *Feed `protobuf:"bytes,1,opt,name=feed,proto3" json:"feed,omitempty"`
}
func (x *GetFeedResponse) Reset() {
*x = GetFeedResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_feeds_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetFeedResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetFeedResponse) ProtoMessage() {}
func (x *GetFeedResponse) ProtoReflect() protoreflect.Message {
mi := &file_feeds_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetFeedResponse.ProtoReflect.Descriptor instead.
func (*GetFeedResponse) Descriptor() ([]byte, []int) {
return file_feeds_proto_rawDescGZIP(), []int{2}
}
func (x *GetFeedResponse) GetFeed() *Feed {
if x != nil {
return x.Feed
}
return nil
}
var File_feeds_proto protoreflect.FileDescriptor
var file_feeds_proto_rawDesc = []byte{
0x0a, 0x0b, 0x66, 0x65, 0x65, 0x64, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9c, 0x01,
0x0a, 0x04, 0x46, 0x65, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x64, 0x12, 0x18,
0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20,
0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x20, 0x0a, 0x0e,
0x47, 0x65, 0x74, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2c,
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x19, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x05, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x52, 0x04, 0x66, 0x65, 0x65, 0x64, 0x32, 0x3d, 0x0a, 0x0b,
0x46, 0x65, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x47,
0x65, 0x74, 0x46, 0x65, 0x65, 0x64, 0x12, 0x0f, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x65, 0x64,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x65,
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1c, 0x5a, 0x1a, 0x68,
0x74, 0x77, 0x6b, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_feeds_proto_rawDescOnce sync.Once
file_feeds_proto_rawDescData = file_feeds_proto_rawDesc
)
func file_feeds_proto_rawDescGZIP() []byte {
file_feeds_proto_rawDescOnce.Do(func() {
file_feeds_proto_rawDescData = protoimpl.X.CompressGZIP(file_feeds_proto_rawDescData)
})
return file_feeds_proto_rawDescData
}
var file_feeds_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_feeds_proto_goTypes = []interface{}{
(*Feed)(nil), // 0: Feed
(*GetFeedRequest)(nil), // 1: GetFeedRequest
(*GetFeedResponse)(nil), // 2: GetFeedResponse
}
var file_feeds_proto_depIdxs = []int32{
0, // 0: GetFeedResponse.feed:type_name -> Feed
1, // 1: FeedService.GetFeed:input_type -> GetFeedRequest
2, // 2: FeedService.GetFeed:output_type -> GetFeedResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_feeds_proto_init() }
func file_feeds_proto_init() {
if File_feeds_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_feeds_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Feed); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_feeds_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFeedRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_feeds_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFeedResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_feeds_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_feeds_proto_goTypes,
DependencyIndexes: file_feeds_proto_depIdxs,
MessageInfos: file_feeds_proto_msgTypes,
}.Build()
File_feeds_proto = out.File
file_feeds_proto_rawDesc = nil
file_feeds_proto_goTypes = nil
file_feeds_proto_depIdxs = nil
}

View File

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v5.27.1
// source: feeds.proto
package modules
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// FeedServiceClient is the client API for FeedService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type FeedServiceClient interface {
GetFeed(ctx context.Context, in *GetFeedRequest, opts ...grpc.CallOption) (*GetFeedResponse, error)
}
type feedServiceClient struct {
cc grpc.ClientConnInterface
}
func NewFeedServiceClient(cc grpc.ClientConnInterface) FeedServiceClient {
return &feedServiceClient{cc}
}
func (c *feedServiceClient) GetFeed(ctx context.Context, in *GetFeedRequest, opts ...grpc.CallOption) (*GetFeedResponse, error) {
out := new(GetFeedResponse)
err := c.cc.Invoke(ctx, "/FeedService/GetFeed", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// FeedServiceServer is the server API for FeedService service.
// All implementations must embed UnimplementedFeedServiceServer
// for forward compatibility
type FeedServiceServer interface {
GetFeed(context.Context, *GetFeedRequest) (*GetFeedResponse, error)
mustEmbedUnimplementedFeedServiceServer()
}
// UnimplementedFeedServiceServer must be embedded to have forward compatible implementations.
type UnimplementedFeedServiceServer struct {
}
func (UnimplementedFeedServiceServer) GetFeed(context.Context, *GetFeedRequest) (*GetFeedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetFeed not implemented")
}
func (UnimplementedFeedServiceServer) mustEmbedUnimplementedFeedServiceServer() {}
// UnsafeFeedServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to FeedServiceServer will
// result in compilation errors.
type UnsafeFeedServiceServer interface {
mustEmbedUnimplementedFeedServiceServer()
}
func RegisterFeedServiceServer(s grpc.ServiceRegistrar, srv FeedServiceServer) {
s.RegisterService(&FeedService_ServiceDesc, srv)
}
func _FeedService_GetFeed_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetFeedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(FeedServiceServer).GetFeed(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/FeedService/GetFeed",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(FeedServiceServer).GetFeed(ctx, req.(*GetFeedRequest))
}
return interceptor(ctx, in, info, handler)
}
// FeedService_ServiceDesc is the grpc.ServiceDesc for FeedService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var FeedService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "FeedService",
HandlerType: (*FeedServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetFeed",
Handler: _FeedService_GetFeed_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "feeds.proto",
}

View File

@ -0,0 +1,712 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v5.27.1
// source: modules.proto
package modules
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Event struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
Day string `protobuf:"bytes,2,opt,name=day,proto3" json:"day,omitempty"`
Week string `protobuf:"bytes,3,opt,name=week,proto3" json:"week,omitempty"`
Start string `protobuf:"bytes,4,opt,name=start,proto3" json:"start,omitempty"`
End string `protobuf:"bytes,5,opt,name=end,proto3" json:"end,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
EventType string `protobuf:"bytes,7,opt,name=eventType,proto3" json:"eventType,omitempty"`
Compulsory string `protobuf:"bytes,8,opt,name=compulsory,proto3" json:"compulsory,omitempty"`
Prof string `protobuf:"bytes,9,opt,name=prof,proto3" json:"prof,omitempty"`
Rooms string `protobuf:"bytes,10,opt,name=rooms,proto3" json:"rooms,omitempty"`
Notes string `protobuf:"bytes,11,opt,name=notes,proto3" json:"notes,omitempty"`
BookedAt string `protobuf:"bytes,12,opt,name=bookedAt,proto3" json:"bookedAt,omitempty"`
Course string `protobuf:"bytes,13,opt,name=course,proto3" json:"course,omitempty"`
Semester string `protobuf:"bytes,14,opt,name=semester,proto3" json:"semester,omitempty"`
}
func (x *Event) Reset() {
*x = Event{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Event) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Event) ProtoMessage() {}
func (x *Event) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Event.ProtoReflect.Descriptor instead.
func (*Event) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{0}
}
func (x *Event) GetUuid() string {
if x != nil {
return x.Uuid
}
return ""
}
func (x *Event) GetDay() string {
if x != nil {
return x.Day
}
return ""
}
func (x *Event) GetWeek() string {
if x != nil {
return x.Week
}
return ""
}
func (x *Event) GetStart() string {
if x != nil {
return x.Start
}
return ""
}
func (x *Event) GetEnd() string {
if x != nil {
return x.End
}
return ""
}
func (x *Event) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Event) GetEventType() string {
if x != nil {
return x.EventType
}
return ""
}
func (x *Event) GetCompulsory() string {
if x != nil {
return x.Compulsory
}
return ""
}
func (x *Event) GetProf() string {
if x != nil {
return x.Prof
}
return ""
}
func (x *Event) GetRooms() string {
if x != nil {
return x.Rooms
}
return ""
}
func (x *Event) GetNotes() string {
if x != nil {
return x.Notes
}
return ""
}
func (x *Event) GetBookedAt() string {
if x != nil {
return x.BookedAt
}
return ""
}
func (x *Event) GetCourse() string {
if x != nil {
return x.Course
}
return ""
}
func (x *Event) GetSemester() string {
if x != nil {
return x.Semester
}
return ""
}
type Module struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Prof string `protobuf:"bytes,3,opt,name=prof,proto3" json:"prof,omitempty"`
Course string `protobuf:"bytes,4,opt,name=course,proto3" json:"course,omitempty"`
Semester string `protobuf:"bytes,5,opt,name=semester,proto3" json:"semester,omitempty"`
Events []*Event `protobuf:"bytes,6,rep,name=events,proto3" json:"events,omitempty"`
}
func (x *Module) Reset() {
*x = Module{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Module) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Module) ProtoMessage() {}
func (x *Module) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Module.ProtoReflect.Descriptor instead.
func (*Module) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{1}
}
func (x *Module) GetUuid() string {
if x != nil {
return x.Uuid
}
return ""
}
func (x *Module) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Module) GetProf() string {
if x != nil {
return x.Prof
}
return ""
}
func (x *Module) GetCourse() string {
if x != nil {
return x.Course
}
return ""
}
func (x *Module) GetSemester() string {
if x != nil {
return x.Semester
}
return ""
}
func (x *Module) GetEvents() []*Event {
if x != nil {
return x.Events
}
return nil
}
type GetModuleRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
}
func (x *GetModuleRequest) Reset() {
*x = GetModuleRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetModuleRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetModuleRequest) ProtoMessage() {}
func (x *GetModuleRequest) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetModuleRequest.ProtoReflect.Descriptor instead.
func (*GetModuleRequest) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{2}
}
func (x *GetModuleRequest) GetUuid() string {
if x != nil {
return x.Uuid
}
return ""
}
type GetModulesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Uuids []string `protobuf:"bytes,1,rep,name=uuids,proto3" json:"uuids,omitempty"`
}
func (x *GetModulesRequest) Reset() {
*x = GetModulesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetModulesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetModulesRequest) ProtoMessage() {}
func (x *GetModulesRequest) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetModulesRequest.ProtoReflect.Descriptor instead.
func (*GetModulesRequest) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{3}
}
func (x *GetModulesRequest) GetUuids() []string {
if x != nil {
return x.Uuids
}
return nil
}
type GetModuleResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Module *Module `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"`
}
func (x *GetModuleResponse) Reset() {
*x = GetModuleResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetModuleResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetModuleResponse) ProtoMessage() {}
func (x *GetModuleResponse) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetModuleResponse.ProtoReflect.Descriptor instead.
func (*GetModuleResponse) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{4}
}
func (x *GetModuleResponse) GetModule() *Module {
if x != nil {
return x.Module
}
return nil
}
type GetModulesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Modules []*Module `protobuf:"bytes,1,rep,name=modules,proto3" json:"modules,omitempty"`
}
func (x *GetModulesResponse) Reset() {
*x = GetModulesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetModulesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetModulesResponse) ProtoMessage() {}
func (x *GetModulesResponse) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetModulesResponse.ProtoReflect.Descriptor instead.
func (*GetModulesResponse) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{5}
}
func (x *GetModulesResponse) GetModules() []*Module {
if x != nil {
return x.Modules
}
return nil
}
type GetEventsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Events []*Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
}
func (x *GetEventsResponse) Reset() {
*x = GetEventsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_modules_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetEventsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetEventsResponse) ProtoMessage() {}
func (x *GetEventsResponse) ProtoReflect() protoreflect.Message {
mi := &file_modules_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetEventsResponse.ProtoReflect.Descriptor instead.
func (*GetEventsResponse) Descriptor() ([]byte, []int) {
return file_modules_proto_rawDescGZIP(), []int{6}
}
func (x *GetEventsResponse) GetEvents() []*Event {
if x != nil {
return x.Events
}
return nil
}
var File_modules_proto protoreflect.FileDescriptor
var file_modules_proto_rawDesc = []byte{
0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0xcb, 0x02, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a,
0x03, 0x64, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x61, 0x79, 0x12,
0x12, 0x0a, 0x04, 0x77, 0x65, 0x65, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x77,
0x65, 0x65, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x1c, 0x0a, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a,
0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x6c, 0x73, 0x6f, 0x72, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x6c, 0x73, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x72, 0x6f, 0x66, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x72, 0x6f,
0x66, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x72, 0x6f, 0x6f, 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73,
0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a,
0x08, 0x62, 0x6f, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x62, 0x6f, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x75,
0x72, 0x73, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6d, 0x65, 0x73, 0x74, 0x65, 0x72, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6d, 0x65, 0x73, 0x74, 0x65, 0x72, 0x22, 0x98, 0x01,
0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x70, 0x72, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x70, 0x72, 0x6f, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08,
0x73, 0x65, 0x6d, 0x65, 0x73, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x73, 0x65, 0x6d, 0x65, 0x73, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x26, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64,
0x22, 0x29, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x75, 0x69, 0x64, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x75, 0x69, 0x64, 0x73, 0x22, 0x34, 0x0a, 0x11, 0x47,
0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x1f, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x07, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x22, 0x37, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x33, 0x0a, 0x11, 0x47, 0x65,
0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x32,
0xbf, 0x01, 0x0a, 0x0d, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x34, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x11,
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x12, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x4d,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x3f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x6f, 0x72,
0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x12, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x47, 0x65,
0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x42, 0x1c, 0x5a, 0x1a, 0x68, 0x74, 0x77, 0x6b, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x65, 0x72,
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_modules_proto_rawDescOnce sync.Once
file_modules_proto_rawDescData = file_modules_proto_rawDesc
)
func file_modules_proto_rawDescGZIP() []byte {
file_modules_proto_rawDescOnce.Do(func() {
file_modules_proto_rawDescData = protoimpl.X.CompressGZIP(file_modules_proto_rawDescData)
})
return file_modules_proto_rawDescData
}
var file_modules_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_modules_proto_goTypes = []interface{}{
(*Event)(nil), // 0: Event
(*Module)(nil), // 1: Module
(*GetModuleRequest)(nil), // 2: GetModuleRequest
(*GetModulesRequest)(nil), // 3: GetModulesRequest
(*GetModuleResponse)(nil), // 4: GetModuleResponse
(*GetModulesResponse)(nil), // 5: GetModulesResponse
(*GetEventsResponse)(nil), // 6: GetEventsResponse
}
var file_modules_proto_depIdxs = []int32{
0, // 0: Module.events:type_name -> Event
1, // 1: GetModuleResponse.module:type_name -> Module
1, // 2: GetModulesResponse.modules:type_name -> Module
0, // 3: GetEventsResponse.events:type_name -> Event
2, // 4: ModuleService.GetModule:input_type -> GetModuleRequest
3, // 5: ModuleService.GetModules:input_type -> GetModulesRequest
3, // 6: ModuleService.GetEventsForModules:input_type -> GetModulesRequest
4, // 7: ModuleService.GetModule:output_type -> GetModuleResponse
5, // 8: ModuleService.GetModules:output_type -> GetModulesResponse
6, // 9: ModuleService.GetEventsForModules:output_type -> GetEventsResponse
7, // [7:10] is the sub-list for method output_type
4, // [4:7] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_modules_proto_init() }
func file_modules_proto_init() {
if File_modules_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_modules_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Event); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Module); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetModuleRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetModulesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetModuleResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetModulesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_modules_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetEventsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_modules_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_modules_proto_goTypes,
DependencyIndexes: file_modules_proto_depIdxs,
MessageInfos: file_modules_proto_msgTypes,
}.Build()
File_modules_proto = out.File
file_modules_proto_rawDesc = nil
file_modules_proto_goTypes = nil
file_modules_proto_depIdxs = nil
}

View File

@ -0,0 +1,177 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v5.27.1
// source: modules.proto
package modules
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ModuleServiceClient is the client API for ModuleService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ModuleServiceClient interface {
GetModule(ctx context.Context, in *GetModuleRequest, opts ...grpc.CallOption) (*GetModuleResponse, error)
GetModules(ctx context.Context, in *GetModulesRequest, opts ...grpc.CallOption) (*GetModulesResponse, error)
GetEventsForModules(ctx context.Context, in *GetModulesRequest, opts ...grpc.CallOption) (*GetEventsResponse, error)
}
type moduleServiceClient struct {
cc grpc.ClientConnInterface
}
func NewModuleServiceClient(cc grpc.ClientConnInterface) ModuleServiceClient {
return &moduleServiceClient{cc}
}
func (c *moduleServiceClient) GetModule(ctx context.Context, in *GetModuleRequest, opts ...grpc.CallOption) (*GetModuleResponse, error) {
out := new(GetModuleResponse)
err := c.cc.Invoke(ctx, "/ModuleService/GetModule", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *moduleServiceClient) GetModules(ctx context.Context, in *GetModulesRequest, opts ...grpc.CallOption) (*GetModulesResponse, error) {
out := new(GetModulesResponse)
err := c.cc.Invoke(ctx, "/ModuleService/GetModules", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *moduleServiceClient) GetEventsForModules(ctx context.Context, in *GetModulesRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) {
out := new(GetEventsResponse)
err := c.cc.Invoke(ctx, "/ModuleService/GetEventsForModules", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ModuleServiceServer is the server API for ModuleService service.
// All implementations must embed UnimplementedModuleServiceServer
// for forward compatibility
type ModuleServiceServer interface {
GetModule(context.Context, *GetModuleRequest) (*GetModuleResponse, error)
GetModules(context.Context, *GetModulesRequest) (*GetModulesResponse, error)
GetEventsForModules(context.Context, *GetModulesRequest) (*GetEventsResponse, error)
mustEmbedUnimplementedModuleServiceServer()
}
// UnimplementedModuleServiceServer must be embedded to have forward compatible implementations.
type UnimplementedModuleServiceServer struct {
}
func (UnimplementedModuleServiceServer) GetModule(context.Context, *GetModuleRequest) (*GetModuleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetModule not implemented")
}
func (UnimplementedModuleServiceServer) GetModules(context.Context, *GetModulesRequest) (*GetModulesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetModules not implemented")
}
func (UnimplementedModuleServiceServer) GetEventsForModules(context.Context, *GetModulesRequest) (*GetEventsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetEventsForModules not implemented")
}
func (UnimplementedModuleServiceServer) mustEmbedUnimplementedModuleServiceServer() {}
// UnsafeModuleServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ModuleServiceServer will
// result in compilation errors.
type UnsafeModuleServiceServer interface {
mustEmbedUnimplementedModuleServiceServer()
}
func RegisterModuleServiceServer(s grpc.ServiceRegistrar, srv ModuleServiceServer) {
s.RegisterService(&ModuleService_ServiceDesc, srv)
}
func _ModuleService_GetModule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetModuleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ModuleServiceServer).GetModule(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ModuleService/GetModule",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ModuleServiceServer).GetModule(ctx, req.(*GetModuleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ModuleService_GetModules_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetModulesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ModuleServiceServer).GetModules(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ModuleService/GetModules",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ModuleServiceServer).GetModules(ctx, req.(*GetModulesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ModuleService_GetEventsForModules_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetModulesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ModuleServiceServer).GetEventsForModules(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ModuleService/GetEventsForModules",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ModuleServiceServer).GetEventsForModules(ctx, req.(*GetModulesRequest))
}
return interceptor(ctx, in, info, handler)
}
// ModuleService_ServiceDesc is the grpc.ServiceDesc for ModuleService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ModuleService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ModuleService",
HandlerType: (*ModuleServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetModule",
Handler: _ModuleService_GetModule_Handler,
},
{
MethodName: "GetModules",
Handler: _ModuleService_GetModules_Handler,
},
{
MethodName: "GetEventsForModules",
Handler: _ModuleService_GetEventsForModules_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "modules.proto",
}

View File

@ -0,0 +1,81 @@
#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/>.
# build stage
FROM golang:alpine AS build
WORKDIR /htwkalender-data-manager
RUN apk add --no-cache --update go gcc g++
# Copy the source from the current directory to the Working Directory inside the container
COPY go.mod go.sum ./
RUN go mod download
COPY data-manager/. ./data-manager
COPY common/. ./common
RUN CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-data-manager data-manager/main.go
# production stage
FROM alpine:3.20.1 AS prod
WORKDIR /htwkalender-data-manager
ARG USER=ical
RUN adduser -Ds /bin/sh "$USER" && \
chown "$USER":"$USER" ./
USER $USER
RUN mkdir -p data
# copies executable from build container
COPY --chown=$USER:$USER --chmod=644 --from=build /htwkalender-data-manager ./
RUN chmod +x main
# Expose port 8090 to the outside world
EXPOSE 8090
ENTRYPOINT ["./main", "serve"]
FROM golang:1.21.6 AS dev
# Set the Current Working Directory inside the container
WORKDIR /htwkalender-data-manager
ARG USER=ical
RUN adduser "$USER" && \
chown "$USER":"$USER" ./ \
&& mkdir -p /htwkalender-data-manager/data \
&& chown "$USER":"$USER" /htwkalender-data-manager/data
# Copy go mod and sum files
COPY go.mod go.sum ./
RUN go mod download
# Copy the source from the current directory to the Working Directory inside the container
COPY data-manager/. ./data-manager
COPY common/. ./common
# Build the Go app
RUN CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-data-manager data-manager/main.go
# Expose port 8091 to the outside world
EXPOSE 8091
USER $USER
# Entry point
ENTRYPOINT ["./main", "serve"]

View File

@ -19,28 +19,47 @@ package main
import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
_ "htwkalender/migrations"
"htwkalender/service"
_ "htwkalender/data-manager/migrations"
"htwkalender/data-manager/model/serviceModel"
"htwkalender/data-manager/service"
"htwkalender/data-manager/service/events"
"htwkalender/data-manager/service/grpc"
"log/slog"
"os"
"strings"
)
func main() {
func setupApp() *pocketbase.PocketBase {
app := pocketbase.New()
courseService := events.NewPocketBaseCourseService(app)
eventService := events.NewPocketBaseEventService(app)
services := serviceModel.Service{
CourseService: courseService,
EventService: eventService,
App: app,
}
// loosely check if it was executed using "go run"
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
//start grpc server
go grpc.StartGRPCServer(app)
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
// enable auto creation of migration files when making collection changes in the Admin UI
// (the isGoRun check is to enable it only during development)
Automigrate: isGoRun,
})
service.AddRoutes(app)
service.AddSchedules(app)
service.AddRoutes(services)
service.AddSchedules(services)
return app
}
func main() {
app := setupApp()
if err := app.Start(); err != nil {
slog.Error("Failed to start app: %v", "error", err)
slog.Error("Failed to start app: ", "error", err)
}
}

View File

@ -0,0 +1,42 @@
//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 main
import (
"testing"
)
func TestSetupApp(t *testing.T) {
tests := []struct {
name string
}{
{
name: "Test setupApp",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := setupApp()
go func() {
if err := app.Start(); err != nil {
t.Errorf("Failed to start app: %v", err)
return
}
}()
})
}
}

View File

@ -0,0 +1,461 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models"
)
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "cfq9mqlmd97v8z5",
"created": "2023-09-19 17:31:15.957Z",
"updated": "2024-07-13 11:37:49.151Z",
"name": "groups",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "85msl21p",
"name": "university",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "2sii4dtp",
"name": "shortcut",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "uiwgo28f",
"name": "groupId",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "y0l1lrzs",
"name": "course",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": 2,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "kr62mhbz",
"name": "faculty",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "ya6znpez",
"name": "facultyId",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "bdhcrksy",
"name": "semester",
"type": "text",
"required": true,
"presentable": false,
"unique": false,
"options": {
"min": 2,
"max": 2,
"pattern": "ws|ss"
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_rcaN2Oq` + "`" + ` ON ` + "`" + `groups` + "`" + ` (\n ` + "`" + `course` + "`" + `,\n ` + "`" + `semester` + "`" + `\n)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "d65h4wh7zk13gxp",
"created": "2023-09-19 17:31:15.957Z",
"updated": "2024-07-13 11:37:49.145Z",
"name": "feeds",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "cowxjfmc",
"name": "modules",
"type": "json",
"required": true,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "wmmney8x",
"name": "retrieved",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": "",
"createRule": null,
"updateRule": "",
"deleteRule": null,
"options": {}
},
{
"id": "7her4515qsmrxe8",
"created": "2023-09-19 17:31:15.958Z",
"updated": "2024-07-13 11:37:49.145Z",
"name": "events",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "m8ne8e3m",
"name": "Day",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "xnsxqp7j",
"name": "Week",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "aeuskrjo",
"name": "Name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "klrzqyw0",
"name": "EventType",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "5zltexoy",
"name": "Prof",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "gy3nvfmx",
"name": "Rooms",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "hn7b8dfy",
"name": "Notes",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "axskpwm8",
"name": "BookedAt",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "vyyefxp7",
"name": "course",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "vlbpm9fz",
"name": "semester",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "0kahthzr",
"name": "uuid",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "6hkjwgb4",
"name": "start",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "szbefpjf",
"name": "end",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "nlnnxu7x",
"name": "Compulsory",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [
"CREATE INDEX ` + "`" + `idx_4vOTAiC` + "`" + ` ON ` + "`" + `events` + "`" + ` (\n ` + "`" + `Name` + "`" + `,\n ` + "`" + `course` + "`" + `,\n ` + "`" + `start` + "`" + `,\n ` + "`" + `end` + "`" + `,\n ` + "`" + `semester` + "`" + `,\n ` + "`" + `EventType` + "`" + `,\n ` + "`" + `Compulsory` + "`" + `\n)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-07-13 11:37:48.913Z",
"updated": "2024-07-13 11:37:49.145Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
}
]`
collections := []*models.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
return err
}
return daos.New(db).ImportCollections(collections, true, nil)
}, func(db dbx.Builder) error {
return nil
})
}

View File

@ -0,0 +1,51 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("d65h4wh7zk13gxp")
if err != nil {
return err
}
// add
new_deleted := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "5d7vjjgo",
"name": "deleted",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}`), new_deleted); err != nil {
return err
}
collection.Schema.AddField(new_deleted)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("d65h4wh7zk13gxp")
if err != nil {
return err
}
// remove
collection.Schema.RemoveField("5d7vjjgo")
return dao.SaveCollection(collection)
})
}

View File

@ -57,6 +57,10 @@ type Event struct {
models.BaseModel
}
type EventType struct {
EventType string `db:"EventType" json:"eventType"`
}
func (e *Event) Equals(event Event) bool {
return e.Day == event.Day &&
e.Week == event.Week &&
@ -79,7 +83,7 @@ func (e *Event) SetCourse(course string) Event {
return *e
}
// Creates an AnonymizedEventDTO from an Event hiding all sensitive data
// AnonymizeEvent Creates an AnonymizedEventDTO from an Event hiding all sensitive data
func (e *Event) AnonymizeEvent() AnonymizedEventDTO {
return AnonymizedEventDTO{
Day: e.Day,

View File

@ -24,7 +24,7 @@ import (
"github.com/pocketbase/pocketbase/tools/types"
)
func TestEvents_Contains(t *testing.T) {
func TestEventsContains(t *testing.T) {
specificTime, _ := types.ParseDateTime("2020-01-01 12:00:00.000Z")
type args struct {
@ -37,25 +37,25 @@ func TestEvents_Contains(t *testing.T) {
want bool
}{
{
name: "empty events",
name: "event contains empty events",
m: Events{},
args: args{event: Event{}},
want: false,
},
{
name: "one event",
name: "event contains one event",
m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}},
args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}},
want: true,
},
{
name: "two events",
name: "event contains two events",
m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}},
args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}},
want: true,
},
{
name: "two events with different values",
name: "event contains two events with different values",
m: Events{{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}},
args: args{event: Event{Day: "test3", Week: "test3", Start: specificTime, End: specificTime, Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}},
want: false,
@ -70,7 +70,7 @@ func TestEvents_Contains(t *testing.T) {
}
}
func TestEvent_Equals(t *testing.T) {
func TestEventEquals(t *testing.T) {
specificTime, _ := types.ParseDateTime("2020-01-01 12:00:00.000Z")
type fields struct {
@ -99,25 +99,25 @@ func TestEvent_Equals(t *testing.T) {
want bool
}{
{
name: "empty events",
name: "event equals empty events",
fields: fields{},
args: args{event: Event{}},
want: true,
},
{
name: "one empty one not",
name: "event equals one empty one not",
fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"},
args: args{event: Event{}},
want: false,
},
{
name: "one event",
name: "event equals one event",
fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"},
args: args{event: Event{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}},
want: true,
},
{
name: "two events",
name: "event equals two events",
fields: fields{Day: "test", Week: "test", Start: specificTime, End: specificTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"},
args: args{event: Event{Day: "test2", Week: "test2", Start: specificTime, End: specificTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}},
want: false,
@ -148,7 +148,7 @@ func TestEvent_Equals(t *testing.T) {
}
}
func TestEvent_AnonymizeEvent(t *testing.T) {
func TestEventAnonymizeEvent(t *testing.T) {
type fields struct {
UUID string
Day string
@ -172,22 +172,22 @@ func TestEvent_AnonymizeEvent(t *testing.T) {
want AnonymizedEventDTO
}{
{
name: "empty event",
name: "event anonymize empty event",
fields: fields{},
want: AnonymizedEventDTO{Day: "", Week: "", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "", Free: false},
},
{
name: "one event",
name: "event anonymize one event",
fields: fields{Name: "Event", Day: "test", Week: "test", Rooms: "test"},
want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: false},
},
{
name: "one event with free",
name: "event anonymize one event with free",
fields: fields{Name: "Räume zur freien Verfügung", Day: "test", Week: "test", Rooms: "test", Course: "test"},
want: AnonymizedEventDTO{Day: "test", Week: "test", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "test", Free: true},
},
{
name: "another free event",
name: "event anonymize another free event",
fields: fields{Name: "Zur freien Verfügung", Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Course: "42INM-3"},
want: AnonymizedEventDTO{Day: "Montag", Week: "5", Start: types.DateTime{}, End: types.DateTime{}, Rooms: "TR_A1.28-S", Free: true},
},
@ -217,3 +217,264 @@ func TestEvent_AnonymizeEvent(t *testing.T) {
})
}
}
func TestEventGetName(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: "event get name - empty event",
fields: fields{},
want: "",
},
{
name: "event get 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 TestEventSetCourse(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 TestEventSetName(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 TestEventTableName(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 TestEventsContains1(t *testing.T) {
type args struct {
event Event
}
tests := []struct {
name string
m Events
args args
want bool
}{
{
name: "event contains - 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)
}
})
}
}

View File

@ -24,9 +24,14 @@ import (
type Feed struct {
Modules string `db:"modules" json:"modules"`
Retrieved types.DateTime `db:"retrieved" json:"retrieved"`
Deleted bool `db:"deleted" json:"deleted"`
models.BaseModel
}
func (f *Feed) TableName() string {
return "feeds"
}
// SetModules set modules field
func (f *Feed) SetModules(modules string) {
f.Modules = modules

View File

@ -0,0 +1,45 @@
package model
import (
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/types"
"testing"
)
func TestFeedSetModules(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)
})
}
}

View File

@ -0,0 +1,126 @@
package model
import "testing"
func TestModuleDTOGetName(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 TestModuleDTOSetName(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 TestModuleSetName(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)
})
}
}

View File

@ -0,0 +1,12 @@
package serviceModel
import (
"github.com/pocketbase/pocketbase"
"htwkalender/data-manager/service/events"
)
type Service struct {
App *pocketbase.PocketBase
EventService events.EventService
CourseService events.CourseService
}

View File

@ -0,0 +1,78 @@
//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 service
import (
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"htwkalender/data-manager/service/feed"
"htwkalender/data-manager/service/ical"
"io"
"log/slog"
"net/http"
)
func addFeedRoutes(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/feed",
Handler: func(c echo.Context) error {
requestBody, _ := io.ReadAll(c.Request().Body)
result, err := ical.CreateIndividualFeed(requestBody, app)
if err != nil {
slog.Error("Failed to create individual feed", "error", err)
return c.JSON(http.StatusInternalServerError, "Failed to create individual feed")
}
return c.JSON(http.StatusOK, result)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete,
Path: "/api/feed",
Handler: func(c echo.Context) error {
token := c.QueryParam("token")
err := feed.MarkFeedForDeletion(app.Dao(), token)
if err != nil {
return c.JSON(http.StatusNotFound, err)
} else {
return c.JSON(http.StatusOK, "Feed deleted")
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
},
})
if err != nil {
return err
}
return nil
})
}

View File

@ -17,18 +17,17 @@
package service
import (
"htwkalender/service/events"
"htwkalender/service/fetch/sport"
v1 "htwkalender/service/fetch/v1"
v2 "htwkalender/service/fetch/v2"
"htwkalender/service/functions/time"
"htwkalender/service/ical"
"htwkalender/service/room"
"htwkalender/data-manager/model/serviceModel"
"htwkalender/data-manager/service/course"
"htwkalender/data-manager/service/fetch/sport"
v1 "htwkalender/data-manager/service/fetch/v1"
v2 "htwkalender/data-manager/service/fetch/v2"
"htwkalender/data-manager/service/functions/time"
"htwkalender/data-manager/service/room"
"log/slog"
"net/http"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"go.mongodb.org/mongo-driver/bson"
@ -36,23 +35,23 @@ import (
const RoomOccupancyGranularity = 15
func AddRoutes(app *pocketbase.PocketBase) {
func AddRoutes(services serviceModel.Service) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/fetch/events",
Handler: func(c echo.Context) error {
savedEvents, err := v2.ParseEventsFromRemote(app)
savedEvents, err := v2.ParseEventsFromRemote(services.App)
if err != nil {
slog.Error("Failed to parse events from remote: %v", "error", err)
slog.Error("Failed to parse events from remote: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to parse events from remote")
} else {
return c.JSON(http.StatusOK, savedEvents)
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})
@ -62,19 +61,38 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.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(services)
return c.JSON(http.StatusOK, "Daily events fetched")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})
if err != nil {
return err
}
return nil
})
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/fetch/groups",
Handler: func(c echo.Context) error {
groups, err := v1.FetchSeminarGroups(app)
groups, err := v1.FetchSeminarGroups(services.App)
if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to fetch seminar groups")
}
return c.JSON(http.StatusOK, groups)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})
@ -84,20 +102,20 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/fetch/sports",
Handler: func(c echo.Context) error {
sportEvents, err := sport.FetchAndUpdateSportEvents(app)
sportEvents, err := sport.FetchAndUpdateSportEvents(services.App)
if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to fetch sport events")
}
return c.JSON(http.StatusOK, sportEvents)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})
@ -107,19 +125,19 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete,
Path: "/api/modules",
Handler: func(c echo.Context) error {
err := events.DeleteAllEvents(app)
err := services.EventService.DeleteAllEvents()
if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to delete events")
}
return c.JSON(http.StatusOK, "Events deleted")
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})
@ -129,19 +147,19 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/rooms",
Handler: func(c echo.Context) error {
rooms, err := room.GetRooms(app)
rooms, err := room.GetRooms(services.App)
if err != nil {
return c.JSON(http.StatusBadRequest, "Failed to get rooms")
}
return c.JSON(http.StatusOK, rooms)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -151,22 +169,22 @@ func AddRoutes(app *pocketbase.PocketBase) {
})
// API Endpoint to get all events for a specific room on a specific day
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/schedule/day",
Handler: func(c echo.Context) error {
roomParam := c.QueryParam("room")
date := c.QueryParam("date")
roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date)
roomSchedule, err := room.GetRoomScheduleForDay(services.App, roomParam, date)
if err != nil {
slog.Error("Failed to get room schedule for day: %v", "error", err)
slog.Error("Failed to get room schedule for day: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day")
}
return c.JSON(http.StatusOK, roomSchedule)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -210,7 +228,7 @@ func AddRoutes(app *pocketbase.PocketBase) {
})
// API Endpoint to create a new iCal feed
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/schedule",
@ -218,15 +236,15 @@ func AddRoutes(app *pocketbase.PocketBase) {
roomParam := c.QueryParam("room")
to := c.QueryParam("to")
from := c.QueryParam("from")
roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to)
roomSchedule, err := room.GetRoomSchedule(services.App, roomParam, from, to)
if err != nil {
slog.Error("Failed to get room schedule: %v", "error", err)
slog.Error("Failed to get room schedule:", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get room schedule")
}
return c.JSON(http.StatusOK, roomSchedule)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -236,30 +254,30 @@ func AddRoutes(app *pocketbase.PocketBase) {
})
// API Endpoint to get all rooms that have no events in a specific time frame
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/rooms/free",
Handler: func(c echo.Context) error {
from, err := time.ParseTime(c.QueryParam("from"))
if err != nil {
slog.Error("Failed to parse time: %v", "error", err)
slog.Error("Failed to parse time: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to parse time")
}
to, err := time.ParseTime(c.QueryParam("to"))
if err != nil {
slog.Error("Failed to parse time: %v", "error", err)
slog.Error("Failed to parse time: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to parse time")
}
rooms, err := room.GetFreeRooms(app, from, to)
rooms, err := room.GetFreeRooms(services.App, from, to)
if err != nil {
slog.Error("Failed to get free rooms: %v", "error", err)
slog.Error("Failed to get free rooms: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get free rooms")
}
return c.JSON(http.StatusOK, rooms)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -268,26 +286,27 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
addFeedRoutes(app)
addFeedRoutes(services.App)
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/course/modules",
Handler: func(c echo.Context) error {
course := c.QueryParam("course")
semester := c.QueryParam("semester")
modules, err := events.GetModulesForCourseDistinct(app, course, semester)
modules, err := services.EventService.GetModulesForCourseDistinct(
c.QueryParam("course"),
c.QueryParam("semester"),
)
if err != nil {
slog.Error("Failed to get modules for course and semester: %v", "error", err)
slog.Error("Failed to get modules for course and semester: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester")
} else {
return c.JSON(http.StatusOK, modules)
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -296,20 +315,20 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/modules",
Handler: func(c echo.Context) error {
modules, err := events.GetAllModulesDistinct(app)
modules, err := services.EventService.GetAllModulesDistinct()
if err != nil {
slog.Error("Failed to get modules: %v", "error", err)
slog.Error("Failed to get modules: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get modules")
}
return c.JSON(http.StatusOK, modules)
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -318,22 +337,22 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/module",
Handler: func(c echo.Context) error {
requestModule := c.QueryParam("uuid")
module, err := events.GetModuleByUUID(app, requestModule)
module, err := services.EventService.GetModuleByUUID(requestModule)
if err != nil {
slog.Error("Failed to get module: %v", "error", err)
slog.Error("Failed to get module: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get module")
} else {
return c.JSON(http.StatusOK, module)
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -342,22 +361,26 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/courses",
Handler: func(c echo.Context) error {
semester := c.QueryParam("semester")
if semester == "" {
courses := events.GetAllCourses(app)
courses := services.CourseService.GetAllCourses()
return c.JSON(200, courses)
} else {
courses := events.GetAllCoursesForSemester(app, semester)
return c.JSON(200, courses)
seminarGroups := services.CourseService.GetAllCoursesForSemester(semester)
courseStringList := make([]string, 0)
for _, seminarGroup := range seminarGroups {
courseStringList = append(courseStringList, seminarGroup.Course)
}
return c.JSON(200, courseStringList)
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -367,23 +390,22 @@ func AddRoutes(app *pocketbase.PocketBase) {
})
// api end point to get all courses for a specific semester with courses that have events
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/courses/events",
Handler: func(c echo.Context) error {
semester := c.QueryParam("semester")
courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester)
courses, err := services.CourseService.GetAllCoursesForSemesterWithEvents(semester)
if err != nil {
slog.Error("Failed to get courses for semester with events: %v", "error", err)
slog.Error("Failed to get courses for semester with events: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events")
} else {
return c.JSON(200, courses)
return c.JSON(http.StatusOK, courses)
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
},
})
if err != nil {
@ -392,48 +414,48 @@ func AddRoutes(app *pocketbase.PocketBase) {
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// API Endpoint to get all eventTypes from the database distinct
services.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 := services.EventService.GetEventTypes()
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{
apis.ActivityLogger(services.App),
},
})
if err != nil {
return err
}
return nil
})
services.App.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodDelete,
Path: "/api/events",
Handler: func(c echo.Context) error {
course := c.QueryParam("course")
semester := c.QueryParam("semester")
err := events.DeleteAllEventsByCourseAndSemester(app, course, semester)
err := services.EventService.DeleteAllEventsByCourseAndSemester(
c.QueryParam("course"),
c.QueryParam("semester"),
)
if err != nil {
slog.Error("Failed to delete events: %v", "error", err)
slog.Error("Failed to delete events: ", "error", err)
return c.JSON(http.StatusBadRequest, "Failed to delete events")
} else {
return c.JSON(http.StatusBadRequest, "Events deleted")
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", "error", err)
return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds")
} else {
return c.JSON(http.StatusOK, "Migrated")
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.ActivityLogger(services.App),
apis.RequireAdminAuth(),
},
})

Some files were not shown because too many files have changed in this diff Show More