diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 49ca5e0..c5edfb3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,250 +13,234 @@
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see .
-
stages:
- - lint
- - build
- - test
- - sonarqube-check
- - oci-build
- - deploy
- - deploy-dev # New stage for development deployment
-
+- lint
+- build
+- test
+- sonarqube-check
+- oci-build
+- deploy
+- deploy-dev
lint-frontend:
image: node:lts
stage: lint
rules:
- - changes:
- - frontend/**/*
+ - changes:
+ - frontend/**/*
script:
- - cd frontend
- - npm i
- - npm run lint-no-fix
-
+ - cd frontend
+ - npm i
+ - npm run lint-no-fix
lint-data-manager:
stage: lint
image: golangci/golangci-lint:latest
rules:
- - changes:
- - services/data-manager/**/*
+ - changes:
+ - services/data-manager/**/*
script:
- - cd services/data-manager
- - go mod download
- - golangci-lint --version
- - golangci-lint run -v --skip-dirs=migrations --timeout=5m
-
+ - 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/**/*
+ - changes:
+ - services/ical/**/*
script:
- - cd services/ical
- - go mod download
- - golangci-lint --version
- - golangci-lint run -v --skip-dirs=migrations --timeout=5m
-
-
+ - cd services/ical
+ - go mod download
+ - golangci-lint --version
+ - golangci-lint run -v --skip-dirs=migrations --timeout=5m
build-data-manager:
image: golang:alpine
stage: build
rules:
- - changes:
- - services/data-manager/**/*
+ - changes:
+ - services/data-manager/**/*
script:
- - cd services/data-manager
- - go build -o htwkalender
+ - cd services/data-manager
+ - go build -o htwkalender
artifacts:
paths:
- - data-manager/htwkalender
- - data-manager/go.sum
- - data-manager/go.mod
-
+ - data-manager/htwkalender
+ - data-manager/go.sum
+ - data-manager/go.mod
build-ical:
image: golang:alpine
stage: build
rules:
- - changes:
- - services/ical/**/*
+ - changes:
+ - services/ical/**/*
script:
- - cd services/ical
- - go build -o htwkalender-ical
+ - cd services/ical
+ - go build -o htwkalender-ical
artifacts:
paths:
- - data-manager/htwkalender-ical
- - data-manager/go.sum
- - data-manager/go.mod
-
+ - data-manager/htwkalender-ical
+ - data-manager/go.sum
+ - data-manager/go.mod
sonarqube-data-manager:
stage: sonarqube-check
image:
name: sonarsource/sonar-scanner-cli:5.0
- entrypoint: [""]
+ entrypoint:
+ - ''
variables:
- SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
- GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
+ SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
+ GIT_DEPTH: '0'
cache:
key: "${CI_JOB_NAME}"
paths:
- - .sonar/cache
+ - ".sonar/cache"
script:
- - cd services/data-manager
- - sonar-scanner
+ - cd services/data-manager
+ - sonar-scanner
allow_failure: true
only:
- - merge_requests
- - master
- - main
- - develop
-
+ - merge_requests
+ - master
+ - main
+ - develop
build-frontend:
- image: node:lts
- stage: build
- rules:
- - changes:
- - frontend/**/*
- script:
- - cd frontend
- - npm i
- - npm run build
- artifacts:
- paths:
- - frontend/build
-
+ image: node:lts
+ stage: build
+ rules:
+ - changes:
+ - frontend/**/*
+ script:
+ - cd frontend
+ - npm i
+ - npm run build
+ artifacts:
+ paths:
+ - frontend/build
test-data-manager:
image: golang:alpine
stage: test
rules:
- - changes:
- - services/data-manager/**/*
+ - changes:
+ - services/data-manager/**/*
script:
- - cd services/data-manager
- - go test -v ./...
+ - cd services/data-manager
+ - go test -v ./...
dependencies:
- - build-data-manager
-
+ - build-data-manager
test-ical:
image: golang:alpine
stage: test
rules:
- - changes:
- - services/ical/**/*
+ - changes:
+ - services/ical/**/*
script:
- - cd services/ical
- - go test -v ./...
+ - cd services/ical
+ - go test -v ./...
dependencies:
- - build-ical
-
+ - build-ical
test-frontend:
image: node:lts
stage: test
rules:
- - changes:
- - frontend/**/*
+ - changes:
+ - frontend/**/*
script:
- - cd frontend
- - npm i
- - npm run test
+ - cd frontend
+ - npm i
+ - npm run test
dependencies:
- - lint-frontend
-
+ - lint-frontend
build-data-manager-image:
stage: oci-build
image: docker:latest
services:
- - docker:dind
+ - docker:dind
tags:
- - image
+ - image
variables:
- IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-data-manager
+ IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-data-manager"
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
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- - docker build --pull -t $IMAGE_TAG -f ./services/data-manager/Dockerfile --target prod ./services
- - docker push $IMAGE_TAG
+ - docker build --pull -t $IMAGE_TAG -f ./services/data-manager/Dockerfile --target
+ prod ./services
+ - docker push $IMAGE_TAG
rules:
- - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
- changes:
- - services/data-manager/**/*
-
+ - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
+ changes:
+ - services/data-manager/**/*
build-ical-image:
stage: oci-build
image: docker:latest
services:
- - docker:dind
+ - docker:dind
tags:
- - image
+ - image
variables:
- IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-ical
+ 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
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- - docker build --pull -t $IMAGE_TAG -f ./services/ical/Dockerfile --target prod ./services
- - docker push $IMAGE_TAG
+ - docker build --pull -t $IMAGE_TAG -f ./services/ical/Dockerfile --target prod
+ ./services
+ - docker push $IMAGE_TAG
rules:
- - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
- changes:
- - services/ical/**/*
-
+ - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
+ changes:
+ - services/ical/**/*
build-frontend-image:
stage: oci-build
image: docker:latest
services:
- - docker:dind
+ - docker:dind
tags:
- - image
+ - image
variables:
- IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-frontend
+ IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-frontend"
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
- - cd ./frontend
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+ - cd ./frontend
script:
- - docker build --pull -t $IMAGE_TAG -f ./Dockerfile --target prod .
- - docker push $IMAGE_TAG
+ - docker build --pull -t $IMAGE_TAG -f ./Dockerfile --target prod .
+ - docker push $IMAGE_TAG
rules:
- - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
- changes:
- - frontend/**/*
-
-# Development deployment job
+ - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "development"
+ changes:
+ - frontend/**/*
deploy-dev:
- stage: deploy-dev # New stage for development deployment
+ stage: deploy-dev
image: alpine:latest
before_script:
- - apk add --no-cache openssh-client sed # install dependencies
- - eval $(ssh-agent -s) # set some ssh variables
- - ssh-add <(echo "$CI_SSH_KEY" | tr -d '\r')
+ - apk add --no-cache openssh-client sed
+ - eval $(ssh-agent -s)
+ - ssh-add <(echo "$CI_SSH_KEY" | tr -d '\r')
script:
- # replace some placeholders
- - sed -i -e "s|DOCKER_REGISTRY_REPO|$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG|" docker-compose.dev.yml # Assuming you have a separate docker-compose file for development
- # upload necessary files to the dev server
- - >
- scp -P $CI_SSH_PORT -o StrictHostKeyChecking=no -o LogLevel=ERROR ./docker-compose.dev.yml ./reverseproxy.dev.conf
- $CI_SSH_USER@$CI_SSH_DEV_HOST:/home/$CI_SSH_USER/docker/htwkalender/
- # ssh to the dev server and start the service
- - >
- ssh -p $CI_SSH_PORT -o StrictHostKeyChecking=no -o LogLevel=ERROR $CI_SSH_USER@$CI_SSH_DEV_HOST
- "cd /home/$CI_SSH_USER/docker/htwkalender/ &&
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY &&
- docker compose -f ./docker-compose.dev.yml down && docker compose -f ./docker-compose.dev.yml up -d --remove-orphans && docker logout"
+ - sed -i -e "s|DOCKER_REGISTRY_REPO|$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG|" docker-compose.dev.yml
+ - 'scp -P $CI_SSH_PORT -o StrictHostKeyChecking=no -o LogLevel=ERROR ./docker-compose.dev.yml
+ ./reverseproxy.dev.conf $CI_SSH_USER@$CI_SSH_DEV_HOST:/home/$CI_SSH_USER/docker/htwkalender/
+
+ '
+ - 'ssh -p $CI_SSH_PORT -o StrictHostKeyChecking=no -o LogLevel=ERROR $CI_SSH_USER@$CI_SSH_DEV_HOST
+ "cd /home/$CI_SSH_USER/docker/htwkalender/ && docker login -u $CI_REGISTRY_USER
+ -p $CI_REGISTRY_PASSWORD $CI_REGISTRY && docker compose -f ./docker-compose.dev.yml
+ down && docker compose -f ./docker-compose.dev.yml up -d --remove-orphans && docker
+ logout"
+
+ '
rules:
- - if: $CI_COMMIT_BRANCH == "development" # Only execute for the development branch
-
-
+ - if: $CI_COMMIT_BRANCH == "development"
deploy-all:
stage: deploy
image: alpine:latest
@@ -280,3 +264,5 @@ deploy-all:
docker exec --user root htwkalender-htwkalender-frontend-1 /bin/sh -c \"echo 'google-site-verification: $GOOGLE_VERIFICATION.html' > ./$GOOGLE_VERIFICATION.html\" "
rules:
- if: $CI_COMMIT_BRANCH == "main"
+include:
+- template: Security/Dependency-Scanning.gitlab-ci.yml
diff --git a/services/go.mod b/services/go.mod
index 0b5f256..6e08859 100644
--- a/services/go.mod
+++ b/services/go.mod
@@ -6,6 +6,7 @@ toolchain go1.23.1
require (
github.com/PuerkitoBio/goquery v1.10.0
+ github.com/arran4/golang-ical v0.3.2-0.20241015053101-5bb438cf85f5
github.com/goccy/go-json v0.10.3
github.com/gofiber/fiber/v3 v3.0.0-beta.3
github.com/google/uuid v1.6.0
diff --git a/services/go.sum b/services/go.sum
index 66c6218..35e5d33 100644
--- a/services/go.sum
+++ b/services/go.sum
@@ -1,5 +1,3 @@
-cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
-cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
@@ -9,62 +7,15 @@ cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
-cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc=
-cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
-cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4=
-cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY=
-cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE=
-cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU=
-cloud.google.com/go/monitoring v1.20.4 h1:zwcViK7mT9SV0kzKqLOI3spRadvsmvw/R9z1MHNeC0E=
-cloud.google.com/go/monitoring v1.20.4/go.mod h1:v7F/UcLRw15EX7xq565N7Ae5tnYEE28+Cl717aTXG4c=
-cloud.google.com/go/pubsub v1.41.0 h1:ZPaM/CvTO6T+1tQOs/jJ4OEMpjtel0PTLV7j1JK+ZrI=
-cloud.google.com/go/pubsub v1.41.0/go.mod h1:g+YzC6w/3N91tzG66e2BZtp7WrpBBMXVa3Y9zVoOGpk=
-cloud.google.com/go/secretmanager v1.13.6 h1:0ZEl/LuoB4xQsjVfQt3Gi/dZfOv36n4JmdPrMargzYs=
-cloud.google.com/go/secretmanager v1.13.6/go.mod h1:x2ySyOrqv3WGFRFn2Xk10iHmNmvmcEVSSqc30eb1bhw=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
-cloud.google.com/go/trace v1.10.12 h1:GoGZv1iAXEa73HgSGNjRl2vKqp5/f2AeKqErRFXA2kg=
-cloud.google.com/go/trace v1.10.12/go.mod h1:tYkAIta/gxgbBZ/PIzFxSH5blajgX4D00RpQqCG/GZs=
-contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec h1:CSNP8nIEQt4sZEo2sGUiWSmVJ9c5QdyIQvwzZAsn+8Y=
-contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4=
-contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc=
-contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60=
-contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
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/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk=
-github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
-github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM=
-github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE=
-github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
-github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
-github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A=
-github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
-github.com/Azure/go-amqp v1.0.5 h1:po5+ljlcNSU8xtapHTe8gIc8yHxCzC03E8afH2g1ftU=
-github.com/Azure/go-amqp v1.0.5/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
-github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
-github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU=
-github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE=
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/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
@@ -73,6 +24,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
+github.com/arran4/golang-ical v0.3.2-0.20241015053101-5bb438cf85f5 h1:aIT5x2Cjcg/bioaV8mc1jpDOiousU0alvWTNIcX+F5E=
+github.com/arran4/golang-ical v0.3.2-0.20241015053101-5bb438cf85f5/go.mod h1:xblDGxxIUMWwFZk9dlECUlc1iXNV65LJZOTHLVwu8bo=
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=
@@ -106,18 +59,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsd
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY=
-github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
-github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc=
-github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c=
-github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI=
-github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw=
-github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc=
-github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI=
-github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7M5lu8PqdvOmbRHw=
-github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4=
@@ -127,17 +70,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlh
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
-github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
-github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
-github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/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=
@@ -146,32 +80,20 @@ 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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
-github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
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/dop251/goja v0.0.0-20240822155948-fa6d1ed5e4b6 h1:0x8Sh2rKCTVUQnRTJFIwtRWAp91VMsnATQEsMAg14kM=
-github.com/dop251/goja v0.0.0-20240822155948-fa6d1ed5e4b6/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
-github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co=
-github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU=
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/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
-github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
-github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
@@ -182,8 +104,6 @@ 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-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
-github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
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.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
@@ -196,15 +116,10 @@ github.com/gofiber/utils/v2 v2.0.0-beta.6 h1:ED62bOmpRXdgviPlfTmf0Q+AXzhaTUAFtdW
github.com/gofiber/utils/v2 v2.0.0-beta.6/go.mod h1:3Kz8Px3jInKFvqxDzDeoSygwEOO+3uyubTmUa6PqY+0=
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-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
-github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
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 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
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=
@@ -226,12 +141,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
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/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
-github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI=
-github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
-github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
-github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
-github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
@@ -245,7 +154,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.3 h1:QRje2j5GZimBzlbhGA2
github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
-github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
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=
@@ -257,7 +165,6 @@ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInw
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
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/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
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=
@@ -269,13 +176,9 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
-github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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=
@@ -293,29 +196,19 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
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/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
-github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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.20 h1:yUkhO5bTPWlzD4ZK6EQlS4R3AcHKDlBD+DxxU2BR83I=
github.com/pocketbase/pocketbase v0.22.20/go.mod h1:Cw5E4uoGhKItBIE2lJL3NfmiUr9Syk2xaNJ2G7Dssow=
-github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342 h1:OcAwewen3hs/zY8i0syt8CcMTGBJhQwQRVDLcoQVXVk=
-github.com/pocketbase/tygoja v0.0.0-20240113091827-17918475d342/go.mod h1:dOJ+pCyqm/jRn5kO/TX598J0e5xGDcJAZerK5atCrKI=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/prometheus v0.54.0 h1:6+VmEkohHcofl3W5LyRlhw1Lfm575w/aX6ZFyVAmzM0=
-github.com/prometheus/prometheus v0.54.0/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY=
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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
@@ -335,8 +228,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
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/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
-github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
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.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
@@ -345,7 +236,6 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
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 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
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=
@@ -359,10 +249,6 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
-go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gocloud.dev v0.39.0 h1:EYABYGhAalPUaMrbSKOr5lejxoxvXj99nE8XFtsDgds=
gocloud.dev v0.39.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -371,14 +257,11 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
-golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
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 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -458,7 +341,6 @@ google.golang.org/api v0.196.0 h1:k/RafYqebaIJBO3+SMnfEGtFVlvp5vSgqTUF54UN/zg=
google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE=
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 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
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=
@@ -467,8 +349,6 @@ google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eY
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk=
google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM=
-google.golang.org/genproto/googleapis/bytestream v0.0.0-20240903143218-8af14fe29dc1 h1:W0PHii1rtgc5UgBtJif8xGePValKeZRomnuC5hatKME=
-google.golang.org/genproto/googleapis/bytestream v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:q0eWNnCW04EJlyrmLT+ZHsjuoUiZ36/eAEdCCezZoco=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -489,24 +369,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/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 h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
-lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
-modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
-modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
-modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA=
-modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
diff --git a/services/ical/main.go b/services/ical/main.go
index 6a21cef..adff01b 100644
--- a/services/ical/main.go
+++ b/services/ical/main.go
@@ -72,7 +72,7 @@ func main() {
fiberApp.Use(logger.New(
logger.Config{
Format: "${time} | ${status} | ${latency} | ${method} - ${path} | ${error}\n",
- TimeFormat: "02-Jan-2006 15:04:05",
+ TimeFormat: "02-01-2006 15:04:05",
},
))
diff --git a/services/ical/service/functions/roomMapping.go b/services/ical/service/functions/roomMapping.go
new file mode 100644
index 0000000..8163c9b
--- /dev/null
+++ b/services/ical/service/functions/roomMapping.go
@@ -0,0 +1,40 @@
+package functions
+
+import (
+ "regexp"
+ "strings"
+)
+
+func MapRoom(room string, output bool) string {
+ // remove dots from room string
+
+ if output {
+ //replace second point in TR_A1.23.1 -> TR_A1.23-1 with a minus
+ re := regexp.MustCompile(`\b[TR_]+[A-ZÄÖÜ]?[0-9]{1,4}[.][0-9]{1,4}[.]\b`)
+ room = re.ReplaceAllStringFunc(room, func(match string) string {
+ return match[:len(match)-1] + "-" + match[len(match)-1:]
+ })
+ // If the output flag is set, remove all dots from the room string
+ room = strings.ReplaceAll(room, ".", "")
+ } else {
+ // If the output flag is false add a dot for all rooms with regexp TR_A123 -> TR_A1.23
+ re := regexp.MustCompile(`\bTR_+[A-ZÄÖÜ]?[0-9]{1,4}[a-zäöü]?\b`)
+ room = re.ReplaceAllStringFunc(room, func(match string) string {
+ return match[:len(match)-2] + "." + match[len(match)-2:]
+ })
+ room = strings.ReplaceAll(room, "-", ".")
+ }
+
+ // Regular expression pattern to match room identifiers
+ // The pattern looks for strings that start with two uppercase letters, optionally followed by an underscore,
+ // followed by 1 to 3 digits, and ending with "-[A-Z]"
+ re := regexp.MustCompile(`\b[A-ZÄÖÜ]{2}([_]+[A-ZÄÖÜ])?[0-9]{1,4}[a-zäöü]?[-]?[0-9]?-[A-ZÄÖÜ]\b`)
+
+ // Use the ReplaceAllStringFunc to process each match
+ room = re.ReplaceAllStringFunc(room, func(match string) string {
+ // Remove the last two characters (i.e., "-")
+ return match[:len(match)-2]
+ })
+
+ return room
+}
diff --git a/services/ical/service/functions/roomMapping_test.go b/services/ical/service/functions/roomMapping_test.go
new file mode 100644
index 0000000..b4e36ea
--- /dev/null
+++ b/services/ical/service/functions/roomMapping_test.go
@@ -0,0 +1,73 @@
+package functions
+
+import "testing"
+
+func TestMapRoom(t *testing.T) {
+ type args struct {
+ room string
+ output bool
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "Test 1 MapRoom",
+ args: args{
+ room: "H.1.1",
+ output: true,
+ },
+ want: "H11",
+ },
+ {
+ name: "Test Treftsbau MapRoom",
+ args: args{
+ room: "TR_L3.03-S,TR_L3.02-S,TR_L2.13-L,TR_L2.05-S,TR_L1.14-H,TR_L1.07-H,TR_L1.06-B,TR_L0.14-S,TR_Innenhof_FF,TR_C1.62-L,TR_B1.50-S,TR_B1.49-S,TR_B1.48-S,TR_B1.46-S,TR_B1.45-S,TR_B0.71-L,TR_B0.70-L,TR_B0.67-L,TR_A_Cafeteria,TR_A2.28-L,TR_A1.40-F,TR_A1.37-S,TR_A1.34-S,TR_A1.29-H,TR_A1.28-S,TR_A1.27-S,TR_A1.26-S,TR_A1.25-S,TR_A1.24-H,TR_A0.34-L,TR_A0.33-L,TR_A0.32.2-L,TR_A0.32.1-L,TR_A0.31.1-L,NI_FoyerK_F,NI_Foyer1_F,NI104-L,NI103-L,NI102-L,NI070-L,GU319-L,GU318-L,GU317b-L,GU317a-L,GU313-L,GU309-L,GU301-L,GU225-L,GU224-L,GU219-L,GU203-L,GU202-L,GU201-L,GU014-L,GU013-L,GU012-L,GU011-L,GU010-L,GU009-L,GU001-L,FÖ306-S,FÖ305-S,FÖ304-S",
+ output: true,
+ },
+ want: "TR_L303,TR_L302,TR_L213,TR_L205,TR_L114,TR_L107,TR_L106,TR_L014,TR_Innenhof_FF,TR_C162,TR_B150,TR_B149,TR_B148,TR_B146,TR_B145,TR_B071,TR_B070,TR_B067,TR_A_Cafeteria,TR_A228,TR_A140,TR_A137,TR_A134,TR_A129,TR_A128,TR_A127,TR_A126,TR_A125,TR_A124,TR_A034,TR_A033,TR_A032-2,TR_A032-1,TR_A031-1,NI_FoyerK_F,NI_Foyer1_F,NI104,NI103,NI102,NI070,GU319,GU318,GU317b,GU317a,GU313,GU309,GU301,GU225,GU224,GU219,GU203,GU202,GU201,GU014,GU013,GU012,GU011,GU010,GU009,GU001,FÖ306,FÖ305,FÖ304",
+ },
+ {
+ name: "Test Trefstbau MapRoom Input",
+ args: args{
+ room: "TR_L321",
+ output: false,
+ },
+ want: "TR_L3.21",
+ },
+ {
+ name: "Test Trefstbau MapRoom Input",
+ args: args{
+ room: "TR_A032-2",
+ output: false,
+ },
+ want: "TR_A0.32.2",
+ },
+ {
+ name: "Test Trefstbau MapRoom Input double point",
+ args: args{
+ //TR_A1.23.1 -> TR_A1.23-1
+ room: "TR_A1.23.1",
+ output: true,
+ },
+ want: "TR_A123-1",
+ },
+ {
+ name: "Test Trefstbau MapRoom Input double point with -S",
+ args: args{
+ //TR_A1.23.1 -> TR_A1.23-1
+ room: "TR_A1.23.1-S",
+ output: true,
+ },
+ want: "TR_A123-1",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := MapRoom(tt.args.room, tt.args.output); got != tt.want {
+ t.Errorf("MapRoom() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/services/ical/service/ical/ical.go b/services/ical/service/ical/ical.go
index 11759e0..95f2272 100644
--- a/services/ical/service/ical/ical.go
+++ b/services/ical/service/ical/ical.go
@@ -17,9 +17,7 @@
package ical
import (
- "bytes"
"fmt"
- "github.com/jordic/goics"
"htwkalender/ical/model"
"htwkalender/ical/service/connector"
htwkalenderGrpc "htwkalender/ical/service/connector/grpc"
@@ -32,7 +30,7 @@ const expirationTime = 5 * time.Minute
var FeedDeletedError = fmt.Errorf("feed deleted")
-func Feed(app model.AppType, token string) (string, string, error) {
+func Feed(app model.AppType, token string, userAgent string) (string, string, error) {
var events model.Events
modules := map[string]model.FeedCollection{}
@@ -72,9 +70,8 @@ func Feed(app model.AppType, token string) (string, string, error) {
// Generate one Hash for E-TAG from all events and modules
etag := functions.HashString(events.String() + fmt.Sprint(modules))
- 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))}
+ cal := GenerateIcalFeed(events, modules, userAgent)
+ icalFeed := &model.FeedModel{Content: cal.Serialize(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))}
return icalFeed.Content, etag, nil
}
diff --git a/services/ical/service/ical/icalDescriptionGenerator.go b/services/ical/service/ical/icalDescriptionGenerator.go
new file mode 100644
index 0000000..8b5fbfc
--- /dev/null
+++ b/services/ical/service/ical/icalDescriptionGenerator.go
@@ -0,0 +1,139 @@
+//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 (
+ ics "github.com/arran4/golang-ical"
+ "htwkalender/ical/model"
+ "htwkalender/ical/service/functions"
+ "log/slog"
+ "net/url"
+ "strings"
+ _ "time/tzdata"
+)
+
+const (
+ Thunderbird = "Thunderbird"
+ GoogleCalendar = "Google-Calendar-Importer"
+)
+
+// Adds a user-agent specific description and ALTREP to the calendar event.
+func addUserAgentSpecificDescription(vEvent *ics.VEvent, event model.Event, userAgent string) {
+ description, altrep := generateDescription(event, userAgent)
+
+ if userAgentType := identifyUserAgent(userAgent); userAgentType == Thunderbird && altrep != "" {
+ vEvent.AddProperty(ics.ComponentPropertyDescription, description, ics.WithAlternativeRepresentation(buildDataURL(altrep)))
+ } else {
+ vEvent.AddProperty(ics.ComponentPropertyDescription, description)
+ }
+}
+
+// Generates a description and ALTREP (alternative representation) based on the user agent.
+func generateDescription(event model.Event, userAgent string) (string, string) {
+ plainDescription := buildPlainTextDescription(event)
+
+ switch identifyUserAgent(userAgent) {
+ case Thunderbird:
+ htmlDescription := generateHTMLDescriptionForThunderbird(event)
+ altrep := "text/html," + url.PathEscape(htmlDescription)
+ return plainDescription, altrep
+ case GoogleCalendar:
+ plainDescription += generateRoomLinksForGoogleCalendar(event.Rooms)
+ }
+ return plainDescription, ""
+}
+
+// Builds a plain text description of the event details.
+func buildPlainTextDescription(event model.Event) string {
+ var description strings.Builder
+
+ if !functions.OnlyWhitespace(event.Prof) {
+ description.WriteString("Profs: " + event.Prof + "\n")
+ }
+ if !functions.OnlyWhitespace(event.Course) {
+ description.WriteString("Gruppen: " + event.Course + "\n")
+ }
+ if !functions.OnlyWhitespace(event.EventType) {
+ description.WriteString("Typ: " + event.EventType + event.Compulsory + "\n")
+ }
+ if !functions.OnlyWhitespace(event.Notes) {
+ description.WriteString("Notizen: " + event.Notes + "\n")
+ }
+
+ return description.String()
+}
+
+// Generates an HTML description for Thunderbird users.
+func generateHTMLDescriptionForThunderbird(event model.Event) string {
+ var htmlDescription strings.Builder
+
+ if !functions.OnlyWhitespace(event.Prof) {
+ htmlDescription.WriteString("Profs: " + event.Prof + "
")
+ }
+ if !functions.OnlyWhitespace(event.Course) {
+ htmlDescription.WriteString("Gruppen: " + event.Course + "
")
+ }
+ if !functions.OnlyWhitespace(event.EventType) {
+ htmlDescription.WriteString("Typ: " + event.EventType + event.Compulsory + "
")
+ }
+
+ if !functions.OnlyWhitespace(event.Rooms) {
+ htmlDescription.WriteString("Orte: ")
+ for _, room := range functions.SeperateRoomString(event.Rooms) {
+ roomLink := functions.MapRoom(room, true)
+ _, err := htmlDescription.WriteString("" + room + " ")
+ if err != nil {
+ slog.Error("Error while writing to HTML description", "error", err)
+ return ""
+ }
+ }
+ }
+
+ return htmlDescription.String()
+}
+
+// Generates room links formatted for Google Calendar.
+func generateRoomLinksForGoogleCalendar(rooms string) string {
+ var description strings.Builder
+ roomList := functions.SeperateRoomString(rooms)
+
+ if !functions.OnlyWhitespace(rooms) {
+ description.WriteString("Orte: ")
+ for _, room := range roomList {
+ roomLink := functions.MapRoom(room, true)
+ description.WriteString(" " + room + " ")
+ }
+ }
+
+ return description.String()
+}
+
+// Identifies the user agent type (e.g., Thunderbird or Google Calendar).
+func identifyUserAgent(userAgent string) string {
+ if strings.Contains(userAgent, Thunderbird) {
+ return Thunderbird
+ }
+ if strings.Contains(userAgent, GoogleCalendar) {
+ return GoogleCalendar
+ }
+ return ""
+}
+
+// Constructs a data URL for the ALTREP representation.
+func buildDataURL(data string) *url.URL {
+ return &url.URL{Scheme: "data", Opaque: data}
+}
diff --git a/services/ical/service/ical/icalDescriptionGenerator_test.go b/services/ical/service/ical/icalDescriptionGenerator_test.go
new file mode 100644
index 0000000..1fd992f
--- /dev/null
+++ b/services/ical/service/ical/icalDescriptionGenerator_test.go
@@ -0,0 +1,324 @@
+package ical
+
+import (
+ "htwkalender/ical/model"
+ "net/url"
+ "reflect"
+ "testing"
+)
+
+func Test_buildDataURL(t *testing.T) {
+ type args struct {
+ data string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *url.URL
+ }{
+ {
+ name: "Test 1",
+ args: args{
+ data: "text/calendar;charset=utf-8;base64,ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg",
+ },
+ want: &url.URL{
+ Scheme: "data",
+ Opaque: "text/calendar;charset=utf-8;base64,ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg",
+ },
+ },
+ {
+ name: "Test 2",
+ args: args{
+ data: "text/calendar;charset=utf-8;base64,ÄÜ'*A`+#Add\"$%&/()=?",
+ },
+ want: &url.URL{
+ Scheme: "data",
+ Opaque: "text/calendar;charset=utf-8;base64,ÄÜ'*A`+#Add\"$%&/()=?",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := buildDataURL(tt.args.data); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("buildDataURL() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_identifyUserAgent(t *testing.T) {
+ type args struct {
+ userAgent string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "Test 1",
+ args: args{
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Thunderbird/78.10.0",
+ },
+ want: "Thunderbird",
+ },
+ {
+ name: "Test 2",
+ args: args{
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 GoogleCalendar/78.10.0",
+ },
+ want: "",
+ },
+ {
+ name: "Test 3",
+ args: args{
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101",
+ },
+ want: "",
+ },
+ {
+ name: "Test 4",
+ args: args{
+ userAgent: "Google-Calendar-Importer 1.0",
+ },
+ want: "Google-Calendar-Importer",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := identifyUserAgent(tt.args.userAgent); got != tt.want {
+ t.Errorf("identifyUserAgent() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_buildPlainTextDescription(t *testing.T) {
+ type args struct {
+ event model.Event
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "All fields have valid values",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "Math 101",
+ EventType: "S",
+ Compulsory: "w",
+ Notes: "Bring textbook",
+ },
+ },
+ want: "Profs: Dr. Smith\nGruppen: Math 101\nTyp: Sw\nNotizen: Bring textbook\n",
+ },
+ {
+ name: "All fields are empty",
+ args: args{
+ event: model.Event{
+ Prof: "",
+ Course: "",
+ EventType: "",
+ Compulsory: "",
+ Notes: "",
+ },
+ },
+ want: "",
+ },
+ {
+ name: "Some fields are empty",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "",
+ EventType: "Seminar",
+ Compulsory: "",
+ Notes: "",
+ },
+ },
+ want: "Profs: Dr. Smith\nTyp: Seminar\n",
+ },
+ {
+ name: "Fields contain only whitespace",
+ args: args{
+ event: model.Event{
+ Prof: " ",
+ Course: " ",
+ EventType: " ",
+ Compulsory: "",
+ Notes: " ",
+ },
+ },
+ want: "",
+ },
+ {
+ name: "Mix of valid values, empty values, and whitespace",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Brown",
+ Course: " ",
+ EventType: "V",
+ Compulsory: "p",
+ Notes: "",
+ },
+ },
+ want: "Profs: Dr. Brown\nTyp: Vp\n",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := buildPlainTextDescription(tt.args.event); got != tt.want {
+ t.Errorf("buildPlainTextDescription() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_generateRoomLinksForGoogleCalendar(t *testing.T) {
+ type args struct {
+ rooms string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "Test 1",
+ args: args{
+ rooms: "A123, ZU423, C789",
+ },
+ want: "Orte: A123 ZU423 C789 ",
+ },
+ {
+ name: "Test 2",
+ args: args{
+ rooms: "A123",
+ },
+ want: "Orte: A123 ",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := generateRoomLinksForGoogleCalendar(tt.args.rooms); got != tt.want {
+ t.Errorf("generateRoomLinksForGoogleCalendar() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_generateHTMLDescriptionForThunderbird(t *testing.T) {
+ type args struct {
+ event model.Event
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "All fields have valid values including rooms",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "Math 101",
+ EventType: "Lecture",
+ Compulsory: " (mandatory)",
+ Rooms: "ZU423, FE231",
+ },
+ },
+ want: "Profs: Dr. Smith
Gruppen: Math 101
Typ: Lecture (mandatory)
Orte: ZU423 FE231 ",
+ },
+ {
+ name: "All fields are empty",
+ args: args{
+ event: model.Event{
+ Prof: "",
+ Course: "",
+ EventType: "",
+ Compulsory: "",
+ Rooms: "",
+ },
+ },
+ want: "",
+ },
+ {
+ name: "Some fields are empty, rooms have valid values",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "",
+ EventType: "Seminar",
+ Compulsory: "",
+ Rooms: "TR_L1.06-B",
+ },
+ },
+ want: "Profs: Dr. Smith
Typ: Seminar
Orte: TR_L1.06-B ",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := generateHTMLDescriptionForThunderbird(tt.args.event); got != tt.want {
+ t.Errorf("generateHTMLDescriptionForThunderbird() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_generateDescription(t *testing.T) {
+ type args struct {
+ event model.Event
+ userAgent string
+ }
+ tests := []struct {
+ name string
+ args args
+ description string
+ altrep string
+ }{
+ {
+ name: "Test 1",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "Math 101",
+ EventType: "Lecture",
+ Compulsory: " (mandatory)",
+ Rooms: "ZU423, FE231",
+ },
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Thunderbird/78.10.0",
+ },
+ description: "Profs: Dr. Smith\nGruppen: Math 101\nTyp: Lecture (mandatory)\n",
+ altrep: "text/html,Profs:%20Dr.%20Smith%3Cbr%3EGruppen:%20Math%20101%3Cbr%3ETyp:%20Lecture%20%28mandatory%29%3Cbr%3EOrte:%20%3Ca%20href=%22https:%2F%2Fmap.htwk-leipzig.de%2Froom%2FZU423%22%3EZU423%3C%2Fa%3E%20%3Ca%20href=%22https:%2F%2Fmap.htwk-leipzig.de%2Froom%2FFE231%22%3EFE231%3C%2Fa%3E%20",
+ },
+ {
+ name: "Test 2",
+ args: args{
+ event: model.Event{
+ Prof: "Dr. Smith",
+ Course: "Math 101",
+ EventType: "Lecture",
+ Compulsory: " (mandatory)",
+ Rooms: "ZU423, FE231",
+ },
+ userAgent: "Google-Calendar-Importer",
+ },
+ description: "Profs: Dr. Smith\nGruppen: Math 101\nTyp: Lecture (mandatory)\nOrte: ZU423 FE231 ",
+ altrep: "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, got1 := generateDescription(tt.args.event, tt.args.userAgent)
+ if got != tt.description {
+ t.Errorf("generateDescription() got = %v, want %v", got, tt.description)
+ }
+ if got1 != tt.altrep {
+ t.Errorf("generateDescription() got1 = %v, want %v", got1, tt.altrep)
+ }
+ })
+ }
+}
diff --git a/services/ical/service/ical/icalFileGeneration.go b/services/ical/service/ical/icalFileGeneration.go
index 9af0c60..9674c06 100644
--- a/services/ical/service/ical/icalFileGeneration.go
+++ b/services/ical/service/ical/icalFileGeneration.go
@@ -17,121 +17,157 @@
package ical
import (
+ ics "github.com/arran4/golang-ical"
"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
-}
+func GenerateIcalFeed(events model.Events, mapping map[string]model.FeedCollection, userAgent string) *ics.Calendar {
-// EmitICal implements the interface for goics
-func (icalModel IcalModel) EmitICal() goics.Componenter {
- internalClock := clock.RealClock{}
- c := generateIcalEmit(icalModel, internalClock)
- return c
-}
+ cal := ics.NewCalendarFor("HTWK Kalender")
+ setDefaultIcalParams(cal)
-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")
+ internalClock := clock.RealClock{}
- 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 events {
+ mapEntry, mappingFound := mapping[event.UUID]
- 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)))
+ icalEvent := ics.NewEvent(eventHash + "@htwkalender.de")
+
+ icalEvent.SetDtStampTime(internalClock.Now().Local().In(europeTime))
+ icalEvent.SetStartAt(time.Time(event.Start).Local().In(europeTime))
+ icalEvent.SetEndAt(time.Time(event.End).Local().In(europeTime))
if mappingFound {
- addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
- addAlarmIfSpecified(s, event, mapEntry, internalClock)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, replaceNameIfUserDefined(&event, mapEntry))
+ addAlarmIfSpecified(icalEvent, event, mapEntry, internalClock)
} else {
- addPropertyIfNotEmpty(s, "SUMMARY", event.Name)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, event.Name)
}
- addPropertyIfNotEmpty(s, "DESCRIPTION", generateDescription(event))
- addPropertyIfNotEmpty(s, "LOCATION", event.Rooms)
- c.AddComponent(s)
+ addUserAgentSpecificDescription(icalEvent, event, userAgent)
+ addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertyLocation, event.Rooms)
+
+ cal.AddVEvent(icalEvent)
+
}
- return c
+ return cal
}
-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)
+func setDefaultIcalParams(cal *ics.Calendar) {
+ cal.SetMethod(ics.MethodPublish)
+ cal.SetProductId("-//HTWK Kalender//htwkalender.de//DE")
+ cal.SetTzid("Europe/Berlin")
+ cal.SetXWRCalName("HTWK Kalender")
+ cal.SetXWRTimezone("Europe/Berlin")
+ cal.SetVersion("2.0")
+ cal.SetCalscale("GREGORIAN")
- c.AddComponent(tz)
+ vTimeZone := ics.NewTimezone("Europe/Berlin")
+
+ vTimeZone.Components = []ics.Component{
+ &ics.Daylight{
+ ComponentBase: ics.ComponentBase{
+ Properties: []ics.IANAProperty{
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyDtstart),
+ Value: "19700329T020000",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyRrule),
+ Value: "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzname),
+ Value: "CEST",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetfrom),
+ Value: "+0100",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetto),
+ Value: "+0200",
+ },
+ },
+ },
+ },
+ },
+ &ics.Standard{
+ ComponentBase: ics.ComponentBase{
+ Properties: []ics.IANAProperty{
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyDtstart),
+ Value: "19701025T030000",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyRrule),
+ Value: "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzname),
+ Value: "CET",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetfrom),
+ Value: "+0200",
+ },
+ },
+ {
+ BaseProperty: ics.BaseProperty{
+ IANAToken: string(ics.PropertyTzoffsetto),
+ Value: "+0100",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ cal.AddVTimezone(vTimeZone)
}
-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)
+// AddPropertyIfNotEmpty adds a property to the component if the value is not empty
+// or contains only whitespaces
+func addPropertyIfNotEmpty(component *ics.VEvent, key ics.ComponentProperty, value string) {
+ if !functions.OnlyWhitespace(value) {
+ component.AddProperty(key, value)
+ }
}
// 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) {
+func addAlarmIfSpecified(icalEvent *ics.VEvent, 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)
+ alarm := icalEvent.AddAlarm()
+ alarm.AddProperty(ics.ComponentPropertyTrigger, "-P0DT0H15M0S")
+ alarm.AddProperty(ics.ComponentPropertyAction, "DISPLAY")
+ alarm.AddProperty(ics.ComponentPropertyDescription, "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
}
}
@@ -141,33 +177,5 @@ func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection)
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
index f85702f..71dfe96 100644
--- a/services/ical/service/ical/icalFileGeneration_test.go
+++ b/services/ical/service/ical/icalFileGeneration_test.go
@@ -1,257 +1,77 @@
-//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
+func Test_replaceNameIfUserDefined(t *testing.T) {
+ type args struct {
+ event *model.Event
+ mapping model.FeedCollection
}
tests := []struct {
- name string
- fields fields
- want *goics.Component
+ name string
+ args args
+ want string
}{
{
- name: "Test EmitICal",
- fields: fields{
- Events: model.Events{
- {
- UUID: "123",
- Name: "Test",
- EventType: "Test",
- Notes: "Test",
- Prof: "Test",
- Rooms: "Test",
- BookedAt: "Test",
- },
+ name: "Custom Name Test",
+ args: args{
+ event: &model.Event{
+ Name: "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"},
+ mapping: model.FeedCollection{
+ Name: "Test",
+ UserDefinedName: "CustomTest",
},
},
+ want: "CustomTest",
},
{
- 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)),
- },
+ name: "Empty Custom Name Test",
+ args: args{
+ event: &model.Event{
+ Name: "Test",
},
- Mapping: map[string]model.FeedCollection{
- "123": {
- UUID: "123",
- Name: "Test",
- Course: "Test",
- UserDefinedName: "UserDefinedName",
- },
+ mapping: model.FeedCollection{
+ Name: "Test",
+ 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"},
- },
- },
+ want: "Test",
+ },
+ {
+ name: "Empty Name Test",
+ args: args{
+ event: &model.Event{
+ Name: "",
},
- 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"},
+ mapping: model.FeedCollection{
+ Name: "",
+ UserDefinedName: "CustomTest",
},
},
+ want: "CustomTest",
+ },
+ {
+ name: "Names dont match but check is done before",
+ args: args{
+ event: &model.Event{
+ Name: "Test",
+ },
+ mapping: model.FeedCollection{
+ Name: "Test2",
+ UserDefinedName: "CustomTest",
+ },
+ },
+ want: "CustomTest",
},
}
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])
- }
- }
+ if got := replaceNameIfUserDefined(tt.args.event, tt.args.mapping); got != tt.want {
+ t.Errorf("replaceNameIfUserDefined() = %v, want %v", got, tt.want)
}
})
}
diff --git a/services/ical/service/routes.go b/services/ical/service/routes.go
index f7da962..3174d46 100644
--- a/services/ical/service/routes.go
+++ b/services/ical/service/routes.go
@@ -35,7 +35,11 @@ func AddFeedRoutes(app model.AppType) {
token := c.Query("token")
ifNoneMatch := c.Get("If-None-Match")
- results, eTag, err := ical.Feed(app, token)
+ // get request userAgent and check if it is Thunderbird
+ userAgent := c.Get("User-Agent")
+ slog.Info("User-Agent", "userAgent", userAgent)
+
+ results, eTag, err := ical.Feed(app, token, userAgent)
if errors.Is(err, ical.FeedDeletedError) {
return c.SendStatus(fiber.StatusGone)