diff --git a/backend/go.mod b/backend/go.mod index 7a92cc2..f9cd989 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -47,21 +47,29 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/google/wire v0.5.0 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/compress v1.13.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect gocloud.dev v0.36.0 // indirect golang.org/x/crypto v0.18.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index ad5fe1f..15ac45c 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -5,13 +5,60 @@ cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiV cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= +cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/monitoring v1.16.3 h1:mf2SN9qSoBtIgiMA4R/y4VADPWZA7VCNJA079qLaZQ8= +cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= +cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= +cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/trace v1.10.4 h1:2qOAuAzNezwW3QN+t41BtkDJOG42HywL73q8x/f6fnM= +cloud.google.com/go/trace v1.10.4/go.mod h1:Nso99EDIK8Mj5/zmB+iGr9dosS/bzWCJ8wGmE6TXNWY= +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= 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.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +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.5.0 h1:HKHkea1fdm18LT8VAxTVZgJpPsLgv+0NZhmtus1UqJQ= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.5.0/go.mod h1:4BbKA+mRmmTP8VaLfDPNF5nOdhRm5upG3AXVWfv1dxc= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= +github.com/Azure/go-amqp v1.0.2 h1:zHCHId+kKC7fO8IkwyZJnWMvtRXhYC0VJtD0GYkHc6M= +github.com/Azure/go-amqp v1.0.2/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.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/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.33.14 h1:9bRF9/edlX1kycHVm/icATaIWfVyHcUB9c68iWWeNok= +github.com/GoogleCloudPlatform/cloudsql-proxy v1.33.14/go.mod h1:vroGijye9h4A6kMWeCtk9/zIh5ebseV/JmbKJ0VL3w8= 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.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= @@ -52,8 +99,18 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIG github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.5 h1:7lKTr8zJ2nVaVgyII+7hUayTi7xWedMuANiNVXiD2S8= +github.com/aws/aws-sdk-go-v2/service/kms v1.27.5/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU= github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.5 h1:qYi/BfDrWXZxlmRjlKCyFmtI4HKJwW8OKDKhKRAOZQI= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.5/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.5 h1:umyC9zH/A1w8AXrrG7iMxT4Rfgj80FjfvLannWt5vuE= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.5/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.29.5 h1:cJb4I498c1mrOVrRqYTcnLD65AFqUuseHfzHdNZHL9U= +github.com/aws/aws-sdk-go-v2/service/sqs v1.29.5/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.5 h1:5SI5O2tMp/7E/FqhYnaKdxbWjlCi2yujjNI/UO725iU= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.5/go.mod h1:uXndCJoDO9gpuK24rNWVCnrGNUydKFEAYAZ7UU9S0rQ= github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4= @@ -63,8 +120,18 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZ github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.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/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= 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= @@ -73,24 +140,38 @@ 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.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/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-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw= +github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ= +github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0= 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.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= 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/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.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-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.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+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.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= @@ -99,10 +180,15 @@ 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/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.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= 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= @@ -118,6 +204,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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= @@ -137,6 +225,7 @@ github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9 github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= @@ -147,6 +236,7 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 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= @@ -156,18 +246,27 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y 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 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= 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.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 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= @@ -181,17 +280,27 @@ github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S 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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= 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.20.5 h1:unrGe6MG/D2AQDjjdcyh1WrbXI10wWe1gFcGdG/yaNU= github.com/pocketbase/pocketbase v0.20.5/go.mod h1:uy7WOxXoICrwe8HlyR78vTvK0RdG5REkhDx4SvYi4FY= +github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4 h1:85kAYIKrKEeau7WgXg8B7Km8etrVavJAyH7XcR5MkFw= +github.com/pocketbase/tygoja v0.0.0-20231111102932-5420517293f4/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.48.0 h1:yrBloImGQ7je4h8M10ujGh4R6oxYQJQKlMuETwNskGk= +github.com/prometheus/prometheus v0.48.0/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= 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.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= @@ -201,6 +310,7 @@ 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 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= @@ -213,9 +323,24 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +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.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus= gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -223,12 +348,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= 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.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= 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= @@ -324,6 +451,8 @@ google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f/go.mod h1:iIgEblxoG4klcXsG0d9cpoxJ4xndv6+1FkDROCHhPRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -346,6 +475,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= @@ -354,15 +484,22 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C 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.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= lukechampine.com/uint128 v1.3.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.2.1 h1:xwwaXFwiPaVZpGRMd19NPLsaiNyNBO8oChey4501g1M= +modernc.org/cc/v4 v4.2.1/go.mod h1:0O8vuqhQfwBy+piyfEjzWIUGV4I3TPsXSf0W05+lgN8= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/ccgo/v4 v4.0.0-20230612200659-63de3e82e68d h1:3yB/pQNL5kVPDifGFqoZjeRxf8m0+Us15rB7ertNASQ= +modernc.org/ccgo/v4 v4.0.0-20230612200659-63de3e82e68d/go.mod h1:austqj6cmEDRfewsUvmGmyIgsI/Nq87oTXlfTgY85Fc= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/gc/v2 v2.1.2-0.20220923113132-f3b5abcf8083 h1:rGoLVwiOxdeVkGYMOF/8Pw7xpDd3OqScJU/tqHgvY1c= +modernc.org/gc/v2 v2.1.2-0.20220923113132-f3b5abcf8083/go.mod h1:Zt5HLUW0j+l02wj99UsPs+1DOFwwsGnqfcw+BGyyP/A= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v1.37.0 h1:WerjebcsP6A7Jy+f2lCnHAkiSTLf7IaSftBYUtoswak= diff --git a/backend/main.go b/backend/main.go index d39757a..e6d2cff 100644 --- a/backend/main.go +++ b/backend/main.go @@ -41,6 +41,6 @@ func main() { service.AddSchedules(app) if err := app.Start(); err != nil { - slog.Error("Failed to start app: %v", err) + slog.Error("Failed to start app: %v", "error", err) } } diff --git a/backend/model/roomOccupancyModel.go b/backend/model/roomOccupancyModel.go new file mode 100644 index 0000000..09c7404 --- /dev/null +++ b/backend/model/roomOccupancyModel.go @@ -0,0 +1,35 @@ +//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 ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type RoomOccupancy struct { + Name string `bson:"name"` + Occupancy primitive.Binary `bson:"occupancy"` +} + +type RoomOccupancyList struct { + Start time.Time `bson:"start"` + Granularity int `bson:"granularity"` + Blocks int `bson:"blocks"` + Rooms []RoomOccupancy `bson:"rooms"` +} diff --git a/backend/openapi.yml b/backend/openapi.yml index ac2dc3d..5ef272d 100644 --- a/backend/openapi.yml +++ b/backend/openapi.yml @@ -116,6 +116,31 @@ paths: responses: '200': description: Successful response + /api/schedule/rooms: + get: + summary: Get Room Occupancy + parameters: + - name: from + in: query + description: date + example: "2024-12-24T00:00:00.000Z" + required: true + schema: + type: string + - name: to + in: query + description: date + example: "2024-12-25T00:00:00.000Z" + required: true + schema: + type: string + responses: + '200': + description: Successful response + content: + application/bson: + schema: + $ref: '#/components/schemas/RoomOccupancy' /api/createFeed: post: summary: Create iCal Feed @@ -262,4 +287,30 @@ components: events: type: array items: - type: string \ No newline at end of file + type: string + RoomOccupancy: + type: object + properties: + start: + type: string + format: date-time + granularity: + type: integer + blocks: + type: integer + rooms: + type: object + properties: + name: + type: string + occupancy: + type: string + format: binary + required: + - name + - occupancy + required: + - start + - granularity + - blocks + - rooms diff --git a/backend/service/addRoute.go b/backend/service/addRoute.go index 9938aca..143bc86 100644 --- a/backend/service/addRoute.go +++ b/backend/service/addRoute.go @@ -31,8 +31,11 @@ import ( "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" + "go.mongodb.org/mongo-driver/bson" ) +const RoomOccupancyGranularity = 5 + func AddRoutes(app *pocketbase.PocketBase) { app.OnBeforeServe().Add(func(e *core.ServeEvent) error { @@ -42,7 +45,7 @@ func AddRoutes(app *pocketbase.PocketBase) { Handler: func(c echo.Context) error { savedEvents, err := v2.ParseEventsFromRemote(app) if err != nil { - slog.Error("Failed to parse events from remote: %v", err) + slog.Error("Failed to parse events from remote: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse events from remote") } else { return c.JSON(http.StatusOK, savedEvents) @@ -157,7 +160,7 @@ func AddRoutes(app *pocketbase.PocketBase) { date := c.QueryParam("date") roomSchedule, err := room.GetRoomScheduleForDay(app, roomParam, date) if err != nil { - slog.Error("Failed to get room schedule for day: %v", err) + slog.Error("Failed to get room schedule for day: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule for day") } return c.JSON(http.StatusOK, roomSchedule) @@ -172,6 +175,40 @@ func AddRoutes(app *pocketbase.PocketBase) { return nil }) + // API Endpoint to get room occupancy for a time period for all rooms, when requested as BSON + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + _, err := e.Router.AddRoute(echo.Route{ + Method: http.MethodGet, + Path: "/api/schedule/rooms", + Handler: func(c echo.Context) error { + from := c.QueryParam("from") + to := c.QueryParam("to") + rooms, err := room.GetRoomOccupancyList(app, from, to, RoomOccupancyGranularity) + + if err != nil { + slog.Error("Failed to get room occupancy: %v", "error", err) + return c.JSON(http.StatusBadRequest, "Failed to get room occupancy") + } + + bson_coded, err := bson.Marshal(rooms) + + if err != nil { + slog.Error("Failed to encode room occupancy to BSON: %v", "error", err) + return c.JSON(http.StatusBadRequest, "Failed to encode room occupancy to BSON") + } + + return c.Blob(http.StatusOK, "application/bson", bson_coded) + }, + Middlewares: []echo.MiddlewareFunc{ + apis.ActivityLogger(app), + }, + }) + if err != nil { + return err + } + return nil + }) + // API Endpoint to create a new iCal feed app.OnBeforeServe().Add(func(e *core.ServeEvent) error { _, err := e.Router.AddRoute(echo.Route{ @@ -183,7 +220,7 @@ func AddRoutes(app *pocketbase.PocketBase) { from := c.QueryParam("from") roomSchedule, err := room.GetRoomSchedule(app, roomParam, from, to) if err != nil { - slog.Error("Failed to get room schedule: %v", err) + slog.Error("Failed to get room schedule: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get room schedule") } return c.JSON(http.StatusOK, roomSchedule) @@ -206,17 +243,17 @@ func AddRoutes(app *pocketbase.PocketBase) { Handler: func(c echo.Context) error { from, err := time.ParseTime(c.QueryParam("from")) if err != nil { - slog.Error("Failed to parse time: %v", err) + slog.Error("Failed to parse time: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse time") } to, err := time.ParseTime(c.QueryParam("to")) if err != nil { - slog.Error("Failed to parse time: %v", err) + slog.Error("Failed to parse time: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to parse time") } rooms, err := room.GetFreeRooms(app, from, to) if err != nil { - slog.Error("Failed to get free rooms: %v", err) + slog.Error("Failed to get free rooms: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get free rooms") } return c.JSON(http.StatusOK, rooms) @@ -243,7 +280,7 @@ func AddRoutes(app *pocketbase.PocketBase) { modules, err := events.GetModulesForCourseDistinct(app, course, semester) if err != nil { - slog.Error("Failed to get modules for course and semester: %v", err) + slog.Error("Failed to get modules for course and semester: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get modules for course and semester") } else { return c.JSON(http.StatusOK, modules) @@ -266,7 +303,7 @@ func AddRoutes(app *pocketbase.PocketBase) { Handler: func(c echo.Context) error { modules, err := events.GetAllModulesDistinct(app) if err != nil { - slog.Error("Failed to get modules: %v", err) + slog.Error("Failed to get modules: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get modules") } return c.JSON(http.StatusOK, modules) @@ -289,7 +326,7 @@ func AddRoutes(app *pocketbase.PocketBase) { requestModule := c.QueryParam("uuid") module, err := events.GetModuleByUUID(app, requestModule) if err != nil { - slog.Error("Failed to get module: %v", err) + slog.Error("Failed to get module: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get module") } else { return c.JSON(http.StatusOK, module) @@ -339,7 +376,7 @@ func AddRoutes(app *pocketbase.PocketBase) { courses, err := events.GetAllCoursesForSemesterWithEvents(app, semester) if err != nil { - slog.Error("Failed to get courses for semester with events: %v", err) + slog.Error("Failed to get courses for semester with events: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to get courses for semester with events") } else { return c.JSON(200, courses) @@ -364,7 +401,7 @@ func AddRoutes(app *pocketbase.PocketBase) { semester := c.QueryParam("semester") err := events.DeleteAllEventsByCourseAndSemester(app, course, semester) if err != nil { - slog.Error("Failed to delete events: %v", err) + slog.Error("Failed to delete events: %v", "error", err) return c.JSON(http.StatusBadRequest, "Failed to delete events") } else { return c.JSON(http.StatusBadRequest, "Events deleted") @@ -389,7 +426,7 @@ func AddRoutes(app *pocketbase.PocketBase) { err := ical.MigrateFeedJson(app) if err != nil { - slog.Error("Failed to migrate feeds: %v", err) + slog.Error("Failed to migrate feeds: %v", "error", err) return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds") } else { return c.JSON(http.StatusOK, "Migrated") diff --git a/backend/service/addSchedule.go b/backend/service/addSchedule.go index 444429e..8e014bd 100644 --- a/backend/service/addSchedule.go +++ b/backend/service/addSchedule.go @@ -54,7 +54,7 @@ func AddSchedules(app *pocketbase.PocketBase) { slog.Info("Started fetching sport events schedule") sportEvents, err := sport.FetchAndUpdateSportEvents(app) if err != nil { - slog.Error("Failed to fetch and save sport events: %v", err) + slog.Error("Failed to fetch and save sport events: %v", "error", err) } slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(sportEvents)), 10) + " sport events") }) @@ -63,7 +63,7 @@ func AddSchedules(app *pocketbase.PocketBase) { scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() { savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{}) if err != nil { - slog.Error("Failed to fetch and save events: %v", err) + slog.Error("Failed to fetch and save events: %v", "error", err) } else { slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events") } diff --git a/backend/service/course/courseFunctions.go b/backend/service/course/courseFunctions.go index 43720b7..059b3ec 100644 --- a/backend/service/course/courseFunctions.go +++ b/backend/service/course/courseFunctions.go @@ -28,7 +28,7 @@ func UpdateCourse(app *pocketbase.PocketBase) { for _, course := range courses { savedEvents, err := events.UpdateModulesForCourse(app, course) if err != nil { - slog.Warn("Update Course: "+course+" failed: %v", err) + slog.Warn("Update Course: "+course+" failed: %v", "error", err) } else { slog.Info("Updated Course: " + course + " with " + strconv.FormatInt(int64(len(savedEvents)), 10) + " events") } diff --git a/backend/service/date/dateFormat.go b/backend/service/date/dateFormat.go index a8e9c16..8eee644 100644 --- a/backend/service/date/dateFormat.go +++ b/backend/service/date/dateFormat.go @@ -29,7 +29,7 @@ func GetDateFromWeekNumber(year int, weekNumber int, dayName string) (time.Time, europeTime, err := time.LoadLocation("Europe/Berlin") if err != nil { - slog.Error("Failed to load location: ", err) + slog.Error("Failed to load location: ", "error", err) return time.Time{}, err } diff --git a/backend/service/db/dbEvents.go b/backend/service/db/dbEvents.go index 5edf53f..f458a58 100644 --- a/backend/service/db/dbEvents.go +++ b/backend/service/db/dbEvents.go @@ -200,7 +200,7 @@ func GetAllModulesForCourse(app *pocketbase.PocketBase, course string, semester // get all events from event records in the events collection err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("course = {:course} AND semester = {:semester}", dbx.Params{"course": course, "semester": semester})).GroupBy("Name").Distinct(true).All(&events) if err != nil { - slog.Error("Error while getting events from database: ", err) + slog.Error("Error while getting events from database: ", "error", err) return nil, fmt.Errorf("error while getting events from database for course %s and semester %s", course, semester) } @@ -212,7 +212,7 @@ func GetAllModulesDistinctByNameAndCourse(app *pocketbase.PocketBase) ([]model.M err := app.Dao().DB().Select("Name", "EventType", "Prof", "course", "semester", "uuid").From("events").GroupBy("Name", "Course").Distinct(true).All(&modules) if err != nil { - slog.Error("Error while getting events from database: ", err) + slog.Error("Error while getting events from database: ", "error", err) return nil, fmt.Errorf("error while getting events distinct by name and course from data") } diff --git a/backend/service/db/dbGroups.go b/backend/service/db/dbGroups.go index 1f51c15..1e5d02c 100644 --- a/backend/service/db/dbGroups.go +++ b/backend/service/db/dbGroups.go @@ -85,7 +85,7 @@ func GetAllCourses(app *pocketbase.PocketBase) []string { // get all rooms from event records in the events collection err := app.Dao().DB().Select("course").From("groups").All(&courses) if err != nil { - slog.Error("Error while getting groups from database: ", err) + slog.Error("Error while getting groups from database: ", "error", err) return []string{} } @@ -107,7 +107,7 @@ func GetAllCoursesForSemester(app *pocketbase.PocketBase, semester string) []str // get all courses for a specific semester err := app.Dao().DB().Select("course").From("groups").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).All(&courses) if err != nil { - slog.Error("Error while getting groups from database: ", err) + slog.Error("Error while getting groups from database: ", "error", err) return []string{} } @@ -130,7 +130,7 @@ func GetAllCoursesForSemesterWithEvents(app *pocketbase.PocketBase, semester str // get all courses from events distinct for a specific semester err := app.Dao().DB().Select("course").From("events").Where(dbx.NewExp("semester = {:semester}", dbx.Params{"semester": semester})).Distinct(true).All(&courses) if err != nil { - slog.Error("Error while getting groups from database: ", err) + slog.Error("Error while getting groups from database: ", "error", err) return nil, err } diff --git a/backend/service/feed/feedFunctions.go b/backend/service/feed/feedFunctions.go index 050c5e2..894f322 100644 --- a/backend/service/feed/feedFunctions.go +++ b/backend/service/feed/feedFunctions.go @@ -30,7 +30,7 @@ import ( func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) { feeds, err := database.GetAllFeeds(db) if err != nil { - slog.Error("CleanFeeds: failed to get all feeds", err) + slog.Error("CleanFeeds: failed to get all feeds", "error", err) return } for _, feed := range feeds { @@ -44,8 +44,8 @@ func ClearFeeds(db *daos.Dao, months int, clock localTime.Clock) { var sqlResult sql.Result sqlResult, err = db.DB().Delete("feeds", dbx.NewExp("id = {:id}", dbx.Params{"id": feed.GetId()})).Execute() if err != nil { - slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", err) - slog.Error("SQL Result: ", sqlResult) + slog.Error("CleanFeeds: delete feed "+feed.GetId()+" failed", "error", err) + slog.Error("SQL Result: ", "error", sqlResult) } else { slog.Info("CleanFeeds: delete feed " + feed.GetId() + " successful") } diff --git a/backend/service/fetch/sport/sportFetcher.go b/backend/service/fetch/sport/sportFetcher.go index f17f41c..b885d57 100644 --- a/backend/service/fetch/sport/sportFetcher.go +++ b/backend/service/fetch/sport/sportFetcher.go @@ -208,7 +208,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [ for _, day := range days { weekDay, err := getDayInt(day) if err != nil { - slog.Error("Error while getting day int: "+day+" ", err) + slog.Error("Error while getting day int: "+day+" ", "error", err) } else { weekEvents = append(weekEvents, model.SportDayStartEnd{ Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()), @@ -233,9 +233,10 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [ endI, endIErr = getDayInt(days[1]) if endIErr != nil || startIErr != nil { - slog.Error("Error while getting day int: "+days[0]+" - "+days[1]+" :", startIErr, endIErr) + slog.Error("Error while getting day int: "+days[0]+" - "+days[1]+" :", "error", startIErr) + slog.Error("Error while getting day int endErr: ", "error", endIErr) } else { - //create a int array with all days from start to end day + //create an int array with all days from start to end day var daysBetween []int for i := startI; i <= endI; i++ { daysBetween = append(daysBetween, i) @@ -258,7 +259,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [ dayInt, err := getDayInt(day) if err != nil { - slog.Error("Error while getting day int: "+day+" ", err) + slog.Error("Error while getting day int: "+day+" ", "error", err) } else { dayNumbers = append(dayNumbers, dayInt) } @@ -270,7 +271,7 @@ func getWeekEvents(start time.Time, end time.Time, cycle string) ([]time.Time, [ weekDay, err := getDayInt(day) if err != nil { - slog.Error("Error while getting day int: "+day+" ", err) + slog.Error("Error while getting day int: "+day+" ", "error", err) } else { weekEvents = append(weekEvents, model.SportDayStartEnd{ Start: time.Date(start.Year(), start.Month(), start.Day(), startHour, startMinute, 0, 0, start.Location()), @@ -377,7 +378,7 @@ func fetchAllAvailableSportCourses() ([]string, error) { var doc, err = htmlRequest(url) if err != nil { - slog.Error("Error while fetching sport courses from webpage", err) + slog.Error("Error while fetching sport courses from webpage", "error", err) return nil, err } @@ -442,7 +443,7 @@ func htmlRequest(url string) (*goquery.Document, error) { defer func(Body io.ReadCloser) { readErr := Body.Close() if readErr != nil { - slog.Error("Error while closing response body from html request", readErr) + slog.Error("Error while closing response body from html request", "error", readErr) return } }(resp.Body) diff --git a/backend/service/fetch/v1/fetchSeminarEventService.go b/backend/service/fetch/v1/fetchSeminarEventService.go index c592340..a2ffa6e 100644 --- a/backend/service/fetch/v1/fetchSeminarEventService.go +++ b/backend/service/fetch/v1/fetchSeminarEventService.go @@ -122,7 +122,7 @@ func parseSeminarGroup(result string) model.SeminarGroup { events, err = SplitEventType(events) if err != nil { - slog.Error("Error occurred while splitting event types: %s", err) + slog.Error("Error occurred while splitting event types: %s", "error", err) return model.SeminarGroup{} } diff --git a/backend/service/fetch/v1/fetchSeminarGroupService.go b/backend/service/fetch/v1/fetchSeminarGroupService.go index 45abf67..dc0fb58 100644 --- a/backend/service/fetch/v1/fetchSeminarGroupService.go +++ b/backend/service/fetch/v1/fetchSeminarGroupService.go @@ -62,14 +62,14 @@ func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) { resultSummer, err := getSeminarHTML("ss") if err != nil { - slog.Error("Error while fetching seminar groups for winter semester", err) + slog.Error("Error while fetching seminar groups for winter semester", "error", err) return nil, err } resultWinter, _ := getSeminarHTML("ws") if err != nil { - slog.Error("Error while fetching seminar groups for summer semester", err) + slog.Error("Error while fetching seminar groups for summer semester", "error", err) return nil, err } @@ -81,14 +81,14 @@ func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) { collection, dbError := db.FindCollection(app, "groups") if dbError != nil { - slog.Error("Error while searching collection groups", dbError) + slog.Error("Error while searching collection groups", "error", dbError) return nil, err } var insertedGroups []*models.Record insertedGroups, dbError = db.SaveGroups(groups, collection, app) if dbError != nil { - slog.Error("Error while saving groups", dbError) + slog.Error("Error while saving groups", "error", dbError) return nil, err } diff --git a/backend/service/fetch/v2/fetcher.go b/backend/service/fetch/v2/fetcher.go index 57a9aef..9800f95 100644 --- a/backend/service/fetch/v2/fetcher.go +++ b/backend/service/fetch/v2/fetcher.go @@ -73,7 +73,7 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([ for _, semester := range calculateSemesterList(clock) { events, fetchErr := fetchAndSaveAllEventsForSemester(app, semester, stubUrl) if fetchErr != nil { - return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", err) + return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", "error", err) } savedRecords = append(savedRecords, events...) } @@ -90,11 +90,11 @@ func fetchAndSaveAllEventsForSemester( url := stubUrl[0] + semester + stubUrl[1] events, err := parseEventForOneSemester(url) if err != nil { - return nil, fmt.Errorf("failed to parse events for "+semester+": %w", err) + return nil, fmt.Errorf("failed to parse events for "+semester+": %w", "error", err) } err = db.DeleteAllEventsBySemesterWithoutCourse(app, "Sport", semester) if err != nil { - return nil, fmt.Errorf("failed to delete all events for "+semester+": %w", err) + return nil, fmt.Errorf("failed to delete all events for "+semester+": %w", "error", err) } savedEvents, dbError := db.SaveEvents(events, app) if dbError != nil { @@ -160,7 +160,7 @@ func parseEventForOneSemester(url string) ([]model.Event, error) { events = convertWeeksToDates(events, semester, year) events, err = v1.SplitEventType(events) if err != nil { - slog.Error("Error occurred while splitting event types: %s", err) + slog.Error("Error occurred while splitting event types: %s", "error", err) return nil, err } events = switchNameAndNotesForExam(events) diff --git a/backend/service/functions/filter.go b/backend/service/functions/filter.go new file mode 100644 index 0000000..0cf0da6 --- /dev/null +++ b/backend/service/functions/filter.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 functions + +// function to filter an array +func Filter[T any](ss []T, test func(T) bool) (ret []T) { + for _, s := range ss { + if test(s) { + ret = append(ret, s) + } + } + return +} diff --git a/backend/service/functions/filter_test.go b/backend/service/functions/filter_test.go new file mode 100644 index 0000000..8dd7ac8 --- /dev/null +++ b/backend/service/functions/filter_test.go @@ -0,0 +1,103 @@ +//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" + "strings" + "testing" +) + +func Test_Filter_number(t *testing.T) { + type args struct { + ss []int + test func(int) bool + } + tests := []struct { + name string + args args + wantRet []int + }{ + { + "filter even numbers", + args{ + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + func(i int) bool { + return i%2 == 0 + }, + }, + []int{2, 4, 6, 8, 10}, + }, + { + "filter smaller than 5", + args{ + []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + func(i int) bool { + return i < 5 + }, + }, + []int{1, 2, 3, 4}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotRet := Filter(tt.args.ss, tt.args.test); !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("filter() = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} + +func Test_Filter_string(t *testing.T) { + type args struct { + ss []string + test func(string) bool + } + tests := []struct { + name string + args args + wantRet []string + }{ + { + "filter contains a", + args{ + []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}, + func(i string) bool { + return strings.Contains(i, "a") + }, + }, + []string{"a"}, + }, + { + "filter starts with prefix 'a'", + args{ + []string{"alpha", "beta", "a", "ab", "ac", "delta"}, + func(i string) bool { + return strings.HasPrefix(i, "a") + }, + }, + []string{"alpha", "a", "ab", "ac"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotRet := Filter(tt.args.ss, tt.args.test); !reflect.DeepEqual(gotRet, tt.wantRet) { + t.Errorf("filter() = %v, want %v", gotRet, tt.wantRet) + } + }) + } +} diff --git a/backend/service/functions/time/parse.go b/backend/service/functions/time/parse.go index 9a5fd93..f1aac1a 100644 --- a/backend/service/functions/time/parse.go +++ b/backend/service/functions/time/parse.go @@ -29,7 +29,7 @@ func ParseTime(timeString string) (time.Time, error) { func ParseAsTypesDatetime(time time.Time) types.DateTime { dateTime, err := types.ParseDateTime(time) if err != nil { - slog.Error("Failed to parse time as types.DateTime: %v", err) + slog.Error("Failed to parse time as types.DateTime: %v", "error", err) return types.DateTime{} } return dateTime diff --git a/backend/service/room/roomService.go b/backend/service/room/roomService.go index e8a18c5..2571711 100644 --- a/backend/service/room/roomService.go +++ b/backend/service/room/roomService.go @@ -17,13 +17,21 @@ package room import ( - "github.com/pocketbase/pocketbase" "htwkalender/model" "htwkalender/service/db" "htwkalender/service/functions" + "math" + "regexp" + "strings" "time" + + "github.com/pocketbase/pocketbase" + "go.mongodb.org/mongo-driver/bson/primitive" ) +// maximum number of blocks is around 6 months with 15 minute granularity (180 * 24 * 4 = 17280) +const MaxNumberOfBlocks = 1728000 + func GetRooms(app *pocketbase.PocketBase) ([]string, error) { rooms, err := db.GetRooms(app) if err != nil { @@ -51,7 +59,128 @@ func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to st return anonymizedRoomSchedule, nil } -// Transform the events to anonymized events throwing away all unnecessary information +/** + * Get the room occupancy for all rooms within a given time range + * @param app pocketbase instance + * @param from start time of the occupancy list + * @param to end time of the occupancy list + * @param granularity number of minutes for one block + * @return room occupancy list + * @return error if the database query fails + */ +func GetRoomOccupancyList(app *pocketbase.PocketBase, from string, to string, granularity int) (model.RoomOccupancyList, error) { + // try parsing the time strings + fromTime, err := time.Parse(time.RFC3339, from) + if err != nil { + return model.RoomOccupancyList{}, err + } + toTime, err := time.Parse(time.RFC3339, to) + if err != nil { + return model.RoomOccupancyList{}, err + } + + // calculate the number of blocks for the given time range and granularity + timeDifference := toTime.Sub(fromTime) + numberOfBlocks := int(math.Ceil(timeDifference.Minutes() / float64(granularity))) + + numberOfBlocks = min(numberOfBlocks, MaxNumberOfBlocks) + + roomOccupancyList := emptyRoomOccupancyList(fromTime, granularity, numberOfBlocks) + + // get all events within the time range + events, err := db.GetEventsThatCollideWithTimeRange(app, fromTime, toTime) + if err != nil { + return model.RoomOccupancyList{}, err + } + + rooms, err := getRelevantRooms(app) + if err != nil { + return model.RoomOccupancyList{}, err + } + + for _, room := range rooms { + // get the schedule for only this room + roomEvents := functions.Filter(events, func(event model.Event) bool { + return strings.Contains(event.Rooms, room) + }) + + // encode the room schedule binary and add it to the list + roomOccupancy, err := createRoomOccupancy(room, roomEvents, fromTime, granularity, numberOfBlocks) + if err != nil { + return model.RoomOccupancyList{}, err + } + roomOccupancyList.Rooms = append(roomOccupancyList.Rooms, roomOccupancy) + } + + return roomOccupancyList, nil +} + +/** + * Get all rooms from the database and filter them by a regex + * @param app pocketbase instance + * @return all rooms that match the regex + * @return error if the database query fails + */ +func getRelevantRooms(app *pocketbase.PocketBase) ([]string, error) { + // get all rooms + rooms, err := db.GetRooms(app) + if err != nil { + return nil, err + } + + // filter by regex for the room name + roomRegex := regexp.MustCompile(".*") + return functions.Filter(rooms, roomRegex.MatchString), nil +} + +/* + * Create an empty room occupancy list + * @param from start time of the occupancy list + * @param granularity number of minutes for one block + * @param blockCount number of blocks that can be either occupied or free + */ +func emptyRoomOccupancyList(from time.Time, granularity int, blockCount int) model.RoomOccupancyList { + return model.RoomOccupancyList{ + Start: from, + Granularity: granularity, + Blocks: blockCount, + Rooms: []model.RoomOccupancy{}, + } +} + +/*+ + * Create the room occupancy for a given room + * @param room room name + * @param events events that could block the room (or be free) + * @param start start time of the schedule + * @param granularity number of minutes for one block + * @param blockCount number of blocks + * @return room occupancy for the given room + * @return error if encoding the room schedule fails + */ +func createRoomOccupancy(room string, events []model.Event, start time.Time, granularity int, blockCount int) (model.RoomOccupancy, error) { + roomSchedule := anonymizeRooms(events) + occupancy, err := encodeRoomSchedule(roomSchedule, start, granularity, blockCount) + + if err != nil { + return model.RoomOccupancy{}, err + } + + return model.RoomOccupancy{ + Name: room, + Occupancy: primitive.Binary{ + Subtype: 0, + Data: occupancy, + }, + }, nil +} + +/* + * Transform the events to anonymized events throwing away all unnecessary information + * @param events events to be anonymized + * @see Event.AnonymizeEvent + * @return anonymized events + */ func anonymizeRooms(events []model.Event) []model.AnonymizedEventDTO { var anonymizedEvents []model.AnonymizedEventDTO for _, event := range events { @@ -102,3 +231,59 @@ func isRoomInSchedule(room string, schedule []model.Event) bool { } return false } + +/** + * Encode the room schedule to a byte array + * + * @param roomSchedule events that block the room + * @param start start time of the schedule + * @param granularity number of minutes for one block + * @param blockCount number of blocks + * + * @return byte array of the encoded room schedule + * @return error if encoding fails + */ +func encodeRoomSchedule(roomSchedule []model.AnonymizedEventDTO, start time.Time, granularity int, blockCount int) ([]byte, error) { + // Create empty occupancy array with blockCount bits + byteCount := int(math.Ceil(float64(blockCount) / 8)) + occupancy := make([]byte, byteCount) + + // Iterate over all events in the schedule + for _, event := range roomSchedule { + // skip if room is not occupied or end time is not after start time + if event.Free || !event.Start.Time().Before(event.End.Time()) { + continue + } + + // Calculate the start and end block of the event + startBlock := int( + math.Floor(event.Start.Time().Sub(start).Minutes() / float64(granularity)), + ) + endBlock := int( + math.Ceil(event.End.Time().Sub(start).Minutes() / float64(granularity)), + ) + + occupyBlocks(occupancy, startBlock, endBlock, blockCount) + } + + return occupancy, nil +} + +/** + * Set the bits of the occupancy array for the given block range + * to 1 + * + * @param occupancy byte array of the occupancy + * @param startBlock start block (bit defined by granularity) of the event + * @param endBlock end block of the event + * @param blockCount number of blocks (bits) in the occupancy array + */ +func occupyBlocks(occupancy []byte, startBlock int, endBlock int, blockCount int) { + lowerBound := max(0, min(startBlock, blockCount)) + upperBound := min(max(endBlock, lowerBound), blockCount) + + // Iterate over all blocks of the event + for i := lowerBound; i < upperBound; i++ { + occupancy[i/8] |= 1 << (7 - i%8) + } +} diff --git a/backend/service/room/roomService_test.go b/backend/service/room/roomService_test.go index 10a6b41..c9b8340 100644 --- a/backend/service/room/roomService_test.go +++ b/backend/service/room/roomService_test.go @@ -17,10 +17,12 @@ package room import ( - "github.com/pocketbase/pocketbase/tools/types" "htwkalender/model" "reflect" "testing" + "time" + + "github.com/pocketbase/pocketbase/tools/types" ) func Test_anonymizeRooms(t *testing.T) { @@ -305,3 +307,275 @@ func Test_getFreeRooms(t *testing.T) { }) } } + +func Test_encodeRoomSchedule(t *testing.T) { + testTime, _ := time.Parse(time.RFC3339, "2024-12-24T12:00:00Z") + testDateTime, _ := types.ParseDateTime(testTime) + testDateTime_m15, _ := types.ParseDateTime(testTime.Add(-time.Minute * 15)) + testDateTime_p10, _ := types.ParseDateTime(testTime.Add(time.Minute * 10)) + testDateTime_p15, _ := types.ParseDateTime(testTime.Add(time.Minute * 15)) + testDateTime_p30, _ := types.ParseDateTime(testTime.Add(time.Minute * 30)) + testDateTime_p45, _ := types.ParseDateTime(testTime.Add(time.Minute * 45)) + testDateTime_late, _ := types.ParseDateTime(testTime.Add(time.Hour * 100)) + + type args struct { + roomSchedule []model.AnonymizedEventDTO + start time.Time + granularity int + blockCount int + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "encode event without length", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p10, + End: testDateTime_p10, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0x00, + }, + wantErr: false, + }, + { + name: "ignore event with start time after end time", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p30, + End: testDateTime_p10, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0x00, + }, + wantErr: false, + }, + { + name: "encode time table without length", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime, + End: testDateTime_p10, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 0, + }, + want: []byte{}, + wantErr: false, + }, + { + name: "encode time table without events", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{}, + start: testTime, + granularity: 15, + blockCount: 24, + }, + want: []byte{ + 0x00, 0x00, 0x00, + }, + wantErr: false, + }, + { + name: "encode time table with single event", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p30, + End: testDateTime_late, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 30, + blockCount: 50, + }, + want: []byte{ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + }, + }, + { + name: "ignore free event", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p15, + End: testDateTime_p45, + Rooms: "Room", + Free: false, + }, + { + Day: "Montag", + Week: "52", + Start: testDateTime, + End: testDateTime_p30, + Rooms: "Room", + Free: true, + }, + }, + start: testTime, + granularity: 15, + blockCount: 50, + }, + want: []byte{ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + { + name: "encode time table with multiple events", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime, + End: testDateTime_p15, + Rooms: "Room", + Free: false, + }, + { + Day: "Montag", + Week: "52", + Start: testDateTime_p30, + End: testDateTime_p45, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0xA0, + }, + }, + { + name: "encode time table with multiple unordered events", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p30, + End: testDateTime_p45, + Rooms: "Room", + Free: false, + }, + { + Day: "Montag", + Week: "52", + Start: testDateTime, + End: testDateTime_p15, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0xA0, + }, + }, + { + name: "encode time table with overlapping events", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_p15, + End: testDateTime_p30, + Rooms: "Room", + Free: false, + }, + { + Day: "Montag", + Week: "52", + Start: testDateTime, + End: testDateTime_p45, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0xE0, + }, + }, + { + name: "consider events starting before the start time", + args: args{ + roomSchedule: []model.AnonymizedEventDTO{ + { + Day: "Montag", + Week: "52", + Start: testDateTime_m15, + End: testDateTime_p15, + Rooms: "Room", + Free: false, + }, + }, + start: testTime, + granularity: 15, + blockCount: 4, + }, + want: []byte{ + 0x80, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := encodeRoomSchedule(tt.args.roomSchedule, tt.args.start, tt.args.granularity, tt.args.blockCount) + if (err != nil) != tt.wantErr { + t.Errorf("encodeRoomSchedule() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("encodeRoomSchedule() = %v, want %v", got, tt.want) + } + }) + } +}