diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9350acd..fbcc4a5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,33 +33,60 @@ 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 build-frontend: image: node:lts @@ -75,17 +102,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 +139,7 @@ test-frontend: dependencies: - lint-frontend -build-backend-image: +build-data-manager-image: stage: oci-build image: docker:latest services: @@ -108,7 +147,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 +155,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/data-manager - 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/ical + - docker push $IMAGE_TAG + rules: + - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development" + changes: + - services/ical/**/* build-frontend-image: stage: oci-build diff --git a/backend/backend.iml b/backend/backend.iml deleted file mode 100644 index 49df094..0000000 --- a/backend/backend.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/backend/service/addCalDavRoutes.go b/backend/service/addCalDavRoutes.go deleted file mode 100644 index 8f91f50..0000000 --- a/backend/service/addCalDavRoutes.go +++ /dev/null @@ -1,198 +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 . - -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") - } - - c.Response().Header().Set("Content-type", "text/calendar") - c.Response().Header().Set("charset", "utf-8") - c.Response().Header().Set("Content-Disposition", "inline") - c.Response().Header().Set("filename", "calendar.ics") - - _, err = c.Response().Write([]byte(result)) - - if err != nil { - return err - } - 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 - }) -} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c22cc99..c394f53 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -15,7 +15,7 @@ #along with this program. If not, see . services: - htwkalender-backend: + htwkalender-data-manager: 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" pull_policy: always @@ -25,12 +25,21 @@ services: networks: - "net" + htwkalender-ical: + image: DOCKER_REGISTRY_REPO-ical # DOCKER_REGISTRY_REPO will be replaced by CI + pull_policy: always + restart: always + target: prod + networks: + - "net" + htwkalender-frontend: image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI pull_policy: always restart: always depends_on: - - htwkalender-backend + - htwkalender-data-manager + - htwkalender-ical networks: - "net" @@ -42,8 +51,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" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f33790e..dafc959 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -15,8 +15,8 @@ #along with this program. If not, see . services: - htwkalender-backend: - image: DOCKER_REGISTRY_REPO-backend # DOCKER_REGISTRY_REPO will be replaced by CI + 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/pb_data" pull_policy: always restart: always @@ -25,12 +25,20 @@ services: networks: - "net" + htwkalender-ical: + image: DOCKER_REGISTRY_REPO-ical # DOCKER_REGISTRY_REPO will be replaced by CI + pull_policy: always + restart: always + target: prod + networks: + - "net" + htwkalender-frontend: image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI pull_policy: always restart: always depends_on: - - htwkalender-backend + - htwkalender-data-manager networks: - "net" @@ -44,7 +52,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" diff --git a/docker-compose.yml b/docker-compose.yml index 9eb3608..101cdf6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,17 +15,24 @@ #along with this program. If not, see . services: - htwkalender-backend: + htwkalender-data-manager: build: dockerfile: Dockerfile - context: ./backend + context: services/data-manager target: dev # prod command: "--http=0.0.0.0:8090 --dir=/htwkalender/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 + # - ./data-manager:/htwkalender/data # for development with bind mount from project directory + + htwkalender-ical: + build: + dockerfile: Dockerfile + context: ./services/ical + target: dev # prod + htwkalender-frontend: build: @@ -44,7 +51,7 @@ services: volumes: - ./reverseproxy.local.conf:/opt/bitnami/nginx/conf/nginx.conf depends_on: - - htwkalender-backend + - htwkalender-data-manager - htwkalender-frontend ports: - "80:80" diff --git a/frontend/src/api/createFeed.ts b/frontend/src/api/createFeed.ts index 7b8fcc4..81a897c 100644 --- a/frontend/src/api/createFeed.ts +++ b/frontend/src/api/createFeed.ts @@ -18,7 +18,7 @@ import { Module } from "../model/module.ts"; export async function createIndividualFeed(modules: Module[]): Promise { try { - const response = await fetch("/api/createFeed", { + const response = await fetch("/api/feed", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/api/fetchModule.ts b/frontend/src/api/fetchModule.ts index f6cfb27..0238933 100644 --- a/frontend/src/api/fetchModule.ts +++ b/frontend/src/api/fetchModule.ts @@ -17,7 +17,7 @@ import { Module } from "../model/module"; export async function fetchModule(module: Module): Promise { - // request to the backend on /api/module with query parameters name as the module name + // 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) diff --git a/frontend/src/api/requestFreeRooms.ts b/frontend/src/api/requestFreeRooms.ts index 4a33d62..5d467ec 100644 --- a/frontend/src/api/requestFreeRooms.ts +++ b/frontend/src/api/requestFreeRooms.ts @@ -14,7 +14,7 @@ //You should have received a copy of the GNU Affero General Public License //along with this program. If not, see . -// 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, diff --git a/reverseproxy.conf b/reverseproxy.conf index 7ced37c..9d2cb1b 100644 --- a/reverseproxy.conf +++ b/reverseproxy.conf @@ -107,6 +107,12 @@ http { 1 $binary_remote_addr; } + # Different rate limits for different request methods + map $request_method $limit_zone { + POST createFeed; # Create feed is limited to 1 request per minute + default feed; # All other requests are limited to 20 requests per minute + } + # 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; @@ -119,13 +125,14 @@ http { server_name cal.htwk-leipzig.de; location /api/feed { - proxy_pass http://htwkalender-backend:8090; + 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 zone=$limit_zone burst=10 nodelay; + limit_req_status 429; } location / { @@ -140,13 +147,14 @@ http { server_name htwkalender.de; location /api/feed { - proxy_pass http://htwkalender-backend:8090; + 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 zone=$limit_zone burst=10 nodelay; + limit_req_status 429; } location / { @@ -174,8 +182,19 @@ http { ssl_certificate cal.htwk-leipzig.de.pem; ssl_certificate_key cal.htwk-leipzig.de.key.pem; + location /api/feed { + 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=$limit_zone 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; @@ -185,7 +204,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; @@ -203,7 +222,7 @@ http { } location /api/events/types { - 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; @@ -221,7 +240,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; @@ -239,7 +258,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; @@ -257,7 +276,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; @@ -274,30 +293,8 @@ 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-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"; diff --git a/reverseproxy.dev.conf b/reverseproxy.dev.conf index 7a99919..3617907 100644 --- a/reverseproxy.dev.conf +++ b/reverseproxy.dev.conf @@ -108,6 +108,12 @@ http { 1 $binary_remote_addr; } + # Different rate limits for different request methods + map $request_method $limit_zone { + POST createFeed; # Create feed is limited to 1 request per minute + default feed; # All other requests are limited to 20 requests per minute + } + # 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; @@ -130,17 +136,18 @@ http { ssl_certificate_key dev_htwkalender_de.key.pem; location /api/feed { - proxy_pass http://htwkalender-backend:8090; + 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 zone=$limit_zone 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; @@ -150,7 +157,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; @@ -168,7 +175,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; @@ -186,7 +193,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; @@ -204,7 +211,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; @@ -221,20 +228,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"; diff --git a/reverseproxy.local.conf b/reverseproxy.local.conf index 9f41876..16c5eba 100644 --- a/reverseproxy.local.conf +++ b/reverseproxy.local.conf @@ -56,8 +56,17 @@ http { listen [::]:80; http2 on; + location /api/feed { + 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; + } + 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; @@ -66,7 +75,7 @@ http { } location /_ { - proxy_pass http://htwkalender-backend:8090; + proxy_pass http://htwkalender-data-manager:8090; # Increase upload file size client_max_body_size 100m; } diff --git a/backend/.gitignore b/services/data-manager/.gitignore similarity index 100% rename from backend/.gitignore rename to services/data-manager/.gitignore diff --git a/backend/Dockerfile b/services/data-manager/Dockerfile similarity index 100% rename from backend/Dockerfile rename to services/data-manager/Dockerfile diff --git a/backend/LICENSE b/services/data-manager/LICENSE similarity index 100% rename from backend/LICENSE rename to services/data-manager/LICENSE diff --git a/backend/README.md b/services/data-manager/README.md similarity index 100% rename from backend/README.md rename to services/data-manager/README.md diff --git a/backend/go.mod b/services/data-manager/go.mod similarity index 100% rename from backend/go.mod rename to services/data-manager/go.mod diff --git a/backend/go.sum b/services/data-manager/go.sum similarity index 100% rename from backend/go.sum rename to services/data-manager/go.sum diff --git a/backend/main.go b/services/data-manager/main.go similarity index 100% rename from backend/main.go rename to services/data-manager/main.go diff --git a/backend/migrations/1695150679_collections_snapshot.go b/services/data-manager/migrations/1695150679_collections_snapshot.go similarity index 100% rename from backend/migrations/1695150679_collections_snapshot.go rename to services/data-manager/migrations/1695150679_collections_snapshot.go diff --git a/backend/migrations/1695151278_add_admin_account.go b/services/data-manager/migrations/1695151278_add_admin_account.go similarity index 100% rename from backend/migrations/1695151278_add_admin_account.go rename to services/data-manager/migrations/1695151278_add_admin_account.go diff --git a/backend/migrations/1697532023_collections_snapshot.go b/services/data-manager/migrations/1697532023_collections_snapshot.go similarity index 100% rename from backend/migrations/1697532023_collections_snapshot.go rename to services/data-manager/migrations/1697532023_collections_snapshot.go diff --git a/backend/migrations/1697570688_collections_snapshot.go b/services/data-manager/migrations/1697570688_collections_snapshot.go similarity index 100% rename from backend/migrations/1697570688_collections_snapshot.go rename to services/data-manager/migrations/1697570688_collections_snapshot.go diff --git a/backend/migrations/1698017941_updated_events.go b/services/data-manager/migrations/1698017941_updated_events.go similarity index 100% rename from backend/migrations/1698017941_updated_events.go rename to services/data-manager/migrations/1698017941_updated_events.go diff --git a/backend/migrations/1698770845_updated_events.go b/services/data-manager/migrations/1698770845_updated_events.go similarity index 100% rename from backend/migrations/1698770845_updated_events.go rename to services/data-manager/migrations/1698770845_updated_events.go diff --git a/backend/migrations/1698770863_updated_events.go b/services/data-manager/migrations/1698770863_updated_events.go similarity index 100% rename from backend/migrations/1698770863_updated_events.go rename to services/data-manager/migrations/1698770863_updated_events.go diff --git a/backend/migrations/1698770891_collections_snapshot.go b/services/data-manager/migrations/1698770891_collections_snapshot.go similarity index 100% rename from backend/migrations/1698770891_collections_snapshot.go rename to services/data-manager/migrations/1698770891_collections_snapshot.go diff --git a/backend/migrations/1700512738_updated_feeds.go b/services/data-manager/migrations/1700512738_updated_feeds.go similarity index 100% rename from backend/migrations/1700512738_updated_feeds.go rename to services/data-manager/migrations/1700512738_updated_feeds.go diff --git a/backend/migrations/1700512916_collections_snapshot.go b/services/data-manager/migrations/1700512916_collections_snapshot.go similarity index 100% rename from backend/migrations/1700512916_collections_snapshot.go rename to services/data-manager/migrations/1700512916_collections_snapshot.go diff --git a/backend/migrations/1706827339_collections_snapshot.go b/services/data-manager/migrations/1706827339_collections_snapshot.go similarity index 100% rename from backend/migrations/1706827339_collections_snapshot.go rename to services/data-manager/migrations/1706827339_collections_snapshot.go diff --git a/backend/migrations/1706827586_updated_groups.go b/services/data-manager/migrations/1706827586_updated_groups.go similarity index 100% rename from backend/migrations/1706827586_updated_groups.go rename to services/data-manager/migrations/1706827586_updated_groups.go diff --git a/backend/model/eventModel.go b/services/data-manager/model/eventModel.go similarity index 100% rename from backend/model/eventModel.go rename to services/data-manager/model/eventModel.go diff --git a/backend/model/eventModel_test.go b/services/data-manager/model/eventModel_test.go similarity index 100% rename from backend/model/eventModel_test.go rename to services/data-manager/model/eventModel_test.go diff --git a/backend/model/feedModel.go b/services/data-manager/model/feedModel.go similarity index 100% rename from backend/model/feedModel.go rename to services/data-manager/model/feedModel.go diff --git a/backend/model/feedModel_test.go b/services/data-manager/model/feedModel_test.go similarity index 100% rename from backend/model/feedModel_test.go rename to services/data-manager/model/feedModel_test.go diff --git a/backend/model/icalModel.go b/services/data-manager/model/icalModel.go similarity index 100% rename from backend/model/icalModel.go rename to services/data-manager/model/icalModel.go diff --git a/backend/model/moduleModel.go b/services/data-manager/model/moduleModel.go similarity index 100% rename from backend/model/moduleModel.go rename to services/data-manager/model/moduleModel.go diff --git a/backend/model/moduleModel_test.go b/services/data-manager/model/moduleModel_test.go similarity index 100% rename from backend/model/moduleModel_test.go rename to services/data-manager/model/moduleModel_test.go diff --git a/backend/model/seminarGroup.go b/services/data-manager/model/seminarGroup.go similarity index 100% rename from backend/model/seminarGroup.go rename to services/data-manager/model/seminarGroup.go diff --git a/backend/model/seminarGroupXMLStruct.go b/services/data-manager/model/seminarGroupXMLStruct.go similarity index 100% rename from backend/model/seminarGroupXMLStruct.go rename to services/data-manager/model/seminarGroupXMLStruct.go diff --git a/backend/model/sportFetcherModel.go b/services/data-manager/model/sportFetcherModel.go similarity index 100% rename from backend/model/sportFetcherModel.go rename to services/data-manager/model/sportFetcherModel.go diff --git a/backend/openapi.yml b/services/data-manager/openapi.yml similarity index 100% rename from backend/openapi.yml rename to services/data-manager/openapi.yml diff --git a/backend/pb_schema.json b/services/data-manager/pb_schema.json similarity index 100% rename from backend/pb_schema.json rename to services/data-manager/pb_schema.json diff --git a/services/data-manager/service/addCalDavRoutes.go b/services/data-manager/service/addCalDavRoutes.go new file mode 100644 index 0000000..9aae82f --- /dev/null +++ b/services/data-manager/service/addCalDavRoutes.go @@ -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 . + +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" + "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 := 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 + }) + +} diff --git a/backend/service/addRoute.go b/services/data-manager/service/addRoute.go similarity index 100% rename from backend/service/addRoute.go rename to services/data-manager/service/addRoute.go diff --git a/backend/service/addSchedule.go b/services/data-manager/service/addSchedule.go similarity index 100% rename from backend/service/addSchedule.go rename to services/data-manager/service/addSchedule.go diff --git a/backend/service/course/courseFunctions.go b/services/data-manager/service/course/courseFunctions.go similarity index 100% rename from backend/service/course/courseFunctions.go rename to services/data-manager/service/course/courseFunctions.go diff --git a/backend/service/date/dateFormat.go b/services/data-manager/service/date/dateFormat.go similarity index 100% rename from backend/service/date/dateFormat.go rename to services/data-manager/service/date/dateFormat.go diff --git a/backend/service/date/dateFormat_test.go b/services/data-manager/service/date/dateFormat_test.go similarity index 100% rename from backend/service/date/dateFormat_test.go rename to services/data-manager/service/date/dateFormat_test.go diff --git a/backend/service/db/dbEvents.go b/services/data-manager/service/db/dbEvents.go similarity index 100% rename from backend/service/db/dbEvents.go rename to services/data-manager/service/db/dbEvents.go diff --git a/backend/service/db/dbEvents_test.go b/services/data-manager/service/db/dbEvents_test.go similarity index 100% rename from backend/service/db/dbEvents_test.go rename to services/data-manager/service/db/dbEvents_test.go diff --git a/backend/service/db/dbFeeds.go b/services/data-manager/service/db/dbFeeds.go similarity index 100% rename from backend/service/db/dbFeeds.go rename to services/data-manager/service/db/dbFeeds.go diff --git a/backend/service/db/dbFeeds_test.go b/services/data-manager/service/db/dbFeeds_test.go similarity index 100% rename from backend/service/db/dbFeeds_test.go rename to services/data-manager/service/db/dbFeeds_test.go diff --git a/backend/service/db/dbFunctions.go b/services/data-manager/service/db/dbFunctions.go similarity index 100% rename from backend/service/db/dbFunctions.go rename to services/data-manager/service/db/dbFunctions.go diff --git a/backend/service/db/dbGroups.go b/services/data-manager/service/db/dbGroups.go similarity index 100% rename from backend/service/db/dbGroups.go rename to services/data-manager/service/db/dbGroups.go diff --git a/backend/service/db/dbRooms.go b/services/data-manager/service/db/dbRooms.go similarity index 100% rename from backend/service/db/dbRooms.go rename to services/data-manager/service/db/dbRooms.go diff --git a/backend/service/db/mockData/data.db b/services/data-manager/service/db/mockData/data.db similarity index 100% rename from backend/service/db/mockData/data.db rename to services/data-manager/service/db/mockData/data.db diff --git a/backend/service/db/mockData/logs.db b/services/data-manager/service/db/mockData/logs.db similarity index 100% rename from backend/service/db/mockData/logs.db rename to services/data-manager/service/db/mockData/logs.db diff --git a/backend/service/events/courseService.go b/services/data-manager/service/events/courseService.go similarity index 100% rename from backend/service/events/courseService.go rename to services/data-manager/service/events/courseService.go diff --git a/backend/service/events/courseService_test.go b/services/data-manager/service/events/courseService_test.go similarity index 100% rename from backend/service/events/courseService_test.go rename to services/data-manager/service/events/courseService_test.go diff --git a/backend/service/events/eventService.go b/services/data-manager/service/events/eventService.go similarity index 100% rename from backend/service/events/eventService.go rename to services/data-manager/service/events/eventService.go diff --git a/backend/service/events/eventService_test.go b/services/data-manager/service/events/eventService_test.go similarity index 100% rename from backend/service/events/eventService_test.go rename to services/data-manager/service/events/eventService_test.go diff --git a/backend/service/feed/feedFunctions.go b/services/data-manager/service/feed/feedFunctions.go similarity index 100% rename from backend/service/feed/feedFunctions.go rename to services/data-manager/service/feed/feedFunctions.go diff --git a/backend/service/feed/feedFunctions_test.go b/services/data-manager/service/feed/feedFunctions_test.go similarity index 100% rename from backend/service/feed/feedFunctions_test.go rename to services/data-manager/service/feed/feedFunctions_test.go diff --git a/backend/service/feed/mockData/data.db b/services/data-manager/service/feed/mockData/data.db similarity index 100% rename from backend/service/feed/mockData/data.db rename to services/data-manager/service/feed/mockData/data.db diff --git a/backend/service/feed/mockData/logs.db b/services/data-manager/service/feed/mockData/logs.db similarity index 100% rename from backend/service/feed/mockData/logs.db rename to services/data-manager/service/feed/mockData/logs.db diff --git a/backend/service/fetch/htmlDownloader.go b/services/data-manager/service/fetch/htmlDownloader.go similarity index 100% rename from backend/service/fetch/htmlDownloader.go rename to services/data-manager/service/fetch/htmlDownloader.go diff --git a/backend/service/fetch/sport/sportFetcher.go b/services/data-manager/service/fetch/sport/sportFetcher.go similarity index 100% rename from backend/service/fetch/sport/sportFetcher.go rename to services/data-manager/service/fetch/sport/sportFetcher.go diff --git a/backend/service/fetch/sport/sportFetcher_test.go b/services/data-manager/service/fetch/sport/sportFetcher_test.go similarity index 100% rename from backend/service/fetch/sport/sportFetcher_test.go rename to services/data-manager/service/fetch/sport/sportFetcher_test.go diff --git a/backend/service/fetch/v1/fetchSeminarEventService.go b/services/data-manager/service/fetch/v1/fetchSeminarEventService.go similarity index 100% rename from backend/service/fetch/v1/fetchSeminarEventService.go rename to services/data-manager/service/fetch/v1/fetchSeminarEventService.go diff --git a/backend/service/fetch/v1/fetchSeminarEventService_test.go b/services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go similarity index 100% rename from backend/service/fetch/v1/fetchSeminarEventService_test.go rename to services/data-manager/service/fetch/v1/fetchSeminarEventService_test.go diff --git a/backend/service/fetch/v1/fetchSeminarGroupService.go b/services/data-manager/service/fetch/v1/fetchSeminarGroupService.go similarity index 100% rename from backend/service/fetch/v1/fetchSeminarGroupService.go rename to services/data-manager/service/fetch/v1/fetchSeminarGroupService.go diff --git a/backend/service/fetch/v1/fetchSeminarGroupService_test.go b/services/data-manager/service/fetch/v1/fetchSeminarGroupService_test.go similarity index 100% rename from backend/service/fetch/v1/fetchSeminarGroupService_test.go rename to services/data-manager/service/fetch/v1/fetchSeminarGroupService_test.go diff --git a/backend/service/fetch/v1/htmlParsingFunctions.go b/services/data-manager/service/fetch/v1/htmlParsingFunctions.go similarity index 100% rename from backend/service/fetch/v1/htmlParsingFunctions.go rename to services/data-manager/service/fetch/v1/htmlParsingFunctions.go diff --git a/backend/service/fetch/v2/eventParser.go b/services/data-manager/service/fetch/v2/eventParser.go similarity index 100% rename from backend/service/fetch/v2/eventParser.go rename to services/data-manager/service/fetch/v2/eventParser.go diff --git a/backend/service/fetch/v2/fetcher.go b/services/data-manager/service/fetch/v2/fetcher.go similarity index 100% rename from backend/service/fetch/v2/fetcher.go rename to services/data-manager/service/fetch/v2/fetcher.go diff --git a/backend/service/fetch/v2/fetcher_test.go b/services/data-manager/service/fetch/v2/fetcher_test.go similarity index 100% rename from backend/service/fetch/v2/fetcher_test.go rename to services/data-manager/service/fetch/v2/fetcher_test.go diff --git a/backend/service/fetch/v2/htmlParsingFunctions.go b/services/data-manager/service/fetch/v2/htmlParsingFunctions.go similarity index 100% rename from backend/service/fetch/v2/htmlParsingFunctions.go rename to services/data-manager/service/fetch/v2/htmlParsingFunctions.go diff --git a/backend/service/functions/semester.go b/services/data-manager/service/functions/semester.go similarity index 100% rename from backend/service/functions/semester.go rename to services/data-manager/service/functions/semester.go diff --git a/backend/service/functions/semester_test.go b/services/data-manager/service/functions/semester_test.go similarity index 100% rename from backend/service/functions/semester_test.go rename to services/data-manager/service/functions/semester_test.go diff --git a/backend/service/functions/string.go b/services/data-manager/service/functions/string.go similarity index 100% rename from backend/service/functions/string.go rename to services/data-manager/service/functions/string.go diff --git a/backend/service/functions/string_test.go b/services/data-manager/service/functions/string_test.go similarity index 100% rename from backend/service/functions/string_test.go rename to services/data-manager/service/functions/string_test.go diff --git a/backend/service/functions/time/mockClock.go b/services/data-manager/service/functions/time/mockClock.go similarity index 100% rename from backend/service/functions/time/mockClock.go rename to services/data-manager/service/functions/time/mockClock.go diff --git a/backend/service/functions/time/parse.go b/services/data-manager/service/functions/time/parse.go similarity index 100% rename from backend/service/functions/time/parse.go rename to services/data-manager/service/functions/time/parse.go diff --git a/backend/service/functions/time/realClock.go b/services/data-manager/service/functions/time/realClock.go similarity index 100% rename from backend/service/functions/time/realClock.go rename to services/data-manager/service/functions/time/realClock.go diff --git a/backend/service/functions/time/time.go b/services/data-manager/service/functions/time/time.go similarity index 100% rename from backend/service/functions/time/time.go rename to services/data-manager/service/functions/time/time.go diff --git a/backend/service/ical/ical.go b/services/data-manager/service/ical/ical.go similarity index 57% rename from backend/service/ical/ical.go rename to services/data-manager/service/ical/ical.go index d1cea64..ec604fc 100644 --- a/backend/service/ical/ical.go +++ b/services/data-manager/service/ical/ical.go @@ -17,60 +17,13 @@ package ical import ( - "bytes" "encoding/json" - "htwkalender/model" - "htwkalender/service/db" - "htwkalender/service/feed" - "time" - - "github.com/jordic/goics" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" + "htwkalender/model" + "htwkalender/service/db" ) -const expirationTime = 5 * time.Minute - -func Feed(app *pocketbase.PocketBase, token string) (string, error) { - var result string - - icalFeed, err := db.FindFeedByToken(token, app) - if icalFeed == nil && err != nil { - return "", err - } - - modules := make(map[string]model.FeedCollection) - var modulesArray []model.FeedCollection - _ = json.Unmarshal([]byte(icalFeed.Modules), &modulesArray) - - for _, module := range modulesArray { - modules[module.UUID] = module - } - - newFeed, createFeedErr := createFeedForToken(app, modules) - if createFeedErr != nil { - return "", createFeedErr - } - result = newFeed.Content - - return result, nil -} - -func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) { - events, err := db.GetPlanForModules(app, modules) - if err != nil { - return nil, apis.NewNotFoundError("Could not fetch events", err) - } - - // Combine Events - events = feed.CombineEventsInFeed(events) - - b := bytes.Buffer{} - goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules}) - icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)} - return icalFeed, nil -} - func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) { var modules []model.FeedCollection diff --git a/backend/service/ical/icalFileGeneration.go b/services/data-manager/service/ical/icalFileGeneration.go similarity index 100% rename from backend/service/ical/icalFileGeneration.go rename to services/data-manager/service/ical/icalFileGeneration.go diff --git a/backend/service/ical/icalFileGeneration_test.go b/services/data-manager/service/ical/icalFileGeneration_test.go similarity index 100% rename from backend/service/ical/icalFileGeneration_test.go rename to services/data-manager/service/ical/icalFileGeneration_test.go diff --git a/backend/service/ical/icsComponenter.go b/services/data-manager/service/ical/icsComponenter.go similarity index 100% rename from backend/service/ical/icsComponenter.go rename to services/data-manager/service/ical/icsComponenter.go diff --git a/backend/service/names/userDefinedNameTemplates.go b/services/data-manager/service/names/userDefinedNameTemplates.go similarity index 100% rename from backend/service/names/userDefinedNameTemplates.go rename to services/data-manager/service/names/userDefinedNameTemplates.go diff --git a/backend/service/names/userDefinedNameTemplates_test.go b/services/data-manager/service/names/userDefinedNameTemplates_test.go similarity index 100% rename from backend/service/names/userDefinedNameTemplates_test.go rename to services/data-manager/service/names/userDefinedNameTemplates_test.go diff --git a/backend/service/room/roomService.go b/services/data-manager/service/room/roomService.go similarity index 100% rename from backend/service/room/roomService.go rename to services/data-manager/service/room/roomService.go diff --git a/backend/service/room/roomService_test.go b/services/data-manager/service/room/roomService_test.go similarity index 100% rename from backend/service/room/roomService_test.go rename to services/data-manager/service/room/roomService_test.go diff --git a/services/ical/Dockerfile b/services/ical/Dockerfile new file mode 100644 index 0000000..5a6be0e --- /dev/null +++ b/services/ical/Dockerfile @@ -0,0 +1,70 @@ +#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 . + +# build stage +FROM golang:alpine AS build + +WORKDIR /app + +# Copy the source from the current directory to the Working Directory inside the container +COPY . ./ +# download needed modules +RUN apk add --no-cache --update go gcc g++ && \ + go mod download && \ + CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-ical + +# production stage +FROM alpine:latest AS prod + +WORKDIR /htwkalender-ical + +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 --from=build /htwkalender-ical ./ + +# Expose port 8090 to the outside world +EXPOSE 8091 + +ENTRYPOINT ["./htwkalender-ical"] + + +FROM golang:1.21.6 AS dev + +# Set the Current Working Directory inside the container +WORKDIR /htwkalender-ical + +# 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 *.go ./ +COPY . . + +# Build the Go app +RUN CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-ical + +# Expose port 8090 to the outside world +EXPOSE 8090 + +# Entry point +ENTRYPOINT ["./htwkalender-ical"] \ No newline at end of file diff --git a/services/ical/go.mod b/services/ical/go.mod new file mode 100644 index 0000000..c087dbe --- /dev/null +++ b/services/ical/go.mod @@ -0,0 +1,90 @@ +module htwkalender-ical + +go 1.21.6 + +require ( + github.com/gofiber/fiber/v3 v3.0.0-beta.2 + github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0 + github.com/pocketbase/pocketbase v0.22.12 +) + +require ( + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.13 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 // indirect + github.com/aws/smithy-go v1.20.2 // 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/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // 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.17.8 // indirect + github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // 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.22 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pocketbase/dbx v1.10.1 // 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/fasthttp v1.54.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.opencensus.io v0.24.0 // indirect + gocloud.dev v0.37.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/image v0.16.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.180.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect + modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect + modernc.org/libc v1.50.5 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.29.9 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/services/ical/go.sum b/services/ical/go.sum new file mode 100644 index 0000000..e4d76bd --- /dev/null +++ b/services/ical/go.sum @@ -0,0 +1,384 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s= +github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.17 h1:9b1Os1s11mF5qTIKLgSsyPG810di2+ySSLIIt9bwe9I= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.17/go.mod h1:9Wp7tDOMhv0+sb/FTRAkbHNQ7abYDnoJRzm5AAtCnTc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2 h1:rq2hglTQM3yHZvOPVMtNvLS5x6hijx7JvRDgKiTNDGQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.53.2/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= +github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/ganigeorgiev/fexpr v0.4.0 h1:ojitI+VMNZX/odeNL1x3RzTTE8qAIVvnSSYPNAnQFDI= +github.com/ganigeorgiev/fexpr v0.4.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= +github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV4E+HpAao= +github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM= +github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co= +github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0 h1:p+k2RozdR141dIkAbOuZafkZjrcjT/YvwYYH7qCSG+c= +github.com/jordic/goics v0.0.0-20210404174824-5a0337b716a0/go.mod h1:YHaw6sOIeFRob8Y9q/blEAMfVcLpeE9+vdhrwyEMxoI= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4= +github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA= +github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= +github.com/pocketbase/pocketbase v0.22.12 h1:2wWcs2yoyA3OkbW9cxFShemzchOh3eHFr1e8QvLy5is= +github.com/pocketbase/pocketbase v0.22.12/go.mod h1:yY/3IGi1tUbcI6yGVFspAyKi/IDHCntdYk4IAP32UF0= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0= +github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro= +gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/cc/v4 v4.21.0 h1:D/gLKtcztomvWbsbvBKo3leKQv+86f+DdqEZBBXhnag= +modernc.org/cc/v4 v4.21.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.17.3 h1:t2CQci84jnxKw3GGnHvjGKjiNZeZqyQx/023spkk4hU= +modernc.org/ccgo/v4 v4.17.3/go.mod h1:1FCbAtWYJoKuc+AviS+dH+vGNtYmFJqBeRWjmnDWsIg= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.50.5 h1:ZzeUd0dIc/sUtoPTCYIrgypkuzoGzNu6kbEWj2VuEmk= +modernc.org/libc v1.50.5/go.mod h1:rhzrUx5oePTSTIzBgM0mTftwWHK8tiT9aNFUt1mldl0= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow= +modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/services/ical/main.go b/services/ical/main.go new file mode 100644 index 0000000..4865e7c --- /dev/null +++ b/services/ical/main.go @@ -0,0 +1,48 @@ +//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 . + +package main + +import ( + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" + "github.com/gofiber/fiber/v3/middleware/logger" + "htwkalender-ical/service" +) + +// main function for the ical service +// uses rest api to get the data from the data-manager +// exposes rest api endpoints with fiber to serve the data for clients +func main() { + + // Initialize a new Fiber app + webdavRequestMethods := []string{"PROPFIND", "MKCOL", "COPY", "MOVE"} + + app := fiber.New(fiber.Config{ + CaseSensitive: true, + StrictRouting: true, + ServerHeader: "Fiber", + AppName: "App Name", + RequestMethods: append(fiber.DefaultMethods[:], webdavRequestMethods...), + }) + + app.Use(logger.New()) + + // Add routes to the app instance for the data-manager ical service + service.AddFeedRoutes(app) + + log.Fatal(app.Listen(":8091")) +} diff --git a/services/ical/model/eventModel.go b/services/ical/model/eventModel.go new file mode 100644 index 0000000..2d92a0d --- /dev/null +++ b/services/ical/model/eventModel.go @@ -0,0 +1,111 @@ +//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 . + +package model + +import ( + "github.com/pocketbase/pocketbase/models" + "slices" + "sort" + "strings" + "time" +) + +type Events []Event + +func (events Events) Contains(event Event) bool { + return slices.Contains(events, event) +} + +func (events Events) Sort() { + // Sort events by start time + sort.Slice(events, func(i, j int) bool { + return time.Time(events[i].Start).Before(time.Time(events[j].Start)) + }) +} + +type AnonymizedEventDTO struct { + Day string `db:"Day" json:"day"` + Week string `db:"Week" json:"week"` + Start JSONTime `db:"start" json:"start"` + End JSONTime `db:"end" json:"end"` + Rooms string `db:"Rooms" json:"rooms"` + Free bool `json:"free"` +} + +type Event struct { + UUID string `db:"uuid" json:"uuid"` + Day string `db:"Day" json:"day"` + Week string `db:"Week" json:"week"` + Start JSONTime `db:"start" json:"start"` + End JSONTime `db:"end" json:"end"` + Name string `db:"Name" json:"name"` + EventType string `db:"EventType" json:"eventType"` + Compulsory string `db:"Compulsory" json:"compulsory"` + Prof string `db:"Prof" json:"prof"` + Rooms string `db:"Rooms" json:"rooms"` + Notes string `db:"Notes" json:"notes"` + BookedAt string `db:"BookedAt" json:"bookedAt"` + Course string `db:"course" json:"course"` + Semester string `db:"semester" json:"semester"` + 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 && + e.Start == event.Start && + e.End == event.End && + e.Name == event.Name && + e.Course == event.Course && + e.Prof == event.Prof && + e.Rooms == event.Rooms && + e.EventType == event.EventType +} + +func (e *Event) TableName() string { + return "events" +} + +// SetCourse func to set the course and returns the event +func (e *Event) SetCourse(course string) Event { + e.Course = course + return *e +} + +// AnonymizeEvent Creates an AnonymizedEventDTO from an Event hiding all sensitive data +func (e *Event) AnonymizeEvent() AnonymizedEventDTO { + return AnonymizedEventDTO{ + Day: e.Day, + Week: e.Week, + Start: e.Start, + End: e.End, + Rooms: e.Rooms, + Free: strings.Contains(strings.ToLower(e.Name), "zur freien verfügung"), + } +} + +func (e *Event) GetName() string { + return e.Name +} + +func (e *Event) SetName(name string) { + e.Name = name +} diff --git a/services/ical/model/eventModel_test.go b/services/ical/model/eventModel_test.go new file mode 100644 index 0000000..bee7eb5 --- /dev/null +++ b/services/ical/model/eventModel_test.go @@ -0,0 +1,483 @@ +//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 . + +package model + +import ( + "github.com/pocketbase/pocketbase/models" + "reflect" + "testing" + "time" +) + +func TestEvents_Contains(t *testing.T) { + // parse time for JSONTime "2020-01-01 12:00:00.000Z" + specificTime, _ := time.Parse("2006-01-02T15:04:05.000Z", "2020-01-01T12:00:00.000Z") + specificJSONTime := JSONTime(specificTime) + + type args struct { + event Event + } + tests := []struct { + name string + m Events + args args + want bool + }{ + { + name: "empty events", + m: Events{}, + args: args{event: Event{}}, + want: false, + }, + { + name: "one event", + m: Events{{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + args: args{event: Event{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + want: true, + }, + { + name: "two events", + m: Events{{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, {Day: "test2", Week: "test2", Start: specificJSONTime, End: specificJSONTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + args: args{event: Event{Day: "test2", Week: "test2", Start: specificJSONTime, End: specificJSONTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + want: true, + }, + { + name: "two events with different values", + m: Events{{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test", UUID: "439ßu56rf8u9ijn4f4-2345345"}, {Day: "test2", Week: "test2", Start: specificJSONTime, End: specificJSONTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2", UUID: "432a39ßu545349ijn4f4-23dsa45"}}, + args: args{event: Event{Day: "test3", Week: "test3", Start: specificJSONTime, End: specificJSONTime, Name: "test3", Course: "test3", Prof: "test3", Rooms: "test3", EventType: "test3", UUID: "934mf43r34f-g68h7655tg3"}}, + 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) + } + }) + } +} + +func TestEvent_Equals(t *testing.T) { + + specificTime, _ := time.Parse("2006-01-02T15:04:05.000Z", "2020-01-01T12:00:00.000Z") + specificJSONTime := JSONTime(specificTime) + + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + Name string + EventType string + Prof string + Rooms string + Notes string + BookedAt string + Course string + Semester string + BaseModel models.BaseModel + } + type args struct { + event Event + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "empty events", + fields: fields{}, + args: args{event: Event{}}, + want: true, + }, + { + name: "one empty one not", + fields: fields{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{}}, + want: false, + }, + { + name: "one event", + fields: fields{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}}, + want: true, + }, + { + name: "two events", + fields: fields{Day: "test", Week: "test", Start: specificJSONTime, End: specificJSONTime, Name: "test", Course: "test", Prof: "test", Rooms: "test", EventType: "test"}, + args: args{event: Event{Day: "test2", Week: "test2", Start: specificJSONTime, End: specificJSONTime, Name: "test2", Course: "test2", Prof: "test2", Rooms: "test2", EventType: "test2"}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &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, + 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 := m.Equals(tt.args.event); got != tt.want { + t.Errorf("Equals() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEvent_AnonymizeEvent(t *testing.T) { + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + 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 AnonymizedEventDTO + }{ + { + name: "empty event", + fields: fields{}, + want: AnonymizedEventDTO{Day: "", Week: "", Start: JSONTime{}, End: JSONTime{}, Rooms: "", Free: false}, + }, + { + name: "one event", + fields: fields{Name: "Event", Day: "test", Week: "test", Rooms: "test"}, + want: AnonymizedEventDTO{Day: "test", Week: "test", Start: JSONTime{}, End: JSONTime{}, Rooms: "test", Free: false}, + }, + { + name: "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: JSONTime{}, End: JSONTime{}, Rooms: "test", Free: true}, + }, + { + name: "another free event", + fields: fields{Name: "Zur freien Verfügung", Day: "Montag", Week: "5", Start: JSONTime{}, End: JSONTime{}, Rooms: "TR_A1.28-S", Course: "42INM-3"}, + want: AnonymizedEventDTO{Day: "Montag", Week: "5", Start: JSONTime{}, End: JSONTime{}, Rooms: "TR_A1.28-S", Free: true}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &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 := m.AnonymizeEvent(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Event.AnonymizeEvent() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEvent_GetName(t *testing.T) { + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + Name string + EventType string + Compulsory string + Prof string + Rooms string + Notes string + BookedAt string + Course string + Semester string + BaseModel models.BaseModel + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "empty event", + fields: fields{}, + want: "", + }, + { + name: "one event", + fields: fields{Name: "Event"}, + want: "Event", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Event{ + UUID: tt.fields.UUID, + Day: tt.fields.Day, + Week: tt.fields.Week, + Start: tt.fields.Start, + End: tt.fields.End, + Name: tt.fields.Name, + EventType: tt.fields.EventType, + Compulsory: tt.fields.Compulsory, + Prof: tt.fields.Prof, + Rooms: tt.fields.Rooms, + Notes: tt.fields.Notes, + BookedAt: tt.fields.BookedAt, + Course: tt.fields.Course, + Semester: tt.fields.Semester, + BaseModel: tt.fields.BaseModel, + } + if got := e.GetName(); got != tt.want { + t.Errorf("GetName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEvent_SetCourse(t *testing.T) { + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + Name string + EventType string + Compulsory string + Prof string + Rooms string + Notes string + BookedAt string + Course string + Semester string + BaseModel models.BaseModel + } + type args struct { + course string + } + tests := []struct { + name string + fields fields + args args + want Event + }{ + { + name: "set course", + fields: fields{}, + args: args{course: "test"}, + want: Event{Course: "test"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Event{ + UUID: tt.fields.UUID, + Day: tt.fields.Day, + Week: tt.fields.Week, + Start: tt.fields.Start, + End: tt.fields.End, + Name: tt.fields.Name, + EventType: tt.fields.EventType, + Compulsory: tt.fields.Compulsory, + Prof: tt.fields.Prof, + Rooms: tt.fields.Rooms, + Notes: tt.fields.Notes, + BookedAt: tt.fields.BookedAt, + Course: tt.fields.Course, + Semester: tt.fields.Semester, + BaseModel: tt.fields.BaseModel, + } + if got := e.SetCourse(tt.args.course); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SetCourse() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEvent_SetName(t *testing.T) { + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + Name string + EventType string + Compulsory string + Prof string + Rooms string + Notes string + BookedAt string + Course string + Semester string + BaseModel models.BaseModel + } + type args struct { + name string + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "set name", + fields: fields{ + Name: "name", + }, + args: args{ + name: "name", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Event{ + UUID: tt.fields.UUID, + Day: tt.fields.Day, + Week: tt.fields.Week, + Start: tt.fields.Start, + End: tt.fields.End, + Name: tt.fields.Name, + EventType: tt.fields.EventType, + Compulsory: tt.fields.Compulsory, + Prof: tt.fields.Prof, + Rooms: tt.fields.Rooms, + Notes: tt.fields.Notes, + BookedAt: tt.fields.BookedAt, + Course: tt.fields.Course, + Semester: tt.fields.Semester, + BaseModel: tt.fields.BaseModel, + } + e.SetName(tt.args.name) + }) + } +} + +func TestEvent_TableName(t *testing.T) { + type fields struct { + UUID string + Day string + Week string + Start JSONTime + End JSONTime + Name string + EventType string + Compulsory string + Prof string + Rooms string + Notes string + BookedAt string + Course string + Semester string + BaseModel models.BaseModel + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "table name", + fields: fields{}, + want: "events", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Event{ + UUID: tt.fields.UUID, + Day: tt.fields.Day, + Week: tt.fields.Week, + Start: tt.fields.Start, + End: tt.fields.End, + Name: tt.fields.Name, + EventType: tt.fields.EventType, + Compulsory: tt.fields.Compulsory, + Prof: tt.fields.Prof, + Rooms: tt.fields.Rooms, + Notes: tt.fields.Notes, + BookedAt: tt.fields.BookedAt, + Course: tt.fields.Course, + Semester: tt.fields.Semester, + BaseModel: tt.fields.BaseModel, + } + if got := e.TableName(); got != tt.want { + t.Errorf("TableName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEvents_Contains1(t *testing.T) { + type args struct { + event Event + } + tests := []struct { + name string + m Events + args args + want bool + }{ + { + name: "empty events", + m: Events{}, + args: args{event: Event{}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Contains(tt.args.event); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/services/ical/model/feedModel.go b/services/ical/model/feedModel.go new file mode 100644 index 0000000..864d629 --- /dev/null +++ b/services/ical/model/feedModel.go @@ -0,0 +1,44 @@ +//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 . + +package model + +type BaseModel struct { + Id string `db:"id" json:"id"` + Created JSONTime `db:"created" json:"created"` + Updated JSONTime `db:"updated" json:"updated"` + CollectionName string `db:"collection_name" json:"collectionName"` + CollectionId string `db:"collection_id" json:"collectionId"` +} + +type Feed struct { + Modules string `db:"modules" json:"modules"` + Retrieved JSONTime `db:"retrieved" json:"retrieved"` + BaseModel +} + +// SetModules set modules field +func (f *Feed) SetModules(modules string) { + f.Modules = modules +} + +type FeedCollection struct { + UUID string `db:"uuid" json:"uuid"` + Name string `db:"Name" json:"name"` + Course string `db:"course" json:"course"` + UserDefinedName string `db:"userDefinedName" json:"userDefinedName"` + Reminder bool `db:"reminder" json:"reminder"` +} diff --git a/services/ical/model/icalModel.go b/services/ical/model/icalModel.go new file mode 100644 index 0000000..a8b09ed --- /dev/null +++ b/services/ical/model/icalModel.go @@ -0,0 +1,82 @@ +//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 . + +package model + +import ( + "encoding/json" + "fmt" + "strings" + "time" +) + +// IcalModel local type for EmitICal function +type IcalModel struct { + Events Events + Mapping map[string]FeedCollection +} + +// FeedModel is an iCal feed +type FeedModel struct { + Content string + ExpiresAt JSONTime + Semester string + Course string +} + +// Entry is a time entry +type Entry struct { + DateStart JSONTime `json:"dateStart"` + DateEnd JSONTime `json:"dateEnd"` + Description string `json:"description"` +} + +// Entries is a collection of entries +type Entries []*Entry + +type FeedModule struct { + UUID string `db:"uuid" json:"uuid"` + Name string `db:"Name" json:"name"` + Course string `db:"course" json:"course"` + UserDefinedName string `db:"userDefinedName" json:"userDefinedName"` + Reminder bool `db:"reminder" json:"reminder"` +} + +type FeedRecord struct { + Modules []FeedModule `db:"modules" json:"modules"` + Retrieved JSONTime `db:"retrieved" json:"retrieved"` + BaseModel +} + +type JSONTime time.Time + +// MarshalJSON Implement Marshaler and Unmarshaler interface +func (j JSONTime) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Time(j)) +} + +func (jt *JSONTime) UnmarshalJSON(b []byte) error { + timeString := strings.Trim(string(b), `"`) + if timeString == "null" || timeString == "" { + return nil + } + t, err := time.Parse("2006-01-02 15:04:05.000Z", timeString) + if err == nil { + *jt = JSONTime(t) + return nil + } + return fmt.Errorf("error parsing time string %s: %w", timeString, err) +} diff --git a/services/ical/model/moduleModel.go b/services/ical/model/moduleModel.go new file mode 100644 index 0000000..eddb839 --- /dev/null +++ b/services/ical/model/moduleModel.go @@ -0,0 +1,12 @@ +package model + +type Module struct { + UUID string `db:"uuid" json:"uuid"` + Name string `db:"Name" json:"name"` + Course string `db:"course" json:"course"` + Prof string `db:"prof" json:"prof"` + Semester string `db:"semester" json:"semester"` + Events Events `json:"events"` +} + +type Modules []Module diff --git a/services/ical/service/connector/feedConnector.go b/services/ical/service/connector/feedConnector.go new file mode 100644 index 0000000..7c98663 --- /dev/null +++ b/services/ical/service/connector/feedConnector.go @@ -0,0 +1,101 @@ +package connector + +import ( + "encoding/json" + "errors" + "htwkalender-ical/model" + "log/slog" +) + +func GetFeedByToken(token string) (model.FeedRecord, error) { + var feed model.FeedRecord + + // /api/collections/feeds/records/{id} + + response, err := RequestApi("/api/collections/feeds/records/" + token) + if err != nil { + return model.FeedRecord{}, err + } + + // parse the response body json to FeedRecord struct + feed, err = parseResponse(response.Body()) + if err != nil { + slog.Error("Failed to read response body", "error", err) + return model.FeedRecord{}, err + } + + return feed, nil + +} + +func parseResponse(response []byte) (model.FeedRecord, error) { + var feedRecord model.FeedRecord + + err := json.Unmarshal(response, &feedRecord) + if err != nil { + return model.FeedRecord{}, err + } + + return feedRecord, nil +} + +func DeleteFeedRecord(token string) error { + err := DeleteRequestApi("/api/feed?token=" + token) + if err != nil { + return err + } + + return nil +} + +func GetModuleWithEvents(module model.FeedModule) (model.Module, error) { + var modules model.Module + + // /api/module?uuid= + + response, err := RequestApi("/api/module?uuid=" + module.UUID) + if err != nil { + return model.Module{}, err + } + + // parse the response body json to Events struct + modules, err = parseModuleResponse(response.Body()) + if err != nil { + slog.Error("Failed to read response body", "error", err) + return model.Module{}, err + } + + return modules, nil +} + +func parseModuleResponse(body []byte) (model.Module, error) { + var module model.Module + + err := json.Unmarshal(body, &module) + if err != nil { + return model.Module{}, err + } + + return module, nil +} + +func SaveFeedRecord(modules []model.FeedCollection) (string, error) { + var token string + + response, err := PostRequestApi("/api/feed", modules) + if err != nil { + return "", err + } + + if response.StatusCode() != 200 { + return "", errors.New("failed to save feed") + } + + // parse the response body json to string + err = json.Unmarshal(response.Body(), &token) + if err != nil { + return "", err + } + + return token, nil +} diff --git a/services/ical/service/connector/restHandler.go b/services/ical/service/connector/restHandler.go new file mode 100644 index 0000000..7fc3381 --- /dev/null +++ b/services/ical/service/connector/restHandler.go @@ -0,0 +1,59 @@ +package connector + +import ( + "github.com/gofiber/fiber/v3/client" + "htwkalender-ical/model" + "time" +) + +const host = "http://htwkalender-data-manager:8090" + +func RequestApi(path string) (*client.Response, error) { + + cc := client.New() + cc.SetTimeout(5 * time.Second) + + // set retry to 0 + response, err := cc.Get(host + path) + if err != nil { + return nil, err + } + + return response, nil +} + +func DeleteRequestApi(path string) error { + + cc := client.New() + cc.SetTimeout(5 * time.Second) + + // set retry to 0 + _, err := cc.Delete(host + path) + if err != nil { + return err + } + + return nil +} + +func PostRequestApi(path string, body []model.FeedCollection) (*client.Response, error) { + + cc := client.New() + cc.SetTimeout(5 * time.Second) + + config := client.Config{ + Body: body, + Header: map[string]string{ + "Content-Type": "application/json", + "Accept": "*/*", + }, + } + + // set retry to 0 + response, err := cc.Post(host+path, config) + if err != nil { + return nil, err + } + + return response, nil +} diff --git a/services/ical/service/functions/semester.go b/services/ical/service/functions/semester.go new file mode 100644 index 0000000..9cf7006 --- /dev/null +++ b/services/ical/service/functions/semester.go @@ -0,0 +1,47 @@ +//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 . + +package functions + +import ( + localTime "htwkalender-ical/service/functions/time" + "time" +) + +// GetCurrentSemesterString returns the current semester as string +// if current month is between 10 and 03 -> winter semester "ws" +func GetCurrentSemesterString(localeTime localTime.Clock) string { + if localeTime.Now().Month() >= 10 || localeTime.Now().Month() <= 3 { + return "ws" + } else { + return "ss" + } +} + +func CalculateSemesterList(clock localTime.Clock) []string { + summerSemester := clock.Now().Month() >= time.March && clock.Now().Month() <= time.September + winterSemester := clock.Now().Month() <= time.March || clock.Now().Month() >= time.September + + if summerSemester && !winterSemester { + return []string{"ss"} + } + + if !summerSemester && winterSemester { + return []string{"ws"} + } + + return []string{"ss", "ws"} +} diff --git a/services/ical/service/functions/semester_test.go b/services/ical/service/functions/semester_test.go new file mode 100644 index 0000000..5a5f96c --- /dev/null +++ b/services/ical/service/functions/semester_test.go @@ -0,0 +1,91 @@ +package functions + +import ( + mockTime "htwkalender-ical/service/functions/time" + "reflect" + "testing" + "time" +) + +func Test_calculateSemesterList(t *testing.T) { + type args struct { + clock mockTime.Clock + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "is summer semester", + args: args{ + clock: mockTime.MockClock{ + NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: []string{"ss"}, + }, + { + name: "is winter semester", + args: args{ + clock: mockTime.MockClock{ + NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: []string{"ws"}, + }, + { + name: "is in both", + args: args{ + clock: mockTime.MockClock{ + NowTime: time.Date(2024, 3, 22, 0, 0, 0, 0, time.UTC), + }, + }, + want: []string{"ss", "ws"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CalculateSemesterList(tt.args.clock); !reflect.DeepEqual(got, tt.want) { + t.Errorf("calculateSemesterList() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetCurrentSemesterString(t *testing.T) { + type args struct { + localeTime mockTime.Clock + } + tests := []struct { + name string + args args + want string + }{ + { + name: "is winter semester", + args: args{ + localeTime: mockTime.MockClock{ + NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: "ws", + }, + { + name: "is summer semester", + args: args{ + localeTime: mockTime.MockClock{ + NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: "ss", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetCurrentSemesterString(tt.args.localeTime); got != tt.want { + t.Errorf("GetCurrentSemesterString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/services/ical/service/functions/string.go b/services/ical/service/functions/string.go new file mode 100644 index 0000000..77984a4 --- /dev/null +++ b/services/ical/service/functions/string.go @@ -0,0 +1,62 @@ +//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 . + +package functions + +import ( + "crypto/sha256" + "encoding/hex" + "strings" +) + +// check if string is empty or contains only whitespaces +func OnlyWhitespace(word string) bool { + return len(strings.TrimSpace(word)) == 0 +} + +// return function to check if rune is a separator +func IsSeparator(separator []rune) func(rune) bool { + return func(character rune) bool { + for _, sep := range separator { + if sep == character { + return true + } + } + return false + } +} + +func Contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func HashString(s string) string { + hash := sha256.New() + hash.Write([]byte(s)) + hashInBytes := hash.Sum(nil) + return hex.EncodeToString(hashInBytes) +} + +func SeperateRoomString(rooms string) []string { + return strings.FieldsFunc(rooms, IsSeparator( + []rune{',', '\t', '\n', '\r', ';', ' ', '\u00A0'}), + ) +} diff --git a/services/ical/service/functions/string_test.go b/services/ical/service/functions/string_test.go new file mode 100644 index 0000000..ee8316a --- /dev/null +++ b/services/ical/service/functions/string_test.go @@ -0,0 +1,145 @@ +//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 . + +package functions + +import ( + "reflect" + "testing" +) + +func TestOnlyWhitespace(t *testing.T) { + type args struct { + word string + } + tests := []struct { + name string + args args + want bool + }{ + {"empty string", args{""}, true}, + {"whitespace", args{" "}, true}, + {"whitespaces", args{" "}, true}, + {"whitespaces and tabs", args{" \t"}, true}, + {"whitespaces and tabs and newlines", args{" \t\n"}, true}, + {"whitespaces and tabs and newlines and non-breaking spaces", args{" \t\n\u00A0"}, true}, + {"non-whitespace", args{"a"}, false}, + {"non-whitespaces", args{"abc"}, false}, + {"non-whitespaces and tabs", args{"abc\t"}, false}, + {"non-whitespaces and tabs and newlines", args{"abc\t\n"}, false}, + {"non-whitespaces and tabs and newlines and non-breaking spaces", args{"abc\t\n\u00A0"}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := OnlyWhitespace(tt.args.word); got != tt.want { + t.Errorf("OnlyWhitespace() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHashString(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + {"empty string", args{""}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"non-empty string", args{"abc"}, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := HashString(tt.args.s); got != tt.want { + t.Errorf("HashString() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsSeparator(t *testing.T) { + type args struct { + separator []rune + character rune + } + tests := []struct { + name string + args args + want bool + }{ + {"empty separator", args{[]rune{}, 'a'}, false}, + {"separator with one rune equal", args{[]rune{'a'}, 'a'}, true}, + {"separator with one rune different", args{[]rune{'a'}, 'b'}, false}, + {"separator with two runes equal", args{[]rune{'a', 'b'}, 'a'}, true}, + {"separator with two runes equal second", args{[]rune{'a', 'b'}, 'b'}, true}, + {"separator with two runes different", args{[]rune{'a', 'b'}, 'c'}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsSeparator(tt.args.separator)(tt.args.character); got != tt.want { + t.Errorf("IsSeparator()() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestContains(t *testing.T) { + type args struct { + s []string + e string + } + tests := []struct { + name string + args args + want bool + }{ + {"empty slice", args{[]string{}, "a"}, false}, + {"slice with one element equal", args{[]string{"a"}, "a"}, true}, + {"slice with one element different", args{[]string{"a"}, "b"}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Contains(tt.args.s, tt.args.e); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSeperateRoomString(t *testing.T) { + type args struct { + rooms string + } + tests := []struct { + name string + args args + want []string + }{ + {"empty string", args{""}, []string{}}, + {"one room", args{"a"}, []string{"a"}}, + {"two rooms", args{"a,b"}, []string{"a", "b"}}, + {"two rooms with whitespace", args{"a, b"}, []string{"a", "b"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SeperateRoomString(tt.args.rooms); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SeperateRoomString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/services/ical/service/functions/time/mockClock.go b/services/ical/service/functions/time/mockClock.go new file mode 100644 index 0000000..cfac7e4 --- /dev/null +++ b/services/ical/service/functions/time/mockClock.go @@ -0,0 +1,26 @@ +//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 . + +package time + +import "time" + +type MockClock struct { + NowTime time.Time +} + +func (m MockClock) Now() time.Time { return m.NowTime } +func (MockClock) After(d time.Duration) <-chan time.Time { return time.After(d) } diff --git a/services/ical/service/functions/time/parse.go b/services/ical/service/functions/time/parse.go new file mode 100644 index 0000000..97a4f22 --- /dev/null +++ b/services/ical/service/functions/time/parse.go @@ -0,0 +1,27 @@ +//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 . + +package time + +import ( + "htwkalender-ical/model" + "time" +) + +func ParseAsTypesDatetime(time time.Time) model.JSONTime { + dateTime := model.JSONTime(time) + return dateTime +} diff --git a/services/ical/service/functions/time/realClock.go b/services/ical/service/functions/time/realClock.go new file mode 100644 index 0000000..cb98d1f --- /dev/null +++ b/services/ical/service/functions/time/realClock.go @@ -0,0 +1,24 @@ +//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 . + +package time + +import "time" + +type RealClock struct{} + +func (RealClock) Now() time.Time { return time.Now() } +func (RealClock) After(d time.Duration) <-chan time.Time { return time.After(d) } diff --git a/services/ical/service/functions/time/time.go b/services/ical/service/functions/time/time.go new file mode 100644 index 0000000..ba239aa --- /dev/null +++ b/services/ical/service/functions/time/time.go @@ -0,0 +1,24 @@ +//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 . + +package time + +import "time" + +type Clock interface { + Now() time.Time + After(d time.Duration) <-chan time.Time +} diff --git a/services/ical/service/ical/ical.go b/services/ical/service/ical/ical.go new file mode 100644 index 0000000..05ae05d --- /dev/null +++ b/services/ical/service/ical/ical.go @@ -0,0 +1,76 @@ +package ical + +import ( + "bytes" + "github.com/jordic/goics" + "htwkalender-ical/model" + "htwkalender-ical/service/connector" + "log/slog" + "time" +) + +const expirationTime = 5 * time.Minute + +func Feed(token string) (string, error) { + // get feed by token + feed, err := connector.GetFeedByToken(token) + if err != nil { + return "", err + } + + var events model.Events + + for _, module := range feed.Modules { + moduleWithEvents, err := connector.GetModuleWithEvents(module) + if err != nil { + return "", err + } + + events = append(events, moduleWithEvents.Events...) + } + + // Sorte events by start date + events.Sort() + + modules := make(map[string]model.FeedCollection) + for _, module := range feed.Modules { + modules[module.UUID] = model.FeedCollection(module) + } + + b := bytes.Buffer{} + goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules}) + icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))} + + return icalFeed.Content, nil +} + +func FeedRecord(token string) (model.FeedRecord, error) { + + feedRecord, err := connector.GetFeedByToken(token) + if err != nil { + return model.FeedRecord{}, err + } + + return feedRecord, nil + +} + +func DeleteFeedRecord(token string) error { + err := connector.DeleteFeedRecord(token) + if err != nil { + return err + } + return nil +} + +func CreateFeed(modules []model.FeedCollection) (string, error) { + + // Save feed + token, err := connector.SaveFeedRecord(modules) + if err != nil { + slog.Error("Failed to save feed", "error", err) + return "", err + } + + return token, nil +} diff --git a/services/ical/service/ical/icalFileGeneration.go b/services/ical/service/ical/icalFileGeneration.go new file mode 100644 index 0000000..57eb4a3 --- /dev/null +++ b/services/ical/service/ical/icalFileGeneration.go @@ -0,0 +1,173 @@ +//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 . + +package ical + +import ( + "htwkalender-ical/model" + "htwkalender-ical/service/functions" + clock "htwkalender-ical/service/functions/time" + "htwkalender-ical/service/names" + "time" + + "github.com/jordic/goics" + _ "time/tzdata" +) + +// IcalModel local type for EmitICal function +type IcalModel struct { + Events model.Events + Mapping map[string]model.FeedCollection +} + +// EmitICal implements the interface for goics +func (icalModel IcalModel) EmitICal() goics.Componenter { + internalClock := clock.RealClock{} + c := generateIcalEmit(icalModel, internalClock) + return c +} + +func generateIcalEmit(icalModel IcalModel, internalClock clock.Clock) *goics.Component { + europeTime, _ := time.LoadLocation("Europe/Berlin") + c := goics.NewComponent() + c.SetType("VCALENDAR") + // PRODID is required by the standard + c.AddProperty("PRODID", "-//HTWK Kalender//htwkalender.de//DE") + + c.AddProperty("VERSION", "2.0") + c.AddProperty("CALSCALE", "GREGORIAN") + c.AddProperty("TZID", "EUROPE/BERLIN") + c.AddProperty("X-WR-CALNAME", "HTWK Kalender") + c.AddProperty("X-WR-TIMEZONE", "EUROPE/BERLIN") + //add v time zone + icalModel.vtimezone(c) + + for _, event := range icalModel.Events { + mapEntry, mappingFound := icalModel.Mapping[event.UUID] + + s := goics.NewComponent() + s.SetType("VEVENT") + + s.AddProperty(goics.FormatDateTime("DTSTAMP", internalClock.Now().Local().In(europeTime))) + + // create a unique id for the event by hashing the event start, end, course and name + var eventHash = functions.HashString(time.Time(event.Start).String() + time.Time(event.End).String() + event.Course + event.Name + event.Rooms) + + s.AddProperty("UID", eventHash+"@htwkalender.de") + s.AddProperty(goics.FormatDateTime("DTEND", time.Time(event.End).Local().In(europeTime))) + s.AddProperty(goics.FormatDateTime("DTSTART", time.Time(event.Start).Local().In(europeTime))) + + if mappingFound { + addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry)) + addAlarmIfSpecified(s, event, mapEntry, internalClock) + } else { + addPropertyIfNotEmpty(s, "SUMMARY", event.Name) + } + + addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event)) + addPropertyIfNotEmpty(s, "LOCATION", event.Rooms) + c.AddComponent(s) + } + return c +} + +func (icalModel IcalModel) vtimezone(c *goics.Component) { + tz := goics.NewComponent() + tz.SetType("VTIMEZONE") + tz.AddProperty("TZID", "EUROPE/BERLIN") + //add standard time + icalModel.standard(tz) + //add daylight time + icalModel.daylight(tz) + + c.AddComponent(tz) +} + +func (icalModel IcalModel) standard(tz *goics.Component) { + st := NewHtwkalenderComponent() + st.SetType("STANDARD") + st.AddProperty("DTSTART", "19701025T030000") + st.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU") + st.AddProperty("TZOFFSETFROM", "+0200") + st.AddProperty("TZOFFSETTO", "+0100") + st.AddProperty("TZNAME", "CET") + tz.AddComponent(st) +} + +// create an override for goics component function Write +// to add the RRULE property + +func (icalModel IcalModel) daylight(tz *goics.Component) { + dt := NewHtwkalenderComponent() + dt.SetType("DAYLIGHT") + dt.AddProperty("DTSTART", "19700329T020000") + dt.AddProperty("TZOFFSETFROM", "+0100") + dt.AddProperty("TZOFFSETTO", "+0200") + dt.AddProperty("TZNAME", "CEST") + dt.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU") + tz.AddComponent(dt) +} + +// if reminder is specified in the configuration for this event, an alarm will be added to the event +func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection, clock clock.Clock) { + // if event.Start > now + // then add alarm + if time.Time(event.Start).Local().After(clock.Now().Local()) && mapping.Reminder { + a := goics.NewComponent() + a.SetType("VALARM") + a.AddProperty("TRIGGER", "-P0DT0H15M0S") + a.AddProperty("ACTION", "DISPLAY") + a.AddProperty("DESCRIPTION", "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms) + s.AddComponent(a) + } +} + +// replaceNameIfUserDefined replaces the name of the event with the user defined name if it is not empty +// all contained template strings will be replaced with the corresponding values from the event +func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string { + if !functions.OnlyWhitespace(mapping.UserDefinedName) { + return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event) + } + + return event.Name +} + +// AddPropertyIfNotEmpty adds a property to the component if the value is not empty +// or contains only whitespaces +func addPropertyIfNotEmpty(component *goics.Component, key string, value string) { + if !functions.OnlyWhitespace(value) { + component.AddProperty(key, value) + } +} + +func generateDescription(event model.Event) string { + var description string + + if !functions.OnlyWhitespace(event.Prof) { + description += "Profs: " + event.Prof + "\n" + } + if !functions.OnlyWhitespace(event.Course) { + description += "Gruppen: " + event.Course + "\n" + } + if !functions.OnlyWhitespace(event.EventType) { + description += "Typ: " + event.EventType + event.Compulsory + "\n" + } + if !functions.OnlyWhitespace(event.Notes) { + description += "Notizen: " + event.Notes + "\n" + } + + return description +} diff --git a/services/ical/service/ical/icalFileGeneration_test.go b/services/ical/service/ical/icalFileGeneration_test.go new file mode 100644 index 0000000..c1a92f0 --- /dev/null +++ b/services/ical/service/ical/icalFileGeneration_test.go @@ -0,0 +1,258 @@ +//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 . + +package ical + +import ( + "github.com/jordic/goics" + "htwkalender-ical/model" + mockTime "htwkalender-ical/service/functions/time" + "reflect" + "testing" + "time" +) + +func TestIcalModel_EmitICal(t *testing.T) { + type fields struct { + Events model.Events + Mapping map[string]model.FeedCollection + } + tests := []struct { + name string + fields fields + want *goics.Component + }{ + { + name: "Test EmitICal", + fields: fields{ + Events: model.Events{ + { + UUID: "123", + Name: "Test", + EventType: "Test", + Notes: "Test", + Prof: "Test", + Rooms: "Test", + BookedAt: "Test", + }, + }, + Mapping: map[string]model.FeedCollection{ + "123": { + UUID: "123", + Name: "Test", + Course: "Test", + UserDefinedName: "Test", + }, + }, + }, + want: &goics.Component{ + Tipo: "VCALENDAR", + Elements: []goics.Componenter{ + &goics.Component{ + Tipo: "VTIMEZONE", + Elements: []goics.Componenter{ + &HtwkalenderComponent{ + Component: &goics.Component{ + Tipo: "STANDARD", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTART": {"19701025T030000"}, + "RRULE": {"FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU"}, + "TZOFFSETFROM": {"+0200"}, + "TZOFFSETTO": {"+0100"}, + "TZNAME": {"CET"}, + }, + }, + }, + &HtwkalenderComponent{ + Component: &goics.Component{ + Tipo: "DAYLIGHT", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTART": {"19700329T020000"}, + "TZOFFSETFROM": {"+0100"}, + "TZOFFSETTO": {"+0200"}, + "TZNAME": {"CEST"}, + "RRULE": {"FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU"}, + }, + }, + }, + }, + Properties: map[string][]string{ + "TZID": {"EUROPE/BERLIN"}, + }, + }, + &goics.Component{ + Tipo: "VEVENT", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTAMP": {"20231201T000000Z"}, + "UID": {"5166fc0abd9d7750077261f1e26a26168d32c88af77198fe83af63e1ba6310dc@htwkalender.de"}, + "DTEND": {"00010101T000000Z"}, + "DTSTART": {"00010101T000000Z"}, + "SUMMARY": {"Test"}, + "DESCRIPTION": {"Profs: Test\nTyp: Test\nNotizen: Test\n"}, + "LOCATION": {"Test"}, + }, + }, + }, + Properties: map[string][]string{ + "PRODID": {"-//HTWK Kalender//htwkalender.de//DE"}, + "VERSION": {"2.0"}, + "CALSCALE": {"GREGORIAN"}, + "TZID": {"EUROPE/BERLIN"}, + "X-WR-CALNAME": {"HTWK Kalender"}, + "X-WR-TIMEZONE": {"EUROPE/BERLIN"}, + }, + }, + }, + { + name: "Test Similar Events like Sport Courses", + fields: fields{ + Events: model.Events{ + { + UUID: "123", + Name: "Test", + Course: "Test", + EventType: "Test", + Notes: "Test", + Prof: "Test", + Rooms: "ZU430", + BookedAt: "Test", + Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), + End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 1, 0, 0, 0, time.UTC)), + }, + { + UUID: "123", + Name: "Test", + Course: "Test", + EventType: "Test", + Notes: "Test", + Prof: "Test", + Rooms: "ZU221", + BookedAt: "Test", + Start: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC)), + End: mockTime.ParseAsTypesDatetime(time.Date(2023, 12, 1, 1, 0, 0, 0, time.UTC)), + }, + }, + Mapping: map[string]model.FeedCollection{ + "123": { + UUID: "123", + Name: "Test", + Course: "Test", + UserDefinedName: "UserDefinedName", + }, + }, + }, + want: &goics.Component{ + Tipo: "VCALENDAR", + Elements: []goics.Componenter{ + &goics.Component{ + Tipo: "VTIMEZONE", + Elements: []goics.Componenter{ + &HtwkalenderComponent{ + Component: &goics.Component{ + Tipo: "STANDARD", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTART": {"19701025T030000"}, + "RRULE": {"FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU"}, + "TZOFFSETFROM": {"+0200"}, + "TZOFFSETTO": {"+0100"}, + "TZNAME": {"CET"}, + }, + }, + }, + &HtwkalenderComponent{ + Component: &goics.Component{ + Tipo: "DAYLIGHT", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTART": {"19700329T020000"}, + "TZOFFSETFROM": {"+0100"}, + "TZOFFSETTO": {"+0200"}, + "TZNAME": {"CEST"}, + "RRULE": {"FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU"}, + }, + }, + }, + }, + Properties: map[string][]string{ + "TZID": {"EUROPE/BERLIN"}, + }, + }, + &goics.Component{ + Tipo: "VEVENT", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTAMP": {"20231201T000000Z"}, + "UID": {"2463aac347bca19130d8e579b4b6d89a32c88f7c7e7f858e56477d94b71543a7@htwkalender.de"}, + "DTEND": {"20231201T010000Z"}, + "DTSTART": {"20231201T000000Z"}, + "SUMMARY": {"UserDefinedName"}, + "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, + "LOCATION": {"ZU430"}, + }, + }, + &goics.Component{ + Tipo: "VEVENT", + Elements: []goics.Componenter{}, + Properties: map[string][]string{ + "DTSTAMP": {"20231201T000000Z"}, + "UID": {"ea42fc31835128735636b235be552af559fae5329fe7e501f529130e11a7f3a1@htwkalender.de"}, + "DTEND": {"20231201T010000Z"}, + "DTSTART": {"20231201T000000Z"}, + "SUMMARY": {"UserDefinedName"}, + "DESCRIPTION": {"Profs: Test\nGruppen: Test\nTyp: Test\nNotizen: Test\n"}, + "LOCATION": {"ZU221"}, + }, + }, + }, + Properties: map[string][]string{ + "PRODID": {"-//HTWK Kalender//htwkalender.de//DE"}, + "VERSION": {"2.0"}, + "CALSCALE": {"GREGORIAN"}, + "TZID": {"EUROPE/BERLIN"}, + "X-WR-CALNAME": {"HTWK Kalender"}, + "X-WR-TIMEZONE": {"EUROPE/BERLIN"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + icalModel := IcalModel{ + Events: tt.fields.Events, + Mapping: tt.fields.Mapping, + } + + mockClock := mockTime.MockClock{ + NowTime: time.Date(2023, 12, 1, 0, 0, 0, 0, time.UTC), + } + + if got := generateIcalEmit(icalModel, mockClock); !reflect.DeepEqual(got, tt.want) { + t.Errorf("EmitICal() = \n%v, want \n%v", got, tt.want) + + // Print the differences + for i, element := range got.Elements { + if !reflect.DeepEqual(element, tt.want.Elements[i]) { + t.Errorf("Element %d: got \n%v, want \n%v", i, element, tt.want.Elements[i]) + } + } + } + }) + } +} diff --git a/services/ical/service/ical/icsComponenter.go b/services/ical/service/ical/icsComponenter.go new file mode 100644 index 0000000..b8c3deb --- /dev/null +++ b/services/ical/service/ical/icsComponenter.go @@ -0,0 +1,62 @@ +//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 . + +package ical + +import ( + "github.com/jordic/goics" + "sort" + "strings" +) + +type HtwkalenderComponent struct { + *goics.Component +} + +func NewHtwkalenderComponent() *HtwkalenderComponent { + return &HtwkalenderComponent{ + Component: goics.NewComponent(), + } +} + +// Writes the component to the Writer +func (c *HtwkalenderComponent) Write(w *goics.ICalEncode) { + w.WriteLine("BEGIN:" + c.Tipo + goics.CRLF) + + // Iterate over component properties + var keys []string + for k := range c.Properties { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + vals := c.Properties[key] + for _, val := range vals { + w.WriteLine(WriteStringField(key, val)) + } + } + + for _, xc := range c.Elements { + xc.Write(w) + } + + w.WriteLine("END:" + c.Tipo + goics.CRLF) +} + +// WriteStringField UID:asdfasdfаs@dfasdf.com +func WriteStringField(key string, val string) string { + return strings.ToUpper(key) + ":" + (val) + goics.CRLF +} diff --git a/services/ical/service/names/userDefinedNameTemplates.go b/services/ical/service/names/userDefinedNameTemplates.go new file mode 100644 index 0000000..59250ff --- /dev/null +++ b/services/ical/service/names/userDefinedNameTemplates.go @@ -0,0 +1,39 @@ +//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 . + +package names + +import ( + "htwkalender-ical/model" + "regexp" +) + +func ReplaceTemplateSubStrings(rawString string, event model.Event) string { + re := regexp.MustCompile(`\%(.)`) + + return re.ReplaceAllStringFunc(rawString, func(match string) string { + switch match { + case "%%": + return "%" + case "%t": + return event.EventType + case "%p": + return event.Compulsory + default: + return match + } + }) +} diff --git a/services/ical/service/names/userDefinedNameTemplates_test.go b/services/ical/service/names/userDefinedNameTemplates_test.go new file mode 100644 index 0000000..bb5ee2b --- /dev/null +++ b/services/ical/service/names/userDefinedNameTemplates_test.go @@ -0,0 +1,94 @@ +//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 . + +package names + +import ( + "htwkalender-ical/model" + "testing" +) + +func TestReplaceTemplateSubStrings(t *testing.T) { + type args struct { + rawString string + event model.Event + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Test 1", + args: args{ + rawString: "%t", + event: model.Event{ + EventType: "Test", + }, + }, + want: "Test", + }, + { + name: "Test 2", + args: args{ + rawString: "%p", + event: model.Event{ + Compulsory: "Test", + }, + }, + want: "Test", + }, + { + name: "Test 3", + args: args{ + rawString: "%%", + event: model.Event{ + EventType: "Test", + }, + }, + want: "%", + }, + { + name: "Test 4", + args: args{ + rawString: "%t %p", + event: model.Event{ + EventType: "Test", + Compulsory: "Test", + }, + }, + want: "Test Test", + }, + { + name: "Test 5", + args: args{ + rawString: "%t %p %%", + event: model.Event{ + EventType: "Test", + Compulsory: "Test", + }, + }, + want: "Test Test %", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ReplaceTemplateSubStrings(tt.args.rawString, tt.args.event); got != tt.want { + t.Errorf("ReplaceTemplateSubStrings() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/services/ical/service/routes.go b/services/ical/service/routes.go new file mode 100644 index 0000000..ee59f0f --- /dev/null +++ b/services/ical/service/routes.go @@ -0,0 +1,108 @@ +package service + +import ( + "encoding/json" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" + "htwkalender-ical/model" + "htwkalender-ical/service/ical" + "log/slog" + "net/http" +) + +// AddFeedRoutes add routes to the app instance for the data-manager ical service +// with golang fiber +func AddFeedRoutes(app *fiber.App) { + + // Define a route for the GET method on the root path '/' + app.Get("/api/feed", func(c fiber.Ctx) error { + + token := c.Query("token") + results, err := ical.Feed(token) + + if err != nil { + slog.Error("Failed to get feed", "error", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + c.Response().Header.Set("Content-type", "text/calendar") + c.Response().Header.Set("charset", "utf-8") + c.Response().Header.Set("Content-Disposition", "inline") + c.Response().Header.Set("filename", "calendar.ics") + + return c.SendString(results) + }) + + // Define a route for the POST method on the root path '/api/feed' + app.Post("/api/feed", func(c fiber.Ctx) error { + var modules []model.FeedCollection + //obtain the body of the request + err := json.Unmarshal(c.Body(), &modules) + if err != nil { + log.Error("Failed to unmarshal request body", "error", err) + return c.SendStatus(fiber.StatusBadRequest) + } + + //create a new feed + token, err := ical.CreateFeed(modules) + if err != nil { + println(err) + log.Error("Failed to create feed", "error", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + + return c.JSON(token) + }) + + // Define a route for the GET method on the root path '/' + app.Get("/api/collections/feeds/records/:token", func(c fiber.Ctx) error { + + token := c.Params("token") + results, err := ical.FeedRecord(token) + + if err != nil { + slog.Error("Failed to get feed", "error", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + c.Response().Header.Set("Content-type", "application/json; charset=UTF-8") + + return c.JSON(results) + }) + + app.Delete("/api/feed", func(c fiber.Ctx) error { + + token := c.Query("token") + err := ical.DeleteFeedRecord(token) + if err != nil { + slog.Error("Feed could not be deleted", "error", err) + return c.JSON(http.StatusNotFound, "Feed could not be deleted") + } else { + return c.JSON(http.StatusOK, "Feed deleted") + } + }) + + app.Put("/api/feed", func(c fiber.Ctx) error { + token := c.Query("token") + return c.JSON(http.StatusOK, "token: "+token) + }) + + app.Head("/api/feed", func(c fiber.Ctx) error { + return c.JSON(http.StatusOK, "") + }) + + app.Add([]string{"PROPFIND"}, "/api/feed", func(c fiber.Ctx) error { + return c.JSON(http.StatusOK, "") + }) + + // Route for Thunderbird to get calendar server information + // Response is a 200 OK without additional content + app.Add([]string{"PROPFIND"}, "/.well-known/caldav", func(c fiber.Ctx) error { + return c.JSON(http.StatusOK, "") + }) + + // Route for Thunderbird to get calendar server information + // Response is a 200 OK without additional content + app.Add([]string{"PROPFIND"}, "/", func(c fiber.Ctx) error { + return c.JSON(http.StatusOK, "") + }) + +}