diff --git a/.gitea/workflows/pr-checks.yml b/.gitea/workflows/pr-checks.yml
index af8671d..2f06423 100644
--- a/.gitea/workflows/pr-checks.yml
+++ b/.gitea/workflows/pr-checks.yml
@@ -6,25 +6,6 @@ on:
- main
jobs:
- security-scan:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Install Trivy
- run: |
- curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
-
- - name: Run Trivy filesystem scan (full repo)
- run: |
- trivy fs \
- --scanners vuln,secret,misconfig \
- --severity HIGH,CRITICAL \
- --exit-code 1 \
- --format table \
- .
-
test-and-build:
runs-on: ubuntu-latest
steps:
@@ -39,7 +20,50 @@ jobs:
- name: Restore dependencies
run: dotnet restore
- - name: Build Shared (includes SAST via SecurityCodeScan)
+ - name: Verify Trivy dependency scan inputs
+ run: |
+ lock_count="$(find . -name packages.lock.json -not -path "*/bin/*" -not -path "*/obj/*" | tee trivy-targets.txt | wc -l)"
+ echo "Trivy NuGet lock files: ${lock_count}"
+ if [ "${lock_count}" -eq 0 ]; then
+ echo "::error::No packages.lock.json files found. Trivy would scan 0 NuGet dependency files."
+ exit 1
+ fi
+
+ # ── Linting ──
+
+ - name: Lint C# code style
+ run: dotnet format --verify-no-changes --verbosity diagnostic
+
+ # ── Security ──
+
+ - name: Check NuGet packages for vulnerabilities
+ run: |
+ dotnet list package --vulnerable --include-transitive 2>&1 | tee nuget-audit.txt
+ if grep -qi "has the following vulnerable packages" nuget-audit.txt; then
+ echo "::error::Vulnerable NuGet packages found!"
+ exit 1
+ fi
+ echo "No vulnerable packages detected."
+
+ - name: Install Trivy
+ run: |
+ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
+ trivy --version
+
+ - name: Trivy filesystem security scan
+ run: |
+ set +e
+ trivy fs --scanners vuln,misconfig,secret --exit-code 1 --severity HIGH,CRITICAL . 2>&1 | tee trivy-scan.log
+ trivy_exit="${PIPESTATUS[0]}"
+ if ! grep -Eq "Number of language-specific files[[:space:]]+num=[1-9][0-9]*" trivy-scan.log; then
+ echo "::error::Trivy did not detect any language-specific dependency files."
+ exit 1
+ fi
+ exit "${trivy_exit}"
+
+ # ── Build (includes SAST via SecurityCodeScan Roslyn analyzer) ──
+
+ - name: Build Shared
run: dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore
- name: Build Bot (compile check, includes SAST)
@@ -48,5 +72,7 @@ jobs:
- name: Build Web (compile check, includes SAST)
run: dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore
+ # ── Tests ──
+
- name: Run tests
- run: dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal
\ No newline at end of file
+ run: dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal
diff --git a/Directory.Build.props b/Directory.Build.props
index abbd5a4..7a5fd28 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,6 +6,7 @@
enable
enable
true
+ true
diff --git a/README.md b/README.md
index 955faa4..f9ada98 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
-**Текущая версия:** `v1.10.2`.
+**Текущая версия:** `v1.14.0`.
---
@@ -16,7 +16,7 @@
- **⚡ Быстрые повторы расписания**: Для регулярной кампании можно указать одну дату, количество игр и интервал, а бот сам развернёт повторяющийся batch.
- **✋ Интерактивная запись и выход**: Игроки записываются на конкретные даты и самостоятельно снимают запись нажатием одной кнопки.
- **👥 Лимит мест и лист ожидания**: ГМ задаёт максимальный состав, бот не переполняет сессию, автоматически ведёт очередь ожидания и освобождённое место отдаёт первому ожидающему.
-- **📁 Поддержка Форумов (Telegram Topics)**: Бот автоматически создает тему во вложенных чатах Telegram под каждую новую пачку игр.
+- **📁 Поддержка Форумов (Telegram Topics)**: Если `/newsession` запущен в теме форума Telegram, расписание и групповые уведомления остаются в этой теме; при запуске из корня форума бот создает отдельную тему и сообщает о необходимости прав admin/Manage Topics, если их не хватает.
- **❌ Управление сессиями**: Owner и назначенные co-GM могут создавать, отменять, удалять и переносить игры из Telegram через `/listsessions`; публичный пост записи показывает только кнопки игроков.
- **🔄 Голосование за перенос**: Быстрый поиск свободного места с через свободное недель и кнопками новых времени и дедлайном.
- **🔔 Уведомления**: Игрок получают за 24 часа, напоминание за 1 час, ссылку перед игрой, отмены и переносы; групповые уведомления при этом остаются.
diff --git a/src/GmRelay.AppHost/packages.lock.json b/src/GmRelay.AppHost/packages.lock.json
new file mode 100644
index 0000000..8e0fb8f
--- /dev/null
+++ b/src/GmRelay.AppHost/packages.lock.json
@@ -0,0 +1,681 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Aspire.Dashboard.Sdk.win-x64": {
+ "type": "Direct",
+ "requested": "[13.2.1, )",
+ "resolved": "13.2.1",
+ "contentHash": "KLB9rXwY8kg2taWwxsJFoK0cAuupSZurcv1zTyYMqLyNuwvYYjs65Yz3g/cgh22QlUfOT3tOh+Jzk5MdJhy5+w=="
+ },
+ "Aspire.Hosting.AppHost": {
+ "type": "Direct",
+ "requested": "[13.2.1, )",
+ "resolved": "13.2.1",
+ "contentHash": "4B/eoZPwOobxpMpvYnqe/EcXabjPhZJhfxlHXv5gdKd16duoWbHnvvAZJsVI3WUpakCwmsCiTrT4sNGfW8H+IQ==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.Uris": "9.0.0",
+ "Aspire.Hosting": "13.2.1",
+ "Google.Protobuf": "3.33.5",
+ "Grpc.AspNetCore": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.78.0",
+ "Humanizer.Core": "2.14.1",
+ "JsonPatch.Net": "3.3.0",
+ "KubernetesClient": "18.0.13",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "10.0.5",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Hosting": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Http": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5",
+ "ModelContextProtocol": "1.0.0",
+ "Newtonsoft.Json": "13.0.4",
+ "Polly.Core": "8.6.5",
+ "Semver": "3.0.0",
+ "StreamJsonRpc": "2.22.23",
+ "System.IO.Hashing": "10.0.3"
+ }
+ },
+ "Aspire.Hosting.Orchestration.win-x64": {
+ "type": "Direct",
+ "requested": "[13.2.1, )",
+ "resolved": "13.2.1",
+ "contentHash": "39lRUH4WuCsBaYB7fZH1/r81SSJIXrA8WphBlAdP1QT95+1sKQHzXJuXU4nzKpBLv4oZmjcWzvA+FDMGZbWmkw=="
+ },
+ "Aspire.Hosting.PostgreSQL": {
+ "type": "Direct",
+ "requested": "[13.2.1, )",
+ "resolved": "13.2.1",
+ "contentHash": "7F/nmeplR9cYE/B/E1haRjnkoBRQ/voMXpnK/SNJoXSFs4Vb/g00CDDvI/xfH3SAV7Xq8ekWa9ZbX56JuQ+YiA==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.NpgSql": "9.0.0",
+ "AspNetCore.HealthChecks.Uris": "9.0.0",
+ "Aspire.Hosting": "13.2.1",
+ "Google.Protobuf": "3.33.5",
+ "Grpc.AspNetCore": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.78.0",
+ "Humanizer.Core": "2.14.1",
+ "JsonPatch.Net": "3.3.0",
+ "KubernetesClient": "18.0.13",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.25",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Hosting": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Http": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5",
+ "ModelContextProtocol": "1.0.0",
+ "Newtonsoft.Json": "13.0.4",
+ "Polly.Core": "8.6.5",
+ "Semver": "3.0.0",
+ "StreamJsonRpc": "2.22.23",
+ "System.IO.Hashing": "10.0.3"
+ }
+ },
+ "Aspire.Hosting": {
+ "type": "Transitive",
+ "resolved": "13.2.1",
+ "contentHash": "GY/T5iK2F4K3Sk60VUeVnTX1MhCjSaX48+qPUjA/rI1x1ONHevHzFj+Gc3fNlGEaZGY8L87hSxwGrV+Bjd5EJw==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.Uris": "9.0.0",
+ "Google.Protobuf": "3.33.5",
+ "Grpc.AspNetCore": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.78.0",
+ "Humanizer.Core": "2.14.1",
+ "JsonPatch.Net": "3.3.0",
+ "KubernetesClient": "18.0.13",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.25",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Hosting": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Http": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5",
+ "ModelContextProtocol": "1.0.0",
+ "Newtonsoft.Json": "13.0.4",
+ "Polly.Core": "8.6.5",
+ "Semver": "3.0.0",
+ "StreamJsonRpc": "2.22.23",
+ "System.IO.Hashing": "10.0.3"
+ }
+ },
+ "AspNetCore.HealthChecks.NpgSql": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "npc58/AD5zuVxERdhCl2Kb7WnL37mwX42SJcXIwvmEig0/dugOLg3SIwtfvvh3TnvTwR/sk5LYNkkPaBdks61A==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.11",
+ "Npgsql": "8.0.3"
+ }
+ },
+ "AspNetCore.HealthChecks.Uris": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "XYdNlA437KeF8p9qOpZFyNqAN+c0FXt/JjTvzH/Qans0q0O3pPE8KPnn39ucQQjR/Roum1vLTP3kXiUs8VHyuA==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.11",
+ "Microsoft.Extensions.Http": "8.0.0"
+ }
+ },
+ "Fractions": {
+ "type": "Transitive",
+ "resolved": "7.3.0",
+ "contentHash": "2bETFWLBc8b7Ut2SVi+bxhGVwiSpknHYGBh2PADyGWONLkTxT7bKyDRhF8ao+XUv90tq8Fl7GTPxSI5bacIRJw=="
+ },
+ "Google.Protobuf": {
+ "type": "Transitive",
+ "resolved": "3.33.5",
+ "contentHash": "XEzLpCTosZb5I6eGSPn7rAES0VfkJkn3Cqydh0W39POdZwkdhPhOmAROTFJF9g0ardst4ulNXRm/q/iXwNu+Qw=="
+ },
+ "Grpc.AspNetCore": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "LyXMmpN2Ba0TE35SOLSKbGqIYtJuhc1UgiaGfoW1X8KJERV70QI5KGW+ckEY7MrXoFWN/uWo4B70siVhbDmCgQ==",
+ "dependencies": {
+ "Google.Protobuf": "3.31.1",
+ "Grpc.AspNetCore.Server.ClientFactory": "2.76.0",
+ "Grpc.Tools": "2.76.0"
+ }
+ },
+ "Grpc.AspNetCore.Server": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "diSC/ZeNdSdxHdYSOpYwuSBBDYpuNVtJQFJfiBB0WrYOQ4lVMmdxuUZJcViahQyo8pCvS3Mueo5lqFxwwMF/iw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0"
+ }
+ },
+ "Grpc.AspNetCore.Server.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "y5KGO1GO0N2L/hCCMR05mmoK8j+v8rKvZ+9nothAxKx2Tf2CwV8f4TM5K0GkKfDsp4vrc4lm90MU6E+DeN7YIw==",
+ "dependencies": {
+ "Grpc.AspNetCore.Server": "2.76.0",
+ "Grpc.Net.ClientFactory": "2.76.0"
+ }
+ },
+ "Grpc.Core.Api": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "cSxC2tdnFdXXuBgIn1pjc4YBx7LXTCp4M0qn+SMBS35VWZY+cEQYLWTBDDhdBH1HzU7BV+ncVZlniGQHMpRJKQ=="
+ },
+ "Grpc.Net.Client": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "K1oldmqw2+Gn69nGRzZLhqSiUZwelX1GrBu/cUl9wNf1C0uB61vFS6JcxUUv9P8VoUJhFsmV44JA6lI2EUt4xw==",
+ "dependencies": {
+ "Grpc.Net.Common": "2.76.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "Grpc.Net.ClientFactory": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "XI+kO69L9AV8B9N0UQOmH911r6MOEp9huHiavEsY56DJYuzJ9KAxNGy37dpV6CLbgCaN2uKmpOsZ9Pao6bmpVQ==",
+ "dependencies": {
+ "Grpc.Net.Client": "2.76.0",
+ "Microsoft.Extensions.Http": "8.0.0"
+ }
+ },
+ "Grpc.Net.Common": {
+ "type": "Transitive",
+ "resolved": "2.76.0",
+ "contentHash": "bZpiMVYgvpB44/wBh1RotrkqC7bg2FOasLri2GhR3hMKyzsiTxCoDE49YjPrJeFc4RW0wS8u+EInI09sjxVFRA==",
+ "dependencies": {
+ "Grpc.Core.Api": "2.76.0"
+ }
+ },
+ "Grpc.Tools": {
+ "type": "Transitive",
+ "resolved": "2.78.0",
+ "contentHash": "6jPG2gHon+w2PczW8jjrCRnW/g9eEfCdd7aK6mDooptWtuPsV3ZxAwKKEx7LGEDVoT4c2SViRl8Yu3L1XiWIIg=="
+ },
+ "Humanizer.Core": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
+ },
+ "Json.More.Net": {
+ "type": "Transitive",
+ "resolved": "2.1.0",
+ "contentHash": "qtwsyAsL55y2vB2/sK4Pjg3ZyVzD5KKSpV3lOAMHlnjFfsjQ/86eHJfQT9aV1YysVXzF4+xyHOZbh7Iu3YQ7Lg=="
+ },
+ "JsonPatch.Net": {
+ "type": "Transitive",
+ "resolved": "3.3.0",
+ "contentHash": "GIcMMDtzfzVfIpQgey8w7dhzcw6jG5nD4DDAdQCTmHfblkCvN7mI8K03to8YyUhKMl4PTR6D6nLSvWmyOGFNTg==",
+ "dependencies": {
+ "JsonPointer.Net": "5.2.0"
+ }
+ },
+ "JsonPointer.Net": {
+ "type": "Transitive",
+ "resolved": "5.2.0",
+ "contentHash": "qe1F7Tr/p4mgwLPU9P60MbYkp+xnL2uCPnWXGgzfR/AZCunAZIC0RZ32dLGJJEhSuLEfm0YF/1R3u5C7mEVq+w==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Json.More.Net": "2.1.0"
+ }
+ },
+ "KubernetesClient": {
+ "type": "Transitive",
+ "resolved": "18.0.13",
+ "contentHash": "X5IuxmydftB148XeULtc7rD5/RvqLuW5SzkIjFovPgJpvV4RAoRqNPruVB7GEFu1Xg+zHVIk88WqdV8JjbgHbA==",
+ "dependencies": {
+ "Fractions": "7.3.0",
+ "YamlDotNet": "16.3.0"
+ }
+ },
+ "MessagePack": {
+ "type": "Transitive",
+ "resolved": "2.5.192",
+ "contentHash": "Jtle5MaFeIFkdXtxQeL9Tu2Y3HsAQGoSntOzrn6Br/jrl6c8QmG22GEioT5HBtZJR0zw0s46OnKU8ei2M3QifA==",
+ "dependencies": {
+ "MessagePack.Annotations": "2.5.192",
+ "Microsoft.NET.StringTools": "17.6.3"
+ }
+ },
+ "MessagePack.Annotations": {
+ "type": "Transitive",
+ "resolved": "2.5.192",
+ "contentHash": "jaJuwcgovWIZ8Zysdyf3b7b34/BrADw4v82GaEZymUhDd3ScMPrYd/cttekeDteJJPXseJxp04yTIcxiVUjTWg=="
+ },
+ "Microsoft.Extensions.AI.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.3.0",
+ "contentHash": "hDjDvUERvUH3HBMs2MDusOcGJBjAHOG5pJIU2x/HZEa4e1UthNKt89cwMi3B+ogJo6skki1XFjfgGN3ksnVqvQ=="
+ },
+ "Microsoft.Extensions.Caching.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.3",
+ "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.3"
+ }
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "8Rx5sqg04FttxrumyG6bmoRuFRgYzK6IVwF1i0/o0cXfKBdDeVpJejKHtJCMjyg9E/DNMVqpqOGe/tCT5gYvVA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "P09QpTHjqHmCLQOTC+WyLkoRNxek4NIvfWt+TnU0etoDUSRxcltyd6+j/ouRbMdLR0j44GqGO+lhI2M4fAHG4g==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "99Z4rjyXopb1MIazDSPcvwYCUdYNO01Cf1GUs2WUjIFAbkGmwzj2vPa2k+3pheJRV+YgNd2QqRKHAri0oBAU4Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "or9fOLopMUTJOQVJ3bou4aD6PwvsiKf4kZC4EE5sRRKSkmh+wfk/LekJXRjAX88X+1JA9zHjDo+5fiQ7z3MY/A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "tchMGQ+zVTO40np/Zzg2Li/TIR8bksQgg4UVXZa0OzeFCKWnIYtxE2FVs+eSmjPGCjMS2voZbwN/mUcYfpSTuA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "OhTr0O79dP49734lLTqVveivVX9sDXxbI/8vjELAZTHXqoN90mdpgTAgwicJED42iaHMCcZcK6Bj+8wNyBikaw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "brBM/WP0YAUYh2+QqSYVdK8eQHYQTtTEUJXJ+84Zkdo2buGLja9VSrMIhgoeBUU7JBmcskAib8Lb/N83bvxgYQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "fhdG6UV9lIp70QhNkVyaHciUVq25IPFkczheVJL9bIFvmnJ+Zghaie6dWkDbbVmxZlHl9gj3zTDxMxJs5zNhIA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "v1SVsowG6YE1YnHVGmLWz57YTRCQRx9pH5ebIESXfm5isI9gA3QaMyg/oMTzPpXYZwSAVDzYItGJKfmV+pqXkQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "iVMtq9eRvzyhx8949EGT0OCYJfXi737SbRVzWXE5GrOgGj5AaZ9eUuxA/BSUfmOMALKn/g8KfFaNQw0eiB3lyA=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vAJHd4yOpmKoK+jBuYV7a3y+Ab9U4ARCc29b6qvMy276RgJFw9LFs0DdsPqOL3ahwzyrX7tM+i4cCxU/RX0qAg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/nYGrpa9/0BZofrVpBbbj+Ns8ZesiPE0V/KxsuHgDgHQopIzN54nRaQGSuvPw16/kI9sW1Zox5yyAPqvf0Jz6A==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "REdt95QXHscGdtw/UUgyCW2lF9DJcAOJxmebKW2IkgUjuCAdMODIi2HNOWg5utW98nm8ekgV0Gjqs/sljwwqMw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "NrIMTy7dpqxAvA6kHAYH8cXID/YgeNOy0OqFKpLtkPu5X4WS/basX91UszANzVrMNRAICJ2GOnGiRxJtsRyEQw=="
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "nCBmCx0Xemlu65ZiWMcXbvfvtznKxf4/YYKF9R28QkqdI9lTikedGqzJ28/xmdGGsxUnsP5/3TQGpiPwVjK0dA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "dMu5kUPSfol1Rqhmr6nWPSmbFjDe9w6bkoKithG17bWTZA0UyKirTatM5mqYUN3mGpNA0MorlusIoVTh6J7o5g==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "mOE3ARusNQR0a5x8YOcnUbfyyXGqoAWQtEc7qFOfNJgruDWQLo39Re+3/Lzj5pLPFuFYj8hN4dgKzaSQDKiOCw=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "8i7e5IBdiKLNqt/+ciWrS8U95Rv5DClaaj7ulkZbimnCi4uREWd+lXzkp3joofFuIPOlAzV4AckxLTIELv2jdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.5",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Logging.Console": "10.0.5",
+ "Microsoft.Extensions.Logging.Debug": "10.0.5",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.5",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+Wb7KAMVZTomwJkQrjuPTe5KBzGod7N8XeG+ScxRlkPOB4sZLG4ccVwjV4Phk5BCJt7uIMnGHVoN6ZMVploX+g==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "AiFvHYM8nP0wPC7bGPI3NHQlSYSLqjjT7DMJUuuxhd+7pz3O89iu2gdQfgACy5DxsXENiok5i1bMacJL7KR8jA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+XTMKQyDWg4ODoNHU/BN3BaI1jhGO7VCS+BnzT/4IauiG6y2iPAte7MyD7rHKS+hNP0TkFkjrae8DFjDUxtcxg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "9HOdqlDtPptVcmKAjsQ/Nr5Rxfq6FMYLdhvZh1lVmeKR738qeYecQD7+ldooXf+u2KzzR1kafSphWngIM3C6ug==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "cSgxsDgfP0+gmVRPVoNHI/KIDavIZxh+CxE6tSLPlYTogqccDnjBFI9CgEsiNuMP6+fiuXUwhhlTz36uUEpwbQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "PMs2gha2v24hvH5o5KQem5aNK4mN0BhhCWlMqsg9tzifWKzjeQi2tyPOP/RaWMVvalOhVLcrmoMYPqbnia/epg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/VacEkBQ02A8PBXSa6YpbIXCuisYy6JJr62/+ANJDZE+RMBfZMcXJXLfr/LpyLE6pgdp17Wxlt7e7R9zvkwZ3Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "0ezhWYJS4/6KrqQel9JL+Tr4n+4EX2TF5EYiaysBWNNEM2c3Gtj1moD39esfgk8OHblSX+UFjtZ3z0c4i9tRvw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "System.Diagnostics.EventLog": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vN+aq1hBFXyYvY5Ow9WyeR66drKQxRZmas4lAjh6QWfryPkjTn1uLtX5AFIxyDaZj78v5TG2sELUyvrXpAPQQw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "MDaQMdUplw0AIRhWWmbLA7yQEXaLIHb+9CTroTiNS8OlI0LMXS4LCxtopqauiqGCWlRgJ+xyraVD8t6veRAFbw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "BB9uUW3+6Rxu1R97OB1H/13lUF8P2+H1+eDhpZlK30kDh/6E4EKHBUqTp+ilXQmZLzsRErxON8aBSR6WpUKJdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/HUHJ0tw/LQvD0DZrz50eQy/3z7PfX7WWEaXnjKTV9/TNdcgFlNTZGo49QhS7PTmhDqMyHRMqAXSBxLh0vso4g=="
+ },
+ "Microsoft.NET.StringTools": {
+ "type": "Transitive",
+ "resolved": "17.6.3",
+ "contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA=="
+ },
+ "Microsoft.VisualStudio.Threading.Only": {
+ "type": "Transitive",
+ "resolved": "17.13.61",
+ "contentHash": "vl5a2URJYCO5m+aZZtNlAXAMz28e2pUotRuoHD7RnCWOCeoyd8hWp5ZBaLNYq4iEj2oeJx5ZxiSboAjVmB20Qg==",
+ "dependencies": {
+ "Microsoft.VisualStudio.Validation": "17.8.8"
+ }
+ },
+ "Microsoft.VisualStudio.Validation": {
+ "type": "Transitive",
+ "resolved": "17.8.8",
+ "contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g=="
+ },
+ "ModelContextProtocol": {
+ "type": "Transitive",
+ "resolved": "1.0.0",
+ "contentHash": "W7UX8AQ1qMjXyCDcpP25u/L1W2vIIgfhLX/B2ZtTU1VUyILXdmVbdRjkQesKVPT/wPMpYXIHUcZJTPdsGfKSfQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Caching.Abstractions": "10.0.3",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.3",
+ "ModelContextProtocol.Core": "1.0.0"
+ }
+ },
+ "ModelContextProtocol.Core": {
+ "type": "Transitive",
+ "resolved": "1.0.0",
+ "contentHash": "QKboiQEq2MJMGeQ029Gy6xqge88abm0Px9lnG7hueOyf+EDCxi5SUATV+Df7GwT+NwWzkEsYG271bUQD+LGhEg==",
+ "dependencies": {
+ "Microsoft.Extensions.AI.Abstractions": "10.3.0",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.3"
+ }
+ },
+ "Nerdbank.Streams": {
+ "type": "Transitive",
+ "resolved": "2.12.87",
+ "contentHash": "oDKOeKZ865I5X8qmU3IXMyrAnssYEiYWTobPGdrqubN3RtTzEHIv+D6fwhdcfrdhPJzHjCkK/ORztR/IsnmA6g==",
+ "dependencies": {
+ "Microsoft.VisualStudio.Threading.Only": "17.13.61",
+ "Microsoft.VisualStudio.Validation": "17.8.8"
+ }
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.4",
+ "contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A=="
+ },
+ "Npgsql": {
+ "type": "Transitive",
+ "resolved": "8.0.3",
+ "contentHash": "6WEmzsQJCZAlUG1pThKg/RmeF6V+I0DmBBBE/8YzpRtEzhyZzKcK7ulMANDm5CkxrALBEC8H+5plxHWtIL7xnA==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.6.5",
+ "contentHash": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg=="
+ },
+ "Semver": {
+ "type": "Transitive",
+ "resolved": "3.0.0",
+ "contentHash": "9jZCicsVgTebqkAujRWtC9J1A5EQVlu0TVKHcgoCuv345ve5DYf4D1MjhKEnQjdRZo6x/vdv6QQrYFs7ilGzLA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "5.0.1"
+ }
+ },
+ "StreamJsonRpc": {
+ "type": "Transitive",
+ "resolved": "2.22.23",
+ "contentHash": "Ahq6uUFPnU9alny5h4agyX74th3PRq3NQCRNaDOqWcx20WT06mH/wENSk5IbHDc8BmfreQVEIBx5IXLBbsLFIA==",
+ "dependencies": {
+ "MessagePack": "2.5.192",
+ "Microsoft.VisualStudio.Threading.Only": "17.13.61",
+ "Microsoft.VisualStudio.Validation": "17.8.8",
+ "Nerdbank.Streams": "2.12.87",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "wugvy+pBVzjQEnRs9wMTWwoaeNFX3hsaHeVHFDIvJSWXp7wfmNWu3mxAwBIE6pyW+g6+rHa1Of5fTzb0QVqUTA=="
+ },
+ "System.IO.Hashing": {
+ "type": "Transitive",
+ "resolved": "10.0.3",
+ "contentHash": "La6ICwsdTKhVX+LKN+pvFjQRR3LhLwq3uKdi2knjLzRyPYBSydF4cjXidYxIiTcDD6XVYdsBWQEI8ZxiZ/OdIg=="
+ },
+ "YamlDotNet": {
+ "type": "Transitive",
+ "resolved": "16.3.0",
+ "contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA=="
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GmRelay.Bot/Dockerfile b/src/GmRelay.Bot/Dockerfile
index 820392c..fe2b961 100644
--- a/src/GmRelay.Bot/Dockerfile
+++ b/src/GmRelay.Bot/Dockerfile
@@ -33,5 +33,7 @@ WORKDIR /app
# Копируем только AOT-результаты из билда
COPY --from=build /app/publish .
+USER $APP_UID
+
# Запуск скомпилированного AOT бинарного файла напрямую
ENTRYPOINT ["./GmRelay.Bot"]
diff --git a/src/GmRelay.Bot/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs b/src/GmRelay.Bot/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs
index 7f231c5..987fd91 100644
--- a/src/GmRelay.Bot/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs
+++ b/src/GmRelay.Bot/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs
@@ -21,7 +21,8 @@ internal sealed record SessionContext(
DateTime ScheduledAt,
string Status,
long GmTelegramId,
- long TelegramChatId);
+ long TelegramChatId,
+ int? ThreadId);
internal sealed record ParticipantRsvp(
long TelegramId,
@@ -95,7 +96,8 @@ public sealed class HandleRsvpHandler(
s.scheduled_at AS ScheduledAt,
s.status AS Status,
g.gm_telegram_id AS GmTelegramId,
- g.telegram_chat_id AS TelegramChatId
+ g.telegram_chat_id AS TelegramChatId,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
WHERE s.id = @SessionId
@@ -191,6 +193,7 @@ public sealed class HandleRsvpHandler(
{
await bot.SendMessage(
chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
text: $"🎉 Игра «{session.Title}» подтверждена! Все участники на месте.",
cancellationToken: ct);
}
diff --git a/src/GmRelay.Bot/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs b/src/GmRelay.Bot/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs
index 1a4f404..59a664d 100644
--- a/src/GmRelay.Bot/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs
+++ b/src/GmRelay.Bot/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs
@@ -15,6 +15,7 @@ internal sealed record SessionInfo(
DateTime ScheduledAt,
Guid GroupId,
long TelegramChatId,
+ int? ThreadId,
string NotificationMode);
internal sealed record ParticipantInfo(
@@ -43,6 +44,7 @@ public sealed class SendConfirmationHandler(
"""
SELECT s.id, s.title, s.scheduled_at AS ScheduledAt, s.group_id AS GroupId,
g.telegram_chat_id AS TelegramChatId,
+ s.thread_id AS ThreadId,
s.notification_mode AS NotificationMode
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
@@ -99,6 +101,7 @@ public sealed class SendConfirmationHandler(
// 4. Send to group
var message = await bot.SendMessage(
chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
text: text,
replyMarkup: keyboard,
cancellationToken: ct);
diff --git a/src/GmRelay.Bot/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs b/src/GmRelay.Bot/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs
index 261b041..593ce60 100644
--- a/src/GmRelay.Bot/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs
+++ b/src/GmRelay.Bot/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs
@@ -14,6 +14,7 @@ internal sealed record JoinLinkSession(
string JoinLink,
DateTime ScheduledAt,
long TelegramChatId,
+ int? ThreadId,
string NotificationMode);
internal sealed record ConfirmedPlayer(
@@ -42,6 +43,7 @@ public sealed class SendJoinLinkHandler(
"""
SELECT s.id, s.title, s.join_link AS JoinLink, s.scheduled_at AS ScheduledAt,
g.telegram_chat_id AS TelegramChatId,
+ s.thread_id AS ThreadId,
s.notification_mode AS NotificationMode
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
@@ -94,6 +96,7 @@ public sealed class SendJoinLinkHandler(
// 4. Send
var message = await bot.SendMessage(
chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
text: text,
cancellationToken: ct);
diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs
index 9d3e428..16af575 100644
--- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs
+++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs
@@ -14,6 +14,7 @@ public sealed record CancelSessionCommand(
long TelegramUserId,
string CallbackQueryId,
long ChatId,
+ int? MessageThreadId,
int MessageId);
// DTOs for AOT compilation
@@ -29,7 +30,7 @@ public sealed class CancelSessionHandler(
{
await using var connection = await dataSource.OpenConnectionAsync(ct);
await using var transaction = await connection.BeginTransactionAsync(ct);
-
+
// 1. Проверяем, что запрос делает управляющий данной группы.
var session = await connection.QuerySingleOrDefaultAsync(
"""
@@ -117,9 +118,14 @@ public sealed class CancelSessionHandler(
ct);
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия отменена!", cancellationToken: ct);
-
+
// Опционально: написать отдельное сообщение в чат
- await bot.SendMessage(command.ChatId, $"❌ Внимание! Сессия \"{System.Net.WebUtility.HtmlEncode(session.Title)}\" отменена.", parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, cancellationToken: ct);
+ await bot.SendMessage(
+ chatId: command.ChatId,
+ messageThreadId: command.MessageThreadId,
+ text: $"❌ Внимание! Сессия \"{System.Net.WebUtility.HtmlEncode(session.Title)}\" отменена.",
+ parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
+ cancellationToken: ct);
var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
if (mode.ShouldSendDirectMessages())
diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs
index a55e2b5..b49ed5a 100644
--- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs
+++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs
@@ -144,14 +144,31 @@ public sealed class CreateSessionHandler(
transaction);
}
- int? messageThreadId = null;
- if (message.Chat.IsForum)
+ var topicDestination = TelegramTopicRouting.ResolveNewScheduleDestination(
+ message.Chat.IsForum,
+ message.MessageThreadId);
+ var messageThreadId = topicDestination.MessageThreadId;
+ var topicCreatedByBot = topicDestination.TopicCreatedByBot;
+ if (topicDestination.ShouldCreateForumTopic)
{
- var topic = await botClient.CreateForumTopic(
- chatId: chatId,
- name: $"🎲 Игры: {title}",
- cancellationToken: cancellationToken);
- messageThreadId = topic.MessageThreadId;
+ try
+ {
+ var topic = await botClient.CreateForumTopic(
+ chatId: chatId,
+ name: $"🎲 Игры: {title}",
+ cancellationToken: cancellationToken);
+ messageThreadId = topic.MessageThreadId;
+ }
+ catch (Telegram.Bot.Exceptions.ApiRequestException ex)
+ when (TelegramTopicRouting.IsMissingForumTopicRightsError(ex.Message))
+ {
+ await transaction.RollbackAsync(cancellationToken);
+ await botClient.SendMessage(
+ chatId,
+ TelegramTopicRouting.MissingForumTopicRightsMessage,
+ cancellationToken: cancellationToken);
+ return;
+ }
}
var batchId = Guid.NewGuid();
@@ -161,8 +178,8 @@ public sealed class CreateSessionHandler(
{
var sessionId = await connection.ExecuteScalarAsync(
"""
- INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players)
- VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @ThreadId, @MaxPlayers)
+ INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, topic_created_by_bot, max_players)
+ VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @ThreadId, @TopicCreatedByBot, @MaxPlayers)
RETURNING id;
""",
new
@@ -173,6 +190,7 @@ public sealed class CreateSessionHandler(
Link = link,
ScheduledAt = scheduledAt,
ThreadId = messageThreadId,
+ TopicCreatedByBot = topicCreatedByBot,
MaxPlayers = parseResult.MaxPlayers,
Status = SessionStatus.Planned
},
diff --git a/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs
index edddbf5..46ed206 100644
--- a/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs
+++ b/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs
@@ -1,6 +1,7 @@
using Dapper;
using Npgsql;
using Telegram.Bot;
+using GmRelay.Bot.Infrastructure.Telegram;
using GmRelay.Shared.Domain;
namespace GmRelay.Bot.Features.Sessions.ListSessions;
@@ -12,7 +13,13 @@ public sealed record DeleteSessionCommand(
long ChatId,
int MessageId);
-internal sealed record DeleteSessionInfoDto(string Title, Guid BatchId, bool CanManage, int? ThreadId);
+internal sealed record DeleteSessionInfoDto(
+ string Title,
+ Guid BatchId,
+ Guid GroupId,
+ bool CanManage,
+ int? ThreadId,
+ bool TopicCreatedByBot);
public sealed class DeleteSessionHandler(
NpgsqlDataSource dataSource,
@@ -29,7 +36,9 @@ public sealed class DeleteSessionHandler(
"""
SELECT s.title AS Title,
s.batch_id AS BatchId,
+ s.group_id AS GroupId,
s.thread_id AS ThreadId,
+ s.topic_created_by_bot AS TopicCreatedByBot,
EXISTS (
SELECT 1
FROM group_managers gm
@@ -57,15 +66,23 @@ public sealed class DeleteSessionHandler(
// 2. Delete session
await connection.ExecuteAsync("DELETE FROM sessions WHERE id = @Id", new { Id = command.SessionId }, transaction);
- // 3. Check if any sessions are left in the batch
- var remainingInBatch = await connection.ExecuteScalarAsync(
- "SELECT COUNT(*) FROM sessions WHERE batch_id = @BatchId",
- new { BatchId = session.BatchId }, transaction);
+ var remainingInTopic = session.ThreadId.HasValue
+ ? await connection.ExecuteScalarAsync(
+ """
+ SELECT COUNT(*)
+ FROM sessions
+ WHERE group_id = @GroupId
+ AND thread_id = @ThreadId
+ """,
+ new { session.GroupId, ThreadId = session.ThreadId.Value },
+ transaction)
+ : 0;
await transaction.CommitAsync(ct);
- // 4. If no sessions left and we have a forum topic, delete the topic
- if (remainingInBatch == 0 && session.ThreadId.HasValue)
+ // 4. If no sessions are left in a bot-owned forum topic, delete the topic.
+ if (session.ThreadId.HasValue &&
+ TelegramTopicRouting.ShouldDeleteForumTopic(session.TopicCreatedByBot, remainingInTopic))
{
try
{
@@ -113,7 +130,7 @@ public sealed class DeleteSessionHandler(
if (sessionsList.Count == 0)
{
- try { await bot.EditMessageText(command.ChatId, command.MessageId, "📭 В этой группе нет предстоящих игр.", cancellationToken: ct); } catch {}
+ try { await bot.EditMessageText(command.ChatId, command.MessageId, "📭 В этой группе нет предстоящих игр.", cancellationToken: ct); } catch { }
return;
}
diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs
index e255761..e1569e6 100644
--- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs
+++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs
@@ -14,7 +14,7 @@ namespace GmRelay.Bot.Features.Sessions.RescheduleSession;
internal sealed record AwaitingProposalDto(
Guid Id, Guid SessionId, string Title, DateTime CurrentScheduledAt,
- Guid BatchId, int? BatchMessageId, long TelegramChatId, string NotificationMode);
+ Guid BatchId, int? BatchMessageId, long TelegramChatId, int? ThreadId, string NotificationMode);
internal sealed record VoteParticipantDto(
Guid PlayerId,
@@ -57,6 +57,7 @@ public sealed class HandleRescheduleTimeInputHandler(
SELECT rp.id AS Id, rp.session_id AS SessionId, s.title AS Title, s.scheduled_at AS CurrentScheduledAt,
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
g.telegram_chat_id AS TelegramChatId,
+ s.thread_id AS ThreadId,
s.notification_mode AS NotificationMode
FROM reschedule_proposals rp
JOIN sessions s ON s.id = rp.session_id
@@ -84,6 +85,7 @@ public sealed class HandleRescheduleTimeInputHandler(
{
await bot.SendMessage(
chatId: chatId,
+ messageThreadId: proposal.ThreadId,
text: $"⚠️ {parseError}\n\nИспользуйте формат:\n25.04.2026 19:30\n26.04.2026 18:00\nДедлайн: 25.04.2026 12:00",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
cancellationToken: ct);
@@ -161,6 +163,7 @@ public sealed class HandleRescheduleTimeInputHandler(
var voteMsg = await bot.SendMessage(
chatId: chatId,
+ messageThreadId: proposal.ThreadId,
text: voteText,
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
replyMarkup: keyboard,
@@ -242,6 +245,7 @@ public sealed class HandleRescheduleTimeInputHandler(
await bot.SendMessage(
chatId: chatId,
+ messageThreadId: proposal.ThreadId,
text: $"✅ Сессия «{proposal.Title}» перенесена!\n\n📅 Новое время: {newTime.ToOffset(TimeSpan.FromHours(3)).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"))} (МСК)\n\nУчастников нет — голосование не требуется.",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
cancellationToken: ct);
diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs
index c6238ff..fd813fc 100644
--- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs
+++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs
@@ -12,6 +12,7 @@ public sealed record InitiateRescheduleCommand(
long TelegramUserId,
string CallbackQueryId,
long ChatId,
+ int? MessageThreadId,
int MessageId);
// ── DTOs ─────────────────────────────────────────────────────────────
@@ -96,6 +97,7 @@ public sealed class InitiateRescheduleHandler(
await bot.SendMessage(
chatId: command.ChatId,
+ messageThreadId: command.MessageThreadId,
text: $"""
⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.
diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs
index 3c856cd..012f4a1 100644
--- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs
+++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs
@@ -20,6 +20,7 @@ internal sealed record DueRescheduleProposalDto(
int? BatchMessageId,
int? VoteMessageId,
long TelegramChatId,
+ int? ThreadId,
string NotificationMode);
public sealed class RescheduleVotingDeadlineService(
@@ -93,6 +94,7 @@ public sealed class RescheduleVotingDeadlineService(
s.batch_id AS BatchId,
s.batch_message_id AS BatchMessageId,
s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId,
g.telegram_chat_id AS TelegramChatId
FROM reschedule_proposals rp
JOIN sessions s ON s.id = rp.session_id
@@ -324,6 +326,7 @@ public sealed class RescheduleVotingDeadlineService(
{
await bot.SendMessage(
chatId: proposal.TelegramChatId,
+ messageThreadId: proposal.ThreadId,
text: $"📣 Расписание обновлено после голосования за перенос сессии «{System.Net.WebUtility.HtmlEncode(proposal.Title)}».",
parseMode: ParseMode.Html,
cancellationToken: ct);
diff --git a/src/GmRelay.Bot/Infrastructure/Telegram/TelegramTopicRouting.cs b/src/GmRelay.Bot/Infrastructure/Telegram/TelegramTopicRouting.cs
new file mode 100644
index 0000000..b72528a
--- /dev/null
+++ b/src/GmRelay.Bot/Infrastructure/Telegram/TelegramTopicRouting.cs
@@ -0,0 +1,40 @@
+namespace GmRelay.Bot.Infrastructure.Telegram;
+
+public sealed record TelegramTopicDestination(
+ int? MessageThreadId,
+ bool ShouldCreateForumTopic,
+ bool TopicCreatedByBot);
+
+public static class TelegramTopicRouting
+{
+ public const string MissingForumTopicRightsMessage =
+ "Не удалось создать Telegram topic. Сделайте бота admin и включите право Manage Topics, затем повторите команду.";
+
+ public static TelegramTopicDestination ResolveNewScheduleDestination(
+ bool chatIsForum,
+ int? incomingMessageThreadId)
+ {
+ if (!chatIsForum)
+ {
+ return new TelegramTopicDestination(null, ShouldCreateForumTopic: false, TopicCreatedByBot: false);
+ }
+
+ if (incomingMessageThreadId.HasValue)
+ {
+ return new TelegramTopicDestination(
+ incomingMessageThreadId,
+ ShouldCreateForumTopic: false,
+ TopicCreatedByBot: false);
+ }
+
+ return new TelegramTopicDestination(null, ShouldCreateForumTopic: true, TopicCreatedByBot: true);
+ }
+
+ public static bool ShouldDeleteForumTopic(bool topicCreatedByBot, int remainingSessionsInTopic) =>
+ topicCreatedByBot && remainingSessionsInTopic == 0;
+
+ public static bool IsMissingForumTopicRightsError(string apiError) =>
+ apiError.Contains("not enough rights", StringComparison.OrdinalIgnoreCase) ||
+ apiError.Contains("CHAT_ADMIN_REQUIRED", StringComparison.OrdinalIgnoreCase) ||
+ apiError.Contains("not an administrator", StringComparison.OrdinalIgnoreCase);
+}
diff --git a/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs b/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs
index 28a7e14..0538a55 100644
--- a/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs
+++ b/src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs
@@ -80,7 +80,7 @@ public sealed class UpdateRouter(
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
MessageId: message.MessageId);
-
+
await joinSessionHandler.HandleAsync(command, ct);
return;
}
@@ -105,6 +105,7 @@ public sealed class UpdateRouter(
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
+ MessageThreadId: message.MessageThreadId,
MessageId: message.MessageId);
await cancelSessionHandler.HandleAsync(command, ct);
@@ -144,6 +145,7 @@ public sealed class UpdateRouter(
TelegramUserId: query.From.Id,
CallbackQueryId: query.Id,
ChatId: message.Chat.Id,
+ MessageThreadId: message.MessageThreadId,
MessageId: message.MessageId);
await initiateRescheduleHandler.HandleAsync(command, ct);
diff --git a/src/GmRelay.Bot/Migrations/V015__add_topic_ownership.sql b/src/GmRelay.Bot/Migrations/V015__add_topic_ownership.sql
new file mode 100644
index 0000000..1acd26e
--- /dev/null
+++ b/src/GmRelay.Bot/Migrations/V015__add_topic_ownership.sql
@@ -0,0 +1,6 @@
+ALTER TABLE sessions
+ ADD COLUMN topic_created_by_bot BOOLEAN NOT NULL DEFAULT FALSE;
+
+UPDATE sessions
+SET topic_created_by_bot = TRUE
+WHERE thread_id IS NOT NULL;
diff --git a/src/GmRelay.Bot/packages.lock.json b/src/GmRelay.Bot/packages.lock.json
new file mode 100644
index 0000000..3f68185
--- /dev/null
+++ b/src/GmRelay.Bot/packages.lock.json
@@ -0,0 +1,683 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Aspire.Npgsql": {
+ "type": "Direct",
+ "requested": "[13.2.2, )",
+ "resolved": "13.2.2",
+ "contentHash": "nEYgziWN7hksgEQEWy24JypcMCU8gKYcIIyPL05JfdXxUWuPRLotH/KOeuHevAjSEOYkL3dtGakBkJAuPobGmA==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.NpgSql": "9.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5",
+ "Npgsql.DependencyInjection": "10.0.1",
+ "Npgsql.OpenTelemetry": "10.0.1",
+ "OpenTelemetry.Extensions.Hosting": "1.15.0"
+ }
+ },
+ "Dapper": {
+ "type": "Direct",
+ "requested": "[2.1.72, )",
+ "resolved": "2.1.72",
+ "contentHash": "ns4mGqQd9a/MhP8m6w556vVlZIa0/MfUu03zrxjZC/jlr1uVCsUac8bkdB+Fs98Llbd56rRSo1eZH5VVmeGZyw=="
+ },
+ "Dapper.AOT": {
+ "type": "Direct",
+ "requested": "[1.0.48, )",
+ "resolved": "1.0.48",
+ "contentHash": "rsLM3yKr4g+YKKox9lhc8D+kz67P7Q9+xdyn1LmCsoYr1kYpJSm+Nt6slo5UrfUrcTiGJ57zUlyO8XUdV7G7iA=="
+ },
+ "dbup-postgresql": {
+ "type": "Direct",
+ "requested": "[7.0.1, )",
+ "resolved": "7.0.1",
+ "contentHash": "mRnmENWWPuuMZ538gOd1mZnzucx6FQk0anmw3EABjGfcbp24FDb9QdGepYrDiaM8K9s5/gd49+5cmBOlniH/lg==",
+ "dependencies": {
+ "Npgsql": "10.0.1",
+ "dbup-core": "6.1.1"
+ }
+ },
+ "Microsoft.DotNet.ILCompiler": {
+ "type": "Direct",
+ "requested": "[10.0.5, )",
+ "resolved": "10.0.5",
+ "contentHash": "yadTZIkStCVsG8nGwvfroSfBApPsgjQbodQyaIfp53dgayE0qhZpywixiCB6lx57JYQ+KVg1m1AFLrj54pxpZg=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Direct",
+ "requested": "[10.0.5, )",
+ "resolved": "10.0.5",
+ "contentHash": "8i7e5IBdiKLNqt/+ciWrS8U95Rv5DClaaj7ulkZbimnCi4uREWd+lXzkp3joofFuIPOlAzV4AckxLTIELv2jdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.5",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Logging.Console": "10.0.5",
+ "Microsoft.Extensions.Logging.Debug": "10.0.5",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.5",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.NET.ILLink.Tasks": {
+ "type": "Direct",
+ "requested": "[10.0.5, )",
+ "resolved": "10.0.5",
+ "contentHash": "A+5ZuQ0f449tM+MQrhf6R9ZX7lYpjk/ODEwLYKrnF6111rtARx8fVsm4YznUnQiKnnXfaXNBqgxmil6RW3L3SA=="
+ },
+ "Npgsql": {
+ "type": "Direct",
+ "requested": "[10.0.2, )",
+ "resolved": "10.0.2",
+ "contentHash": "q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.0"
+ }
+ },
+ "Telegram.Bot": {
+ "type": "Direct",
+ "requested": "[22.9.5.3, )",
+ "resolved": "22.9.5.3",
+ "contentHash": "7u8rZU9Vx9XEyIm6pB+dAlITsi1v63I+hKo7IEXGiQZnVjzvZgPs9yDCP17/Cwm7lgjCNEqknlbv/yoBnsUYFw==",
+ "dependencies": {
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "AspNetCore.HealthChecks.NpgSql": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "npc58/AD5zuVxERdhCl2Kb7WnL37mwX42SJcXIwvmEig0/dugOLg3SIwtfvvh3TnvTwR/sk5LYNkkPaBdks61A==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.11",
+ "Npgsql": "8.0.3"
+ }
+ },
+ "dbup-core": {
+ "type": "Transitive",
+ "resolved": "6.1.1",
+ "contentHash": "kgpuyJVEFJHoIj/slnc994Go88aoeZqNDfGHDBr4sh7CsEWwJhOTCt/FJqO4ziUImL5L0NEY0kxxOiNgPKI2Fw==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.AmbientMetadata.Application": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "CNrEjaOCZ8d1HtB0mvpiX4EWxLkee2xy+CsYXxmsEYJSFgw3OmF9pIhP/tCTeYBHhpsKJj5wM63G8IBFGxAcsw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.2",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Compliance.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "1a4xDAT6fRyP8t419q3WvWMmMslDTvI7OAZLWBhn5rysFG0bl5xFenTswd1xAbT/3u3mx4Xyb5bPx+V+18tJeQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "8Rx5sqg04FttxrumyG6bmoRuFRgYzK6IVwF1i0/o0cXfKBdDeVpJejKHtJCMjyg9E/DNMVqpqOGe/tCT5gYvVA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "P09QpTHjqHmCLQOTC+WyLkoRNxek4NIvfWt+TnU0etoDUSRxcltyd6+j/ouRbMdLR0j44GqGO+lhI2M4fAHG4g==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "99Z4rjyXopb1MIazDSPcvwYCUdYNO01Cf1GUs2WUjIFAbkGmwzj2vPa2k+3pheJRV+YgNd2QqRKHAri0oBAU4Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "or9fOLopMUTJOQVJ3bou4aD6PwvsiKf4kZC4EE5sRRKSkmh+wfk/LekJXRjAX88X+1JA9zHjDo+5fiQ7z3MY/A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "tchMGQ+zVTO40np/Zzg2Li/TIR8bksQgg4UVXZa0OzeFCKWnIYtxE2FVs+eSmjPGCjMS2voZbwN/mUcYfpSTuA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "OhTr0O79dP49734lLTqVveivVX9sDXxbI/8vjELAZTHXqoN90mdpgTAgwicJED42iaHMCcZcK6Bj+8wNyBikaw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "brBM/WP0YAUYh2+QqSYVdK8eQHYQTtTEUJXJ+84Zkdo2buGLja9VSrMIhgoeBUU7JBmcskAib8Lb/N83bvxgYQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "fhdG6UV9lIp70QhNkVyaHciUVq25IPFkczheVJL9bIFvmnJ+Zghaie6dWkDbbVmxZlHl9gj3zTDxMxJs5zNhIA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "v1SVsowG6YE1YnHVGmLWz57YTRCQRx9pH5ebIESXfm5isI9gA3QaMyg/oMTzPpXYZwSAVDzYItGJKfmV+pqXkQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "iVMtq9eRvzyhx8949EGT0OCYJfXi737SbRVzWXE5GrOgGj5AaZ9eUuxA/BSUfmOMALKn/g8KfFaNQw0eiB3lyA=="
+ },
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Z/OI261l7LnxyODKPx0trQyIHFyicCR/akfn64lGOjPcf4FpAZ7ePAGl2HPvQBUBSNfPTF0gWeCfuFmyftMgYA==",
+ "dependencies": {
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vAJHd4yOpmKoK+jBuYV7a3y+Ab9U4ARCc29b6qvMy276RgJFw9LFs0DdsPqOL3ahwzyrX7tM+i4cCxU/RX0qAg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/nYGrpa9/0BZofrVpBbbj+Ns8ZesiPE0V/KxsuHgDgHQopIzN54nRaQGSuvPw16/kI9sW1Zox5yyAPqvf0Jz6A==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "3qMK1D40D10kb5TdBtFJpzz6/WH0NinWs68ZZS8jCFgHMXDiOjGiPOneMmIocCP/wnUUW4Hzf8lMsIE1xIGxDA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "REdt95QXHscGdtw/UUgyCW2lF9DJcAOJxmebKW2IkgUjuCAdMODIi2HNOWg5utW98nm8ekgV0Gjqs/sljwwqMw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "NrIMTy7dpqxAvA6kHAYH8cXID/YgeNOy0OqFKpLtkPu5X4WS/basX91UszANzVrMNRAICJ2GOnGiRxJtsRyEQw=="
+ },
+ "Microsoft.Extensions.Features": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "X7tm2aV2w3lN9roSSGhl19lz4w76HvdiuKNhIv2XOiorYII9XCm66o/z9IJ0+QwkgvEv5gMZDM6rV6uwABHEQQ=="
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "nCBmCx0Xemlu65ZiWMcXbvfvtznKxf4/YYKF9R28QkqdI9lTikedGqzJ28/xmdGGsxUnsP5/3TQGpiPwVjK0dA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "dMu5kUPSfol1Rqhmr6nWPSmbFjDe9w6bkoKithG17bWTZA0UyKirTatM5mqYUN3mGpNA0MorlusIoVTh6J7o5g==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "mOE3ARusNQR0a5x8YOcnUbfyyXGqoAWQtEc7qFOfNJgruDWQLo39Re+3/Lzj5pLPFuFYj8hN4dgKzaSQDKiOCw=="
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+Wb7KAMVZTomwJkQrjuPTe5KBzGod7N8XeG+ScxRlkPOB4sZLG4ccVwjV4Phk5BCJt7uIMnGHVoN6ZMVploX+g==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "egUPC0xydb1ugCMcRyJ6zaOGOzx7N4coOVlGeLcIsXhUf1xHHwZeX+ob7JuG0dXExFduHYE/t+4/4y8BLlBKmw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Diagnostics": "10.0.2",
+ "Microsoft.Extensions.Logging": "10.0.2",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Http.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "I0FBgF6yZRwYH9E3KQ2vHm80YZ7YBj+52GDsmOWXPBv/p15b/wUoNupV9kw3LnSNVsWMqlGbiuZgBnHpMwPh+Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Http": "10.0.2",
+ "Microsoft.Extensions.Telemetry": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Http.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Lg+OjBW+ODDbM4Ax4LoERvQ1dqSZ8I2gQc2+B0/WOWl2+PunLJ3xb3x8MtHGfcb/Mp98RoMpwRKm6Aj9mzXwrA==",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Diagnostics": "10.2.0",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Resilience": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+XTMKQyDWg4ODoNHU/BN3BaI1jhGO7VCS+BnzT/4IauiG6y2iPAte7MyD7rHKS+hNP0TkFkjrae8DFjDUxtcxg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "9HOdqlDtPptVcmKAjsQ/Nr5Rxfq6FMYLdhvZh1lVmeKR738qeYecQD7+ldooXf+u2KzzR1kafSphWngIM3C6ug==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "cSgxsDgfP0+gmVRPVoNHI/KIDavIZxh+CxE6tSLPlYTogqccDnjBFI9CgEsiNuMP6+fiuXUwhhlTz36uUEpwbQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "PMs2gha2v24hvH5o5KQem5aNK4mN0BhhCWlMqsg9tzifWKzjeQi2tyPOP/RaWMVvalOhVLcrmoMYPqbnia/epg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/VacEkBQ02A8PBXSa6YpbIXCuisYy6JJr62/+ANJDZE+RMBfZMcXJXLfr/LpyLE6pgdp17Wxlt7e7R9zvkwZ3Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "0ezhWYJS4/6KrqQel9JL+Tr4n+4EX2TF5EYiaysBWNNEM2c3Gtj1moD39esfgk8OHblSX+UFjtZ3z0c4i9tRvw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "System.Diagnostics.EventLog": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vN+aq1hBFXyYvY5Ow9WyeR66drKQxRZmas4lAjh6QWfryPkjTn1uLtX5AFIxyDaZj78v5TG2sELUyvrXpAPQQw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.ObjectPool": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "kpCp4m7nwJVBcRKWXYHdVK/W0dkKyyFOjCmKVdO+zKThWvUxP1V+jVEP9FGpqRu4GPl9041SEXu2f+U/l825nQ=="
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "MDaQMdUplw0AIRhWWmbLA7yQEXaLIHb+9CTroTiNS8OlI0LMXS4LCxtopqauiqGCWlRgJ+xyraVD8t6veRAFbw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "BB9uUW3+6Rxu1R97OB1H/13lUF8P2+H1+eDhpZlK30kDh/6E4EKHBUqTp+ilXQmZLzsRErxON8aBSR6WpUKJdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/HUHJ0tw/LQvD0DZrz50eQy/3z7PfX7WWEaXnjKTV9/TNdcgFlNTZGo49QhS7PTmhDqMyHRMqAXSBxLh0vso4g=="
+ },
+ "Microsoft.Extensions.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "v4WOdAOFxB3AcsUkZWNcHL3mYzs4KAPtHO8rkoQlFKOBoD3KyjjAL+h3tRwSK5i4UpF/yhxsQRY0JxKj4osxxw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics": "10.0.2",
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "10.2.0",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0",
+ "Polly.Extensions": "8.4.2",
+ "Polly.RateLimiting": "8.4.2"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "AHTPfiKodj66xA8RwRkFD4q11V2AvzcuDsujv6ViPkOPtvBEYcPVplHakK56pPzWlX08MDS+TAQXfFXAeP7J5w==",
+ "dependencies": {
+ "Microsoft.Extensions.Http": "10.0.2",
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "sANlOvfqfw/yfych4CLlHSKSWzIie6mQG7w83gVur1foNOafyHxcgpoQMvBf+KiB4Tpls6P1/Z77IIQSK8hxFg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Features": "10.0.2",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2",
+ "Microsoft.Extensions.Primitives": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "ssW5gosYlewNH/ISTyaLD/XfJT4GSjwShOUKv61fpXrqVmHkhuIA/5bBAGStM1XbzJjt9IG2vzfdHTu4zlX9Ew==",
+ "dependencies": {
+ "Microsoft.Extensions.AmbientMetadata.Application": "10.2.0",
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": "10.2.0",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Telemetry.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "6V4V6NX6RLUYWwV89DeW/4zK5xOycYHWhsfMXSpKVGgMHfXcczmbk6hBeqTnRPzhpATYcOWlmA6hk1jgdxUugA==",
+ "dependencies": {
+ "Microsoft.Extensions.Compliance.Abstractions": "10.2.0",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2"
+ }
+ },
+ "Npgsql.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "YHFa4vD27sNIfv6s5q8Zi1fLvKfmK1xcpMv0PUvXOxDFbRmuMRSHwpZTbPvsAlj97q1/o7DfyynLqfqrCm1VnA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Npgsql": "10.0.1"
+ }
+ },
+ "Npgsql.OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "G9fEIBaHggZXWfDSDnKLc0XwKcbuU6i2eXp7zDqpgYxbhCmIN9fRgaSOGyyMNHSo/yY1IB4G4CjW5VO/SKRR0g==",
+ "dependencies": {
+ "Npgsql": "10.0.1",
+ "OpenTelemetry.API": "1.14.0"
+ }
+ },
+ "OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.0",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.0",
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Api": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g=="
+ },
+ "OpenTelemetry.Api.ProviderBuilderExtensions": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0",
+ "OpenTelemetry.Api": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==",
+ "dependencies": {
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.0",
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Instrumentation.AspNetCore": {
+ "type": "Transitive",
+ "resolved": "1.15.2",
+ "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Http": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "vFO4Fj/dXkoVNGo/nhoGpO2zYQmZwr4jTID7oRGo+XlQ8LqksyZjUXQ4p39RfUvTID7IzzL8Qe71tW7CcAFymA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.0",
+ "Microsoft.Extensions.Options": "10.0.0",
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Runtime": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "cpPwlUT5HXcLGPaIgsbSy0W9eFYAPGVbTP1p8/uyQ4Osvf5BJuPpEXE7crL09SmEd44r0DGNKDtsqxaAz0HxQw==",
+ "dependencies": {
+ "OpenTelemetry.Api": "[1.15.3, 2.0.0)"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g=="
+ },
+ "Polly.Extensions": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0",
+ "Polly.Core": "8.4.2"
+ }
+ },
+ "Polly.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2",
+ "System.Threading.RateLimiting": "8.0.0"
+ }
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "wugvy+pBVzjQEnRs9wMTWwoaeNFX3hsaHeVHFDIvJSWXp7wfmNWu3mxAwBIE6pyW+g6+rHa1Of5fTzb0QVqUTA=="
+ },
+ "System.Threading.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q=="
+ },
+ "gmrelay.servicedefaults": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Resilience": "[10.2.0, )",
+ "Microsoft.Extensions.ServiceDiscovery": "[10.2.0, )",
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": "[1.15.3, )",
+ "OpenTelemetry.Extensions.Hosting": "[1.15.3, )",
+ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )",
+ "OpenTelemetry.Instrumentation.Http": "[1.15.1, )",
+ "OpenTelemetry.Instrumentation.Runtime": "[1.15.1, )"
+ }
+ },
+ "gmrelay.shared": {
+ "type": "Project"
+ }
+ },
+ "net10.0/win-x64": {
+ "Microsoft.DotNet.ILCompiler": {
+ "type": "Direct",
+ "requested": "[10.0.5, )",
+ "resolved": "10.0.5",
+ "contentHash": "yadTZIkStCVsG8nGwvfroSfBApPsgjQbodQyaIfp53dgayE0qhZpywixiCB6lx57JYQ+KVg1m1AFLrj54pxpZg==",
+ "dependencies": {
+ "runtime.win-x64.Microsoft.DotNet.ILCompiler": "10.0.5"
+ }
+ },
+ "runtime.win-x64.Microsoft.DotNet.ILCompiler": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vblLkpVhSDYOmrEW0jypX7YVtLg7idU1QzUyx45ZdZ2sFUSSf3mYFCr0FW3+KZgXWpN1ve9ZPrxNywvHISF4bA=="
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "wugvy+pBVzjQEnRs9wMTWwoaeNFX3hsaHeVHFDIvJSWXp7wfmNWu3mxAwBIE6pyW+g6+rHa1Of5fTzb0QVqUTA=="
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GmRelay.ServiceDefaults/packages.lock.json b/src/GmRelay.ServiceDefaults/packages.lock.json
new file mode 100644
index 0000000..65d1c84
--- /dev/null
+++ b/src/GmRelay.ServiceDefaults/packages.lock.json
@@ -0,0 +1,175 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Microsoft.Extensions.Http.Resilience": {
+ "type": "Direct",
+ "requested": "[10.2.0, )",
+ "resolved": "10.2.0",
+ "contentHash": "Lg+OjBW+ODDbM4Ax4LoERvQ1dqSZ8I2gQc2+B0/WOWl2+PunLJ3xb3x8MtHGfcb/Mp98RoMpwRKm6Aj9mzXwrA==",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Diagnostics": "10.2.0",
+ "Microsoft.Extensions.Resilience": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery": {
+ "type": "Direct",
+ "requested": "[10.2.0, )",
+ "resolved": "10.2.0",
+ "contentHash": "AHTPfiKodj66xA8RwRkFD4q11V2AvzcuDsujv6ViPkOPtvBEYcPVplHakK56pPzWlX08MDS+TAQXfFXAeP7J5w==",
+ "dependencies": {
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": "10.2.0"
+ }
+ },
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": {
+ "type": "Direct",
+ "requested": "[1.15.3, )",
+ "resolved": "1.15.3",
+ "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Extensions.Hosting": {
+ "type": "Direct",
+ "requested": "[1.15.3, )",
+ "resolved": "1.15.3",
+ "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Instrumentation.AspNetCore": {
+ "type": "Direct",
+ "requested": "[1.15.2, )",
+ "resolved": "1.15.2",
+ "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Http": {
+ "type": "Direct",
+ "requested": "[1.15.1, )",
+ "resolved": "1.15.1",
+ "contentHash": "vFO4Fj/dXkoVNGo/nhoGpO2zYQmZwr4jTID7oRGo+XlQ8LqksyZjUXQ4p39RfUvTID7IzzL8Qe71tW7CcAFymA==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Runtime": {
+ "type": "Direct",
+ "requested": "[1.15.1, )",
+ "resolved": "1.15.1",
+ "contentHash": "cpPwlUT5HXcLGPaIgsbSy0W9eFYAPGVbTP1p8/uyQ4Osvf5BJuPpEXE7crL09SmEd44r0DGNKDtsqxaAz0HxQw==",
+ "dependencies": {
+ "OpenTelemetry.Api": "[1.15.3, 2.0.0)"
+ }
+ },
+ "Microsoft.Extensions.AmbientMetadata.Application": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "CNrEjaOCZ8d1HtB0mvpiX4EWxLkee2xy+CsYXxmsEYJSFgw3OmF9pIhP/tCTeYBHhpsKJj5wM63G8IBFGxAcsw=="
+ },
+ "Microsoft.Extensions.Compliance.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "1a4xDAT6fRyP8t419q3WvWMmMslDTvI7OAZLWBhn5rysFG0bl5xFenTswd1xAbT/3u3mx4Xyb5bPx+V+18tJeQ=="
+ },
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Z/OI261l7LnxyODKPx0trQyIHFyicCR/akfn64lGOjPcf4FpAZ7ePAGl2HPvQBUBSNfPTF0gWeCfuFmyftMgYA=="
+ },
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "3qMK1D40D10kb5TdBtFJpzz6/WH0NinWs68ZZS8jCFgHMXDiOjGiPOneMmIocCP/wnUUW4Hzf8lMsIE1xIGxDA=="
+ },
+ "Microsoft.Extensions.Http.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "I0FBgF6yZRwYH9E3KQ2vHm80YZ7YBj+52GDsmOWXPBv/p15b/wUoNupV9kw3LnSNVsWMqlGbiuZgBnHpMwPh+Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Telemetry": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "v4WOdAOFxB3AcsUkZWNcHL3mYzs4KAPtHO8rkoQlFKOBoD3KyjjAL+h3tRwSK5i4UpF/yhxsQRY0JxKj4osxxw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "10.2.0",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0",
+ "Polly.Extensions": "8.4.2",
+ "Polly.RateLimiting": "8.4.2"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "sANlOvfqfw/yfych4CLlHSKSWzIie6mQG7w83gVur1foNOafyHxcgpoQMvBf+KiB4Tpls6P1/Z77IIQSK8hxFg=="
+ },
+ "Microsoft.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "ssW5gosYlewNH/ISTyaLD/XfJT4GSjwShOUKv61fpXrqVmHkhuIA/5bBAGStM1XbzJjt9IG2vzfdHTu4zlX9Ew==",
+ "dependencies": {
+ "Microsoft.Extensions.AmbientMetadata.Application": "10.2.0",
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": "10.2.0",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Telemetry.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "6V4V6NX6RLUYWwV89DeW/4zK5xOycYHWhsfMXSpKVGgMHfXcczmbk6hBeqTnRPzhpATYcOWlmA6hk1jgdxUugA==",
+ "dependencies": {
+ "Microsoft.Extensions.Compliance.Abstractions": "10.2.0"
+ }
+ },
+ "OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Api": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g=="
+ },
+ "OpenTelemetry.Api.ProviderBuilderExtensions": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==",
+ "dependencies": {
+ "OpenTelemetry.Api": "1.15.3"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g=="
+ },
+ "Polly.Extensions": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2"
+ }
+ },
+ "Polly.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/GmRelay.Shared/Domain/MoscowTime.cs b/src/GmRelay.Shared/Domain/MoscowTime.cs
index 823f6bf..01c630b 100644
--- a/src/GmRelay.Shared/Domain/MoscowTime.cs
+++ b/src/GmRelay.Shared/Domain/MoscowTime.cs
@@ -21,7 +21,7 @@ public static class MoscowTime
public static bool TryParseMoscow(string text, out DateTimeOffset utcTime)
{
- if (DateTime.TryParseExact(text, new[] { "dd.MM.yyyy HH:mm", "dd.MM.yyyy H:mm", "d.MM.yyyy HH:mm" },
+ if (DateTime.TryParseExact(text, new[] { "dd.MM.yyyy HH:mm", "dd.MM.yyyy H:mm", "d.MM.yyyy HH:mm" },
System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var localDt))
{
utcTime = new DateTimeOffset(localDt, MoscowOffset).ToUniversalTime();
diff --git a/src/GmRelay.Shared/packages.lock.json b/src/GmRelay.Shared/packages.lock.json
new file mode 100644
index 0000000..4a91a8c
--- /dev/null
+++ b/src/GmRelay.Shared/packages.lock.json
@@ -0,0 +1,6 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {}
+ }
+}
\ No newline at end of file
diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor
index 92c036b..b32e0d7 100644
--- a/src/GmRelay.Web/Components/Layout/NavMenu.razor
+++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor
@@ -79,4 +79,4 @@
private void ToggleMenu() => isOpen = !isOpen;
private void CloseMenu() => isOpen = false;
-}
\ No newline at end of file
+}
diff --git a/src/GmRelay.Web/Dockerfile b/src/GmRelay.Web/Dockerfile
index 7a6f163..e73a6f1 100644
--- a/src/GmRelay.Web/Dockerfile
+++ b/src/GmRelay.Web/Dockerfile
@@ -18,8 +18,9 @@ RUN dotnet publish "GmRelay.Web.csproj" -c Release -o /app/publish /p:UseAppHost
# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble AS final
WORKDIR /app
-RUN apt-get update && apt-get install -y libgssapi-krb5-2 && rm -rf /var/lib/apt/lists/*
+RUN apt-get update && apt-get install -y --no-install-recommends libgssapi-krb5-2 && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
+USER $APP_UID
ENTRYPOINT ["dotnet", "GmRelay.Web.dll"]
diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs
index 1524c67..ba98850 100644
--- a/src/GmRelay.Web/Services/SessionService.cs
+++ b/src/GmRelay.Web/Services/SessionService.cs
@@ -38,7 +38,8 @@ public sealed record WebSession(
int? MaxPlayers,
int ActivePlayerCount,
int WaitlistedPlayerCount,
- string NotificationMode = SessionNotificationModeExtensions.GroupAndDirectValue);
+ string NotificationMode = SessionNotificationModeExtensions.GroupAndDirectValue,
+ int? ThreadId = null);
public sealed record WebParticipant(
Guid Id,
@@ -73,7 +74,8 @@ internal sealed record WebBatchSessionRow(
int? BatchMessageId,
long TelegramChatId,
int? ThreadId,
- string NotificationMode);
+ string NotificationMode,
+ bool TopicCreatedByBot = false);
internal sealed record WebTemplateGroupDto(long TelegramChatId);
public sealed class SessionService(
@@ -314,7 +316,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
- s.notification_mode AS NotificationMode
+ s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
LEFT JOIN LATERAL (
@@ -351,7 +354,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
- s.notification_mode AS NotificationMode
+ s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
LEFT JOIN LATERAL (
@@ -409,7 +413,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
0 AS ActivePlayerCount,
0 AS WaitlistedPlayerCount,
- s.notification_mode AS NotificationMode
+ s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
WHERE s.id = @Id AND s.group_id = @GroupId",
@@ -463,7 +468,11 @@ public sealed class SessionService(
"\n" +
$"👥 Мест: {(maxPlayers.HasValue ? maxPlayers.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : "без лимита")}";
- await bot.SendMessage(oldSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
+ await bot.SendMessage(
+ chatId: oldSession.TelegramChatId,
+ messageThreadId: oldSession.ThreadId,
+ text: notification,
+ parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
var mode = SessionNotificationModeExtensions.FromDatabaseValue(oldSession.NotificationMode);
if (mode.ShouldSendDirectMessages())
@@ -490,7 +499,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
0 AS ActivePlayerCount,
0 AS WaitlistedPlayerCount,
- s.notification_mode AS NotificationMode
+ s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
WHERE s.id = @SessionId AND s.group_id = @GroupId
@@ -567,8 +577,9 @@ public sealed class SessionService(
await transaction.CommitAsync();
await bot.SendMessage(
- session.TelegramChatId,
- $"⬆️ {System.Net.WebUtility.HtmlEncode(promoted.DisplayName)} переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
+ chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
+ text: $"⬆️ {System.Net.WebUtility.HtmlEncode(promoted.DisplayName)} переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
if (session.BatchMessageId.HasValue)
@@ -612,7 +623,8 @@ public sealed class SessionService(
s.max_players AS MaxPlayers,
0 AS ActivePlayerCount,
0 AS WaitlistedPlayerCount,
- s.notification_mode AS NotificationMode
+ s.notification_mode AS NotificationMode,
+ s.thread_id AS ThreadId
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
WHERE s.id = @SessionId AND s.group_id = @GroupId
@@ -697,15 +709,17 @@ public sealed class SessionService(
await transaction.CommitAsync();
await bot.SendMessage(
- session.TelegramChatId,
- $"🚪 {System.Net.WebUtility.HtmlEncode(participant.DisplayName)} удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
+ chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
+ text: $"🚪 {System.Net.WebUtility.HtmlEncode(participant.DisplayName)} удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
if (promoted is not null)
{
await bot.SendMessage(
- session.TelegramChatId,
- $"⬆️ {System.Net.WebUtility.HtmlEncode(promoted.DisplayName)} переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
+ chatId: session.TelegramChatId,
+ messageThreadId: session.ThreadId,
+ text: $"⬆️ {System.Net.WebUtility.HtmlEncode(promoted.DisplayName)} переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
if (session.BatchMessageId.HasValue)
@@ -813,6 +827,7 @@ public sealed class SessionService(
s.batch_message_id AS BatchMessageId,
g.telegram_chat_id AS TelegramChatId,
s.thread_id AS ThreadId,
+ s.topic_created_by_bot AS TopicCreatedByBot,
s.notification_mode AS NotificationMode
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
@@ -865,7 +880,11 @@ public sealed class SessionService(
$"🗓 Новое начало: {firstScheduledAt.FormatMoscow()} (МСК)\n" +
$"↔️ Шаг: {intervalDays} дн.";
- await bot.SendMessage(firstSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
+ await bot.SendMessage(
+ chatId: firstSession.TelegramChatId,
+ messageThreadId: firstSession.ThreadId,
+ text: notification,
+ parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
var mode = SessionNotificationModeExtensions.FromDatabaseValue(firstSession.NotificationMode);
if (mode.ShouldSendDirectMessages())
@@ -892,6 +911,7 @@ public sealed class SessionService(
s.batch_message_id AS BatchMessageId,
g.telegram_chat_id AS TelegramChatId,
s.thread_id AS ThreadId,
+ s.topic_created_by_bot AS TopicCreatedByBot,
s.notification_mode AS NotificationMode
FROM sessions s
JOIN game_groups g ON g.id = s.group_id
@@ -920,8 +940,8 @@ public sealed class SessionService(
var scheduledAt = BatchSchedulePlanner.ShiftForClone(sourceSession.ScheduledAt, interval);
var sessionId = await conn.ExecuteScalarAsync(
"""
- INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players, notification_mode)
- VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @MaxPlayers, @NotificationMode)
+ INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, topic_created_by_bot, max_players, notification_mode)
+ VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @TopicCreatedByBot, @MaxPlayers, @NotificationMode)
RETURNING id
""",
new
@@ -933,6 +953,7 @@ public sealed class SessionService(
ScheduledAt = scheduledAt,
Status = SessionStatus.Planned,
ThreadId = threadId,
+ sourceSession.TopicCreatedByBot,
sourceSession.MaxPlayers,
sourceSession.NotificationMode
},
diff --git a/src/GmRelay.Web/packages.lock.json b/src/GmRelay.Web/packages.lock.json
new file mode 100644
index 0000000..d634357
--- /dev/null
+++ b/src/GmRelay.Web/packages.lock.json
@@ -0,0 +1,244 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Aspire.Npgsql": {
+ "type": "Direct",
+ "requested": "[13.2.2, )",
+ "resolved": "13.2.2",
+ "contentHash": "nEYgziWN7hksgEQEWy24JypcMCU8gKYcIIyPL05JfdXxUWuPRLotH/KOeuHevAjSEOYkL3dtGakBkJAuPobGmA==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.NpgSql": "9.0.0",
+ "Npgsql.DependencyInjection": "10.0.1",
+ "Npgsql.OpenTelemetry": "10.0.1",
+ "OpenTelemetry.Extensions.Hosting": "1.15.0"
+ }
+ },
+ "Dapper": {
+ "type": "Direct",
+ "requested": "[2.1.72, )",
+ "resolved": "2.1.72",
+ "contentHash": "ns4mGqQd9a/MhP8m6w556vVlZIa0/MfUu03zrxjZC/jlr1uVCsUac8bkdB+Fs98Llbd56rRSo1eZH5VVmeGZyw=="
+ },
+ "Microsoft.AspNetCore.App.Internal.Assets": {
+ "type": "Direct",
+ "requested": "[10.0.5, )",
+ "resolved": "10.0.5",
+ "contentHash": "Oxw9Ps1/nd6c/EMCAI13AeJFEqXezAvCEOshMjUWmL7LeGirHJNzytR2e/3jINYg0j2TmPvNUowGHf+gp8zDSQ=="
+ },
+ "Npgsql": {
+ "type": "Direct",
+ "requested": "[10.0.2, )",
+ "resolved": "10.0.2",
+ "contentHash": "q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg=="
+ },
+ "Telegram.Bot": {
+ "type": "Direct",
+ "requested": "[22.9.6.1, )",
+ "resolved": "22.9.6.1",
+ "contentHash": "I0eaMaETcWIhMn4uu4RGd9e6PLJOjaOG3QAcKPsTcS80H3TF6gqj3UF9NKu4ZY90ul6Y6NiWToHkg/PsvxkotA=="
+ },
+ "AspNetCore.HealthChecks.NpgSql": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "npc58/AD5zuVxERdhCl2Kb7WnL37mwX42SJcXIwvmEig0/dugOLg3SIwtfvvh3TnvTwR/sk5LYNkkPaBdks61A==",
+ "dependencies": {
+ "Npgsql": "8.0.3"
+ }
+ },
+ "Microsoft.Extensions.AmbientMetadata.Application": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "CNrEjaOCZ8d1HtB0mvpiX4EWxLkee2xy+CsYXxmsEYJSFgw3OmF9pIhP/tCTeYBHhpsKJj5wM63G8IBFGxAcsw=="
+ },
+ "Microsoft.Extensions.Compliance.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "1a4xDAT6fRyP8t419q3WvWMmMslDTvI7OAZLWBhn5rysFG0bl5xFenTswd1xAbT/3u3mx4Xyb5bPx+V+18tJeQ=="
+ },
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Z/OI261l7LnxyODKPx0trQyIHFyicCR/akfn64lGOjPcf4FpAZ7ePAGl2HPvQBUBSNfPTF0gWeCfuFmyftMgYA=="
+ },
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "3qMK1D40D10kb5TdBtFJpzz6/WH0NinWs68ZZS8jCFgHMXDiOjGiPOneMmIocCP/wnUUW4Hzf8lMsIE1xIGxDA=="
+ },
+ "Microsoft.Extensions.Http.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "I0FBgF6yZRwYH9E3KQ2vHm80YZ7YBj+52GDsmOWXPBv/p15b/wUoNupV9kw3LnSNVsWMqlGbiuZgBnHpMwPh+Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Telemetry": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Http.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Lg+OjBW+ODDbM4Ax4LoERvQ1dqSZ8I2gQc2+B0/WOWl2+PunLJ3xb3x8MtHGfcb/Mp98RoMpwRKm6Aj9mzXwrA==",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Diagnostics": "10.2.0",
+ "Microsoft.Extensions.Resilience": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "v4WOdAOFxB3AcsUkZWNcHL3mYzs4KAPtHO8rkoQlFKOBoD3KyjjAL+h3tRwSK5i4UpF/yhxsQRY0JxKj4osxxw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "10.2.0",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0",
+ "Polly.Extensions": "8.4.2",
+ "Polly.RateLimiting": "8.4.2"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "AHTPfiKodj66xA8RwRkFD4q11V2AvzcuDsujv6ViPkOPtvBEYcPVplHakK56pPzWlX08MDS+TAQXfFXAeP7J5w==",
+ "dependencies": {
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "sANlOvfqfw/yfych4CLlHSKSWzIie6mQG7w83gVur1foNOafyHxcgpoQMvBf+KiB4Tpls6P1/Z77IIQSK8hxFg=="
+ },
+ "Microsoft.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "ssW5gosYlewNH/ISTyaLD/XfJT4GSjwShOUKv61fpXrqVmHkhuIA/5bBAGStM1XbzJjt9IG2vzfdHTu4zlX9Ew==",
+ "dependencies": {
+ "Microsoft.Extensions.AmbientMetadata.Application": "10.2.0",
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": "10.2.0",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Telemetry.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "6V4V6NX6RLUYWwV89DeW/4zK5xOycYHWhsfMXSpKVGgMHfXcczmbk6hBeqTnRPzhpATYcOWlmA6hk1jgdxUugA==",
+ "dependencies": {
+ "Microsoft.Extensions.Compliance.Abstractions": "10.2.0"
+ }
+ },
+ "Npgsql.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "YHFa4vD27sNIfv6s5q8Zi1fLvKfmK1xcpMv0PUvXOxDFbRmuMRSHwpZTbPvsAlj97q1/o7DfyynLqfqrCm1VnA==",
+ "dependencies": {
+ "Npgsql": "10.0.1"
+ }
+ },
+ "Npgsql.OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "G9fEIBaHggZXWfDSDnKLc0XwKcbuU6i2eXp7zDqpgYxbhCmIN9fRgaSOGyyMNHSo/yY1IB4G4CjW5VO/SKRR0g==",
+ "dependencies": {
+ "Npgsql": "10.0.1",
+ "OpenTelemetry.API": "1.14.0"
+ }
+ },
+ "OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Api": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g=="
+ },
+ "OpenTelemetry.Api.ProviderBuilderExtensions": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==",
+ "dependencies": {
+ "OpenTelemetry.Api": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Instrumentation.AspNetCore": {
+ "type": "Transitive",
+ "resolved": "1.15.2",
+ "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Http": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "vFO4Fj/dXkoVNGo/nhoGpO2zYQmZwr4jTID7oRGo+XlQ8LqksyZjUXQ4p39RfUvTID7IzzL8Qe71tW7CcAFymA==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Runtime": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "cpPwlUT5HXcLGPaIgsbSy0W9eFYAPGVbTP1p8/uyQ4Osvf5BJuPpEXE7crL09SmEd44r0DGNKDtsqxaAz0HxQw==",
+ "dependencies": {
+ "OpenTelemetry.Api": "[1.15.3, 2.0.0)"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g=="
+ },
+ "Polly.Extensions": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2"
+ }
+ },
+ "Polly.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2"
+ }
+ },
+ "gmrelay.servicedefaults": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Resilience": "[10.2.0, )",
+ "Microsoft.Extensions.ServiceDiscovery": "[10.2.0, )",
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": "[1.15.3, )",
+ "OpenTelemetry.Extensions.Hosting": "[1.15.3, )",
+ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )",
+ "OpenTelemetry.Instrumentation.Http": "[1.15.1, )",
+ "OpenTelemetry.Instrumentation.Runtime": "[1.15.1, )"
+ }
+ },
+ "gmrelay.shared": {
+ "type": "Project"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicIntegrationSmokeTests.cs b/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicIntegrationSmokeTests.cs
new file mode 100644
index 0000000..0e33432
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicIntegrationSmokeTests.cs
@@ -0,0 +1,76 @@
+namespace GmRelay.Bot.Tests.Infrastructure.Telegram;
+
+public sealed class TelegramTopicIntegrationSmokeTests
+{
+ [Fact]
+ public async Task BotAndWebCode_ShouldPersistAndUseTopicOwnership()
+ {
+ var migration = await ReadRepositoryFileAsync("src/GmRelay.Bot/Migrations/V015__add_topic_ownership.sql");
+ var createHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs");
+ var deleteHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs");
+
+ Assert.Contains("topic_created_by_bot", migration, StringComparison.Ordinal);
+ Assert.Contains("ResolveNewScheduleDestination", createHandler, StringComparison.Ordinal);
+ Assert.Contains("message.MessageThreadId", createHandler, StringComparison.Ordinal);
+ Assert.Contains("topic_created_by_bot", createHandler, StringComparison.Ordinal);
+ Assert.Contains("MissingForumTopicRightsMessage", createHandler, StringComparison.Ordinal);
+ Assert.Contains("TopicCreatedByBot", deleteHandler, StringComparison.Ordinal);
+ Assert.Contains("ShouldDeleteForumTopic", deleteHandler, StringComparison.Ordinal);
+ Assert.Contains("remainingInTopic", deleteHandler, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public async Task GroupNotifications_ShouldSendToStoredForumTopic()
+ {
+ var confirmationHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs");
+ var joinLinkHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs");
+ var rsvpHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs");
+ var cancelHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs");
+ var initiateRescheduleHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs");
+ var rescheduleInputHandler = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs");
+ var rescheduleDeadlineService = await ReadRepositoryFileAsync("src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs");
+
+ Assert.Contains("int? ThreadId", confirmationHandler, StringComparison.Ordinal);
+ Assert.Contains("s.thread_id AS ThreadId", confirmationHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: session.ThreadId", confirmationHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? ThreadId", joinLinkHandler, StringComparison.Ordinal);
+ Assert.Contains("s.thread_id AS ThreadId", joinLinkHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: session.ThreadId", joinLinkHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? ThreadId", rsvpHandler, StringComparison.Ordinal);
+ Assert.Contains("s.thread_id AS ThreadId", rsvpHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: session.ThreadId", rsvpHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? MessageThreadId", cancelHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: command.MessageThreadId", cancelHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? MessageThreadId", initiateRescheduleHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: command.MessageThreadId", initiateRescheduleHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? ThreadId", rescheduleInputHandler, StringComparison.Ordinal);
+ Assert.Contains("s.thread_id AS ThreadId", rescheduleInputHandler, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: proposal.ThreadId", rescheduleInputHandler, StringComparison.Ordinal);
+
+ Assert.Contains("int? ThreadId", rescheduleDeadlineService, StringComparison.Ordinal);
+ Assert.Contains("s.thread_id AS ThreadId", rescheduleDeadlineService, StringComparison.Ordinal);
+ Assert.Contains("messageThreadId: proposal.ThreadId", rescheduleDeadlineService, StringComparison.Ordinal);
+ }
+
+ private static async Task ReadRepositoryFileAsync(string relativePath)
+ {
+ var directory = new DirectoryInfo(AppContext.BaseDirectory);
+ while (directory is not null)
+ {
+ var candidate = Path.Combine(directory.FullName, relativePath);
+ if (File.Exists(candidate))
+ {
+ return await File.ReadAllTextAsync(candidate);
+ }
+
+ directory = directory.Parent;
+ }
+
+ throw new FileNotFoundException($"Could not locate repository file '{relativePath}'.");
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicRoutingTests.cs b/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicRoutingTests.cs
new file mode 100644
index 0000000..7949a70
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramTopicRoutingTests.cs
@@ -0,0 +1,80 @@
+using GmRelay.Bot.Infrastructure.Telegram;
+
+namespace GmRelay.Bot.Tests.Infrastructure.Telegram;
+
+public sealed class TelegramTopicRoutingTests
+{
+ [Fact]
+ public void ResolveNewScheduleDestination_UsesIncomingTopic_WhenForumCommandWasSentInsideTopic()
+ {
+ var destination = TelegramTopicRouting.ResolveNewScheduleDestination(
+ chatIsForum: true,
+ incomingMessageThreadId: 42);
+
+ Assert.Equal(42, destination.MessageThreadId);
+ Assert.False(destination.ShouldCreateForumTopic);
+ Assert.False(destination.TopicCreatedByBot);
+ }
+
+ [Fact]
+ public void ResolveNewScheduleDestination_CreatesBotOwnedTopic_WhenForumCommandWasSentInRoot()
+ {
+ var destination = TelegramTopicRouting.ResolveNewScheduleDestination(
+ chatIsForum: true,
+ incomingMessageThreadId: null);
+
+ Assert.Null(destination.MessageThreadId);
+ Assert.True(destination.ShouldCreateForumTopic);
+ Assert.True(destination.TopicCreatedByBot);
+ }
+
+ [Fact]
+ public void ResolveNewScheduleDestination_UsesPlainChat_WhenChatIsNotForum()
+ {
+ var destination = TelegramTopicRouting.ResolveNewScheduleDestination(
+ chatIsForum: false,
+ incomingMessageThreadId: 42);
+
+ Assert.Null(destination.MessageThreadId);
+ Assert.False(destination.ShouldCreateForumTopic);
+ Assert.False(destination.TopicCreatedByBot);
+ }
+
+ [Fact]
+ public void MissingForumTopicRightsMessage_NamesRequiredAdminRight()
+ {
+ Assert.Contains("admin", TelegramTopicRouting.MissingForumTopicRightsMessage, StringComparison.OrdinalIgnoreCase);
+ Assert.Contains("Manage Topics", TelegramTopicRouting.MissingForumTopicRightsMessage, StringComparison.Ordinal);
+ }
+
+ [Theory]
+ [InlineData("Bad Request: not enough rights to create forum topic")]
+ [InlineData("Bad Request: CHAT_ADMIN_REQUIRED")]
+ [InlineData("Forbidden: bot is not an administrator")]
+ public void IsMissingForumTopicRightsError_MatchesAdminPermissionErrors(string apiError)
+ {
+ Assert.True(TelegramTopicRouting.IsMissingForumTopicRightsError(apiError));
+ }
+
+ [Fact]
+ public void IsMissingForumTopicRightsError_IgnoresUnrelatedApiErrors()
+ {
+ Assert.False(TelegramTopicRouting.IsMissingForumTopicRightsError("Bad Request: topic name is invalid"));
+ }
+
+ [Theory]
+ [InlineData(true, 0, true)]
+ [InlineData(true, 1, false)]
+ [InlineData(false, 0, false)]
+ public void ShouldDeleteForumTopic_DeletesOnlyBotOwnedEmptyTopic(
+ bool topicCreatedByBot,
+ int remainingSessionsInTopic,
+ bool expected)
+ {
+ var shouldDelete = TelegramTopicRouting.ShouldDeleteForumTopic(
+ topicCreatedByBot,
+ remainingSessionsInTopic);
+
+ Assert.Equal(expected, shouldDelete);
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/packages.lock.json b/tests/GmRelay.Bot.Tests/packages.lock.json
new file mode 100644
index 0000000..743677d
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/packages.lock.json
@@ -0,0 +1,765 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[6.0.4, )",
+ "resolved": "6.0.4",
+ "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[17.14.1, )",
+ "resolved": "17.14.1",
+ "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "17.14.1",
+ "Microsoft.TestPlatform.TestHost": "17.14.1"
+ }
+ },
+ "xunit": {
+ "type": "Direct",
+ "requested": "[2.9.3, )",
+ "resolved": "2.9.3",
+ "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
+ "dependencies": {
+ "xunit.analyzers": "1.18.0",
+ "xunit.assert": "2.9.3",
+ "xunit.core": "[2.9.3]"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.4, )",
+ "resolved": "3.1.4",
+ "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg=="
+ },
+ "Aspire.Npgsql": {
+ "type": "Transitive",
+ "resolved": "13.2.2",
+ "contentHash": "nEYgziWN7hksgEQEWy24JypcMCU8gKYcIIyPL05JfdXxUWuPRLotH/KOeuHevAjSEOYkL3dtGakBkJAuPobGmA==",
+ "dependencies": {
+ "AspNetCore.HealthChecks.NpgSql": "9.0.0",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5",
+ "Npgsql.DependencyInjection": "10.0.1",
+ "Npgsql.OpenTelemetry": "10.0.1",
+ "OpenTelemetry.Extensions.Hosting": "1.15.0"
+ }
+ },
+ "AspNetCore.HealthChecks.NpgSql": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "npc58/AD5zuVxERdhCl2Kb7WnL37mwX42SJcXIwvmEig0/dugOLg3SIwtfvvh3TnvTwR/sk5LYNkkPaBdks61A==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks": "8.0.11",
+ "Npgsql": "8.0.3"
+ }
+ },
+ "Dapper": {
+ "type": "Transitive",
+ "resolved": "2.1.72",
+ "contentHash": "ns4mGqQd9a/MhP8m6w556vVlZIa0/MfUu03zrxjZC/jlr1uVCsUac8bkdB+Fs98Llbd56rRSo1eZH5VVmeGZyw=="
+ },
+ "Dapper.AOT": {
+ "type": "Transitive",
+ "resolved": "1.0.48",
+ "contentHash": "rsLM3yKr4g+YKKox9lhc8D+kz67P7Q9+xdyn1LmCsoYr1kYpJSm+Nt6slo5UrfUrcTiGJ57zUlyO8XUdV7G7iA=="
+ },
+ "dbup-core": {
+ "type": "Transitive",
+ "resolved": "6.1.1",
+ "contentHash": "kgpuyJVEFJHoIj/slnc994Go88aoeZqNDfGHDBr4sh7CsEWwJhOTCt/FJqO4ziUImL5L0NEY0kxxOiNgPKI2Fw==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0"
+ }
+ },
+ "dbup-postgresql": {
+ "type": "Transitive",
+ "resolved": "7.0.1",
+ "contentHash": "mRnmENWWPuuMZ538gOd1mZnzucx6FQk0anmw3EABjGfcbp24FDb9QdGepYrDiaM8K9s5/gd49+5cmBOlniH/lg==",
+ "dependencies": {
+ "Npgsql": "10.0.1",
+ "dbup-core": "6.1.1"
+ }
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
+ },
+ "Microsoft.Extensions.AmbientMetadata.Application": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "CNrEjaOCZ8d1HtB0mvpiX4EWxLkee2xy+CsYXxmsEYJSFgw3OmF9pIhP/tCTeYBHhpsKJj5wM63G8IBFGxAcsw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.2",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Compliance.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "1a4xDAT6fRyP8t419q3WvWMmMslDTvI7OAZLWBhn5rysFG0bl5xFenTswd1xAbT/3u3mx4Xyb5bPx+V+18tJeQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "8Rx5sqg04FttxrumyG6bmoRuFRgYzK6IVwF1i0/o0cXfKBdDeVpJejKHtJCMjyg9E/DNMVqpqOGe/tCT5gYvVA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "P09QpTHjqHmCLQOTC+WyLkoRNxek4NIvfWt+TnU0etoDUSRxcltyd6+j/ouRbMdLR0j44GqGO+lhI2M4fAHG4g==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "99Z4rjyXopb1MIazDSPcvwYCUdYNO01Cf1GUs2WUjIFAbkGmwzj2vPa2k+3pheJRV+YgNd2QqRKHAri0oBAU4Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "or9fOLopMUTJOQVJ3bou4aD6PwvsiKf4kZC4EE5sRRKSkmh+wfk/LekJXRjAX88X+1JA9zHjDo+5fiQ7z3MY/A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "tchMGQ+zVTO40np/Zzg2Li/TIR8bksQgg4UVXZa0OzeFCKWnIYtxE2FVs+eSmjPGCjMS2voZbwN/mUcYfpSTuA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "OhTr0O79dP49734lLTqVveivVX9sDXxbI/8vjELAZTHXqoN90mdpgTAgwicJED42iaHMCcZcK6Bj+8wNyBikaw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "brBM/WP0YAUYh2+QqSYVdK8eQHYQTtTEUJXJ+84Zkdo2buGLja9VSrMIhgoeBUU7JBmcskAib8Lb/N83bvxgYQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "fhdG6UV9lIp70QhNkVyaHciUVq25IPFkczheVJL9bIFvmnJ+Zghaie6dWkDbbVmxZlHl9gj3zTDxMxJs5zNhIA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "v1SVsowG6YE1YnHVGmLWz57YTRCQRx9pH5ebIESXfm5isI9gA3QaMyg/oMTzPpXYZwSAVDzYItGJKfmV+pqXkQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "iVMtq9eRvzyhx8949EGT0OCYJfXi737SbRVzWXE5GrOgGj5AaZ9eUuxA/BSUfmOMALKn/g8KfFaNQw0eiB3lyA=="
+ },
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Z/OI261l7LnxyODKPx0trQyIHFyicCR/akfn64lGOjPcf4FpAZ7ePAGl2HPvQBUBSNfPTF0gWeCfuFmyftMgYA==",
+ "dependencies": {
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vAJHd4yOpmKoK+jBuYV7a3y+Ab9U4ARCc29b6qvMy276RgJFw9LFs0DdsPqOL3ahwzyrX7tM+i4cCxU/RX0qAg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/nYGrpa9/0BZofrVpBbbj+Ns8ZesiPE0V/KxsuHgDgHQopIzN54nRaQGSuvPw16/kI9sW1Zox5yyAPqvf0Jz6A==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "3qMK1D40D10kb5TdBtFJpzz6/WH0NinWs68ZZS8jCFgHMXDiOjGiPOneMmIocCP/wnUUW4Hzf8lMsIE1xIGxDA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "REdt95QXHscGdtw/UUgyCW2lF9DJcAOJxmebKW2IkgUjuCAdMODIi2HNOWg5utW98nm8ekgV0Gjqs/sljwwqMw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "NrIMTy7dpqxAvA6kHAYH8cXID/YgeNOy0OqFKpLtkPu5X4WS/basX91UszANzVrMNRAICJ2GOnGiRxJtsRyEQw=="
+ },
+ "Microsoft.Extensions.Features": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "X7tm2aV2w3lN9roSSGhl19lz4w76HvdiuKNhIv2XOiorYII9XCm66o/z9IJ0+QwkgvEv5gMZDM6rV6uwABHEQQ=="
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "nCBmCx0Xemlu65ZiWMcXbvfvtznKxf4/YYKF9R28QkqdI9lTikedGqzJ28/xmdGGsxUnsP5/3TQGpiPwVjK0dA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "dMu5kUPSfol1Rqhmr6nWPSmbFjDe9w6bkoKithG17bWTZA0UyKirTatM5mqYUN3mGpNA0MorlusIoVTh6J7o5g==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "mOE3ARusNQR0a5x8YOcnUbfyyXGqoAWQtEc7qFOfNJgruDWQLo39Re+3/Lzj5pLPFuFYj8hN4dgKzaSQDKiOCw=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "8i7e5IBdiKLNqt/+ciWrS8U95Rv5DClaaj7ulkZbimnCi4uREWd+lXzkp3joofFuIPOlAzV4AckxLTIELv2jdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.5",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.5",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Json": "10.0.5",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.5",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Logging.Console": "10.0.5",
+ "Microsoft.Extensions.Logging.Debug": "10.0.5",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.5",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+Wb7KAMVZTomwJkQrjuPTe5KBzGod7N8XeG+ScxRlkPOB4sZLG4ccVwjV4Phk5BCJt7uIMnGHVoN6ZMVploX+g==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.5",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Http": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "egUPC0xydb1ugCMcRyJ6zaOGOzx7N4coOVlGeLcIsXhUf1xHHwZeX+ob7JuG0dXExFduHYE/t+4/4y8BLlBKmw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Diagnostics": "10.0.2",
+ "Microsoft.Extensions.Logging": "10.0.2",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Http.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "I0FBgF6yZRwYH9E3KQ2vHm80YZ7YBj+52GDsmOWXPBv/p15b/wUoNupV9kw3LnSNVsWMqlGbiuZgBnHpMwPh+Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Http": "10.0.2",
+ "Microsoft.Extensions.Telemetry": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Http.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "Lg+OjBW+ODDbM4Ax4LoERvQ1dqSZ8I2gQc2+B0/WOWl2+PunLJ3xb3x8MtHGfcb/Mp98RoMpwRKm6Aj9mzXwrA==",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Diagnostics": "10.2.0",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Resilience": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "+XTMKQyDWg4ODoNHU/BN3BaI1jhGO7VCS+BnzT/4IauiG6y2iPAte7MyD7rHKS+hNP0TkFkjrae8DFjDUxtcxg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "9HOdqlDtPptVcmKAjsQ/Nr5Rxfq6FMYLdhvZh1lVmeKR738qeYecQD7+ldooXf+u2KzzR1kafSphWngIM3C6ug==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "cSgxsDgfP0+gmVRPVoNHI/KIDavIZxh+CxE6tSLPlYTogqccDnjBFI9CgEsiNuMP6+fiuXUwhhlTz36uUEpwbQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.5",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "PMs2gha2v24hvH5o5KQem5aNK4mN0BhhCWlMqsg9tzifWKzjeQi2tyPOP/RaWMVvalOhVLcrmoMYPqbnia/epg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/VacEkBQ02A8PBXSa6YpbIXCuisYy6JJr62/+ANJDZE+RMBfZMcXJXLfr/LpyLE6pgdp17Wxlt7e7R9zvkwZ3Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "0ezhWYJS4/6KrqQel9JL+Tr4n+4EX2TF5EYiaysBWNNEM2c3Gtj1moD39esfgk8OHblSX+UFjtZ3z0c4i9tRvw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "System.Diagnostics.EventLog": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "vN+aq1hBFXyYvY5Ow9WyeR66drKQxRZmas4lAjh6QWfryPkjTn1uLtX5AFIxyDaZj78v5TG2sELUyvrXpAPQQw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Logging": "10.0.5",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.ObjectPool": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "kpCp4m7nwJVBcRKWXYHdVK/W0dkKyyFOjCmKVdO+zKThWvUxP1V+jVEP9FGpqRu4GPl9041SEXu2f+U/l825nQ=="
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "MDaQMdUplw0AIRhWWmbLA7yQEXaLIHb+9CTroTiNS8OlI0LMXS4LCxtopqauiqGCWlRgJ+xyraVD8t6veRAFbw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "BB9uUW3+6Rxu1R97OB1H/13lUF8P2+H1+eDhpZlK30kDh/6E4EKHBUqTp+ilXQmZLzsRErxON8aBSR6WpUKJdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.5",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.5",
+ "Microsoft.Extensions.Options": "10.0.5",
+ "Microsoft.Extensions.Primitives": "10.0.5"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "/HUHJ0tw/LQvD0DZrz50eQy/3z7PfX7WWEaXnjKTV9/TNdcgFlNTZGo49QhS7PTmhDqMyHRMqAXSBxLh0vso4g=="
+ },
+ "Microsoft.Extensions.Resilience": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "v4WOdAOFxB3AcsUkZWNcHL3mYzs4KAPtHO8rkoQlFKOBoD3KyjjAL+h3tRwSK5i4UpF/yhxsQRY0JxKj4osxxw==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics": "10.0.2",
+ "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "10.2.0",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.2",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0",
+ "Polly.Extensions": "8.4.2",
+ "Polly.RateLimiting": "8.4.2"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "AHTPfiKodj66xA8RwRkFD4q11V2AvzcuDsujv6ViPkOPtvBEYcPVplHakK56pPzWlX08MDS+TAQXfFXAeP7J5w==",
+ "dependencies": {
+ "Microsoft.Extensions.Http": "10.0.2",
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.ServiceDiscovery.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "sANlOvfqfw/yfych4CLlHSKSWzIie6mQG7w83gVur1foNOafyHxcgpoQMvBf+KiB4Tpls6P1/Z77IIQSK8hxFg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Features": "10.0.2",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2",
+ "Microsoft.Extensions.Primitives": "10.0.2"
+ }
+ },
+ "Microsoft.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "ssW5gosYlewNH/ISTyaLD/XfJT4GSjwShOUKv61fpXrqVmHkhuIA/5bBAGStM1XbzJjt9IG2vzfdHTu4zlX9Ew==",
+ "dependencies": {
+ "Microsoft.Extensions.AmbientMetadata.Application": "10.2.0",
+ "Microsoft.Extensions.DependencyInjection.AutoActivation": "10.2.0",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Telemetry.Abstractions": "10.2.0"
+ }
+ },
+ "Microsoft.Extensions.Telemetry.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.2.0",
+ "contentHash": "6V4V6NX6RLUYWwV89DeW/4zK5xOycYHWhsfMXSpKVGgMHfXcczmbk6hBeqTnRPzhpATYcOWlmA6hk1jgdxUugA==",
+ "dependencies": {
+ "Microsoft.Extensions.Compliance.Abstractions": "10.2.0",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.2",
+ "Microsoft.Extensions.ObjectPool": "10.0.2",
+ "Microsoft.Extensions.Options": "10.0.2"
+ }
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ=="
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "17.14.1",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "Npgsql": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.0"
+ }
+ },
+ "Npgsql.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "YHFa4vD27sNIfv6s5q8Zi1fLvKfmK1xcpMv0PUvXOxDFbRmuMRSHwpZTbPvsAlj97q1/o7DfyynLqfqrCm1VnA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Npgsql": "10.0.1"
+ }
+ },
+ "Npgsql.OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "10.0.1",
+ "contentHash": "G9fEIBaHggZXWfDSDnKLc0XwKcbuU6i2eXp7zDqpgYxbhCmIN9fRgaSOGyyMNHSo/yY1IB4G4CjW5VO/SKRR0g==",
+ "dependencies": {
+ "Npgsql": "10.0.1",
+ "OpenTelemetry.API": "1.14.0"
+ }
+ },
+ "OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.0",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.0",
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Api": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g=="
+ },
+ "OpenTelemetry.Api.ProviderBuilderExtensions": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0",
+ "OpenTelemetry.Api": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==",
+ "dependencies": {
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.0",
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Instrumentation.AspNetCore": {
+ "type": "Transitive",
+ "resolved": "1.15.2",
+ "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Http": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "vFO4Fj/dXkoVNGo/nhoGpO2zYQmZwr4jTID7oRGo+XlQ8LqksyZjUXQ4p39RfUvTID7IzzL8Qe71tW7CcAFymA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.0",
+ "Microsoft.Extensions.Options": "10.0.0",
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "OpenTelemetry.Instrumentation.Runtime": {
+ "type": "Transitive",
+ "resolved": "1.15.1",
+ "contentHash": "cpPwlUT5HXcLGPaIgsbSy0W9eFYAPGVbTP1p8/uyQ4Osvf5BJuPpEXE7crL09SmEd44r0DGNKDtsqxaAz0HxQw==",
+ "dependencies": {
+ "OpenTelemetry.Api": "[1.15.3, 2.0.0)"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g=="
+ },
+ "Polly.Extensions": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0",
+ "Polly.Core": "8.4.2"
+ }
+ },
+ "Polly.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.4.2",
+ "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==",
+ "dependencies": {
+ "Polly.Core": "8.4.2",
+ "System.Threading.RateLimiting": "8.0.0"
+ }
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.5",
+ "contentHash": "wugvy+pBVzjQEnRs9wMTWwoaeNFX3hsaHeVHFDIvJSWXp7wfmNWu3mxAwBIE6pyW+g6+rHa1Of5fTzb0QVqUTA=="
+ },
+ "System.Threading.RateLimiting": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q=="
+ },
+ "Telegram.Bot": {
+ "type": "Transitive",
+ "resolved": "22.9.6.1",
+ "contentHash": "I0eaMaETcWIhMn4uu4RGd9e6PLJOjaOG3QAcKPsTcS80H3TF6gqj3UF9NKu4ZY90ul6Y6NiWToHkg/PsvxkotA==",
+ "dependencies": {
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0"
+ }
+ },
+ "xunit.abstractions": {
+ "type": "Transitive",
+ "resolved": "2.0.3",
+ "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.18.0",
+ "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
+ },
+ "xunit.assert": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
+ },
+ "xunit.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]",
+ "xunit.extensibility.execution": "[2.9.3]"
+ }
+ },
+ "xunit.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
+ "dependencies": {
+ "xunit.abstractions": "2.0.3"
+ }
+ },
+ "xunit.extensibility.execution": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]"
+ }
+ },
+ "gmrelay.bot": {
+ "type": "Project",
+ "dependencies": {
+ "Aspire.Npgsql": "[13.2.2, )",
+ "Dapper": "[2.1.72, )",
+ "Dapper.AOT": "[1.0.48, )",
+ "GmRelay.ServiceDefaults": "[1.14.0, )",
+ "GmRelay.Shared": "[1.14.0, )",
+ "Microsoft.Extensions.Hosting": "[10.0.5, )",
+ "Npgsql": "[10.0.2, )",
+ "Telegram.Bot": "[22.9.5.3, )",
+ "dbup-postgresql": "[7.0.1, )"
+ }
+ },
+ "gmrelay.servicedefaults": {
+ "type": "Project",
+ "dependencies": {
+ "Microsoft.Extensions.Http.Resilience": "[10.2.0, )",
+ "Microsoft.Extensions.ServiceDiscovery": "[10.2.0, )",
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": "[1.15.3, )",
+ "OpenTelemetry.Extensions.Hosting": "[1.15.3, )",
+ "OpenTelemetry.Instrumentation.AspNetCore": "[1.15.2, )",
+ "OpenTelemetry.Instrumentation.Http": "[1.15.1, )",
+ "OpenTelemetry.Instrumentation.Runtime": "[1.15.1, )"
+ }
+ },
+ "gmrelay.shared": {
+ "type": "Project"
+ },
+ "gmrelay.web": {
+ "type": "Project",
+ "dependencies": {
+ "Aspire.Npgsql": "[13.2.2, )",
+ "Dapper": "[2.1.72, )",
+ "GmRelay.ServiceDefaults": "[1.14.0, )",
+ "GmRelay.Shared": "[1.14.0, )",
+ "Npgsql": "[10.0.2, )",
+ "Telegram.Bot": "[22.9.6.1, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file