Compare commits

...

17 Commits

Author SHA1 Message Date
Toutsu 9deccd3a9d docs: add MIT LICENSE file
PR Checks / test-and-build (pull_request) Successful in 7m7s
Add LICENSE file with MIT License text to repository root.
README.md already references it; the file was missing.

Includes TDD-verified tests ensuring LICENSE exists and contains
MIT License text, and README references it correctly.

Bump version → 1.15.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 16:25:17 +03:00
Toutsu 81d4ec2c97 fix(web): ensure dataprotection-keys dir is owned by app user before switching USER
Deploy Telegram Bot / build-and-push (push) Successful in 3m58s
Deploy Telegram Bot / scan-images (push) Successful in 1m58s
Deploy Telegram Bot / deploy (push) Successful in 13s
The volume mount /app/dataprotection-keys was created under root:root
permissions on the host. When the container restarted with the 1.15.0
image, the non-root app user (uid=1654) could no longer read/write
DataProtection keys, causing every request to fail with
UnauthorizedAccessException and fall back to the generic /Error page.

Add RUN chown during the final Docker stage so the directory ownership
matches the runtime user before USER $APP_UID takes effect.
2026-05-12 16:05:48 +03:00
Toutsu c0a5482e1a Merge pull request #61: infra: add PostgreSQL daily backup via pg_dump with rotation
Deploy Telegram Bot / build-and-push (push) Successful in 3m58s
Deploy Telegram Bot / scan-images (push) Successful in 1m44s
Deploy Telegram Bot / deploy (push) Successful in 14s
- Add db-backup service to compose.yaml (postgres:17-alpine + cron)
- Add pgbackups volume for backup storage
- Add scripts/restore.sh for manual restore from latest backup
- Update .env.example with BACKUP_RETENTION_DAYS and BACKUP_VOLUME_NAME
- Document backup/restore flow in README

Bump version -> 1.15.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:16:11 +03:00
Toutsu 5a18cacb2e fix: address review feedback for backup infrastructure
PR Checks / test-and-build (pull_request) Successful in 6m52s
- compose.yaml: rewrite db-backup to use heredoc script instead of inline
cron command, fixing date escaping and adding temp-file pipeline for
reliable error detection
- compose.yaml: fix pipefail issue by writing pg_dump to tmp file before
compression and rotation
- restore.sh: pass PGPASSWORD explicitly via docker compose exec -e
- restore.sh: use ". .env" with set -a/+a instead of fragile xargs export

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:04:53 +03:00
Toutsu 121272fdfe infra: add PostgreSQL daily backup via pg_dump with rotation
PR Checks / test-and-build (pull_request) Successful in 6m24s
- Add db-backup service to compose.yaml (postgres:17-alpine + cron)
- Add pgbackups volume for backup storage
- Add scripts/restore.sh for manual restore from latest backup
- Update .env.example with BACKUP_RETENTION_DAYS and BACKUP_VOLUME_NAME
- Document backup/restore flow in README

Bump version -> 1.15.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 13:36:47 +03:00
Toutsu ccf11457ca Merge pull request #56: ci: add Trivy security scanning (SAST/SCA) to pipeline
Deploy Telegram Bot / build-and-push (push) Successful in 24s
Deploy Telegram Bot / scan-images (push) Successful in 1m23s
Deploy Telegram Bot / deploy (push) Successful in 11s
- Trivy fs scan (vuln, misconfig, secret) with lock file verification
- Trivy image scan before deploy
- SecurityCodeScan deep SAST via Roslyn analyzers
- NuGet vulnerability audit via dotnet list package
- C# code style linting via dotnet format
2026-05-12 13:07:20 +03:00
Toutsu e492d4fc2d Merge branch 'main' of ssh://git.codeanddice.ru:222/Toutsu/GmRelayBot 2026-05-12 13:07:20 +03:00
Toutsu 11f6b1bcc9 Merge remote-tracking branch 'origin/main' into feature/trivy-security-scan
PR Checks / test-and-build (pull_request) Successful in 5m50s
2026-05-12 12:59:49 +03:00
Toutsu 06d40fdbc8 ci: add deep SAST via SecurityCodeScan Roslyn analyzer
PR Checks / security-scan (pull_request) Failing after 1m17s
PR Checks / test-and-build (pull_request) Successful in 3m27s
- SecurityCodeScan.VS2019 5.6.7 injected into Directory.Build.props
  scans all C# source during every dotnet build
- HIGH/CRITICAL findings fail the build because TreatWarningsAsErrors=true
- No extra CI step needed: analyzer runs inside every build job automatically

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:45:36 +03:00
Toutsu 043ed9ce45 ci: add Trivy security scanning (SAST/SCA) to pipeline
PR Checks / security-scan (pull_request) Failing after 1m15s
PR Checks / test-and-build (pull_request) Successful in 3m24s
- PR checks: filesystem scan with Trivy (vuln, secret, misconfig)
- Deploy pipeline: image scan for bot and web containers before deploy
- Scans entire repository, not filtered file subsets
- Bump version -> 1.14.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:42:32 +03:00
Toutsu 320aba2877 Merge pull request #55: feat(#21): support selected Telegram topics for schedules
Deploy Telegram Bot / build-and-push (push) Successful in 4m1s
Deploy Telegram Bot / deploy (push) Successful in 12s
2026-05-12 12:40:20 +03:00
Toutsu e3fdac15b5 ci: satisfy trivy dockerfile checks
PR Checks / test-and-build (pull_request) Successful in 5m12s
Run runtime images as the built-in non-root .NET app user and install Web runtime OS dependencies with --no-install-recommends.
2026-05-12 12:31:20 +03:00
Toutsu 105a051c2f ci: install latest trivy and verify scan inputs
PR Checks / test-and-build (pull_request) Failing after 6m30s
Enable NuGet lock files so Trivy has dependency targets, fail PR checks when no lock files or language-specific files are detected, and let the installer fetch the latest Trivy release.
2026-05-12 12:20:42 +03:00
Toutsu de9f56c97d feat(#21): support selected telegram topics for schedules
PR Checks / test-and-build (pull_request) Failing after 3m18s
Route new schedules to an existing forum topic when /newsession is sent inside one, create bot-owned topics only from the forum root, and keep group notifications/dashboard updates threaded to the stored topic.

Persist topic ownership so deletion only removes empty bot-created topics, add topic routing tests and smoke coverage, and bump release metadata to 1.14.0.
2026-05-12 12:07:51 +03:00
Hermes Agent 007806a5d8 feat(ci): add C# linter and security scanner to PR checks
Deploy Telegram Bot / build-and-push (push) Successful in 24s
Deploy Telegram Bot / deploy (push) Successful in 10s
- dotnet format --verify-no-changes (C# code style linting)
- dotnet list package --vulnerable --include-transitive (NuGet vulnerability check)
- Trivy filesystem scan (CVE, secrets, dependency scanning)
2026-05-11 20:11:15 +00:00
Toutsu c9627e51a2 chore: ignore .claude and .serena directories 2026-05-11 14:29:04 +03:00
Toutsu 2a3285996e Merge pull request #53: feat(#20): довести RSVP и напоминания до полного набора событий
Deploy Telegram Bot / build-and-push (push) Successful in 3m54s
Deploy Telegram Bot / deploy (push) Successful in 13s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 13:54:56 +03:00
35 changed files with 3232 additions and 55 deletions
+7
View File
@@ -15,3 +15,10 @@ POSTGRES_PASSWORD=StrongPasswordForDatabase
# Локальный порт веб-интерфейса GM-Relay
GMRELAY_WEB_PORT=8080
# === Backup ===
# Сколько дней хранить дампы PostgreSQL (default: 7)
BACKUP_RETENTION_DAYS=7
# Имя Docker volume для резервных копий БД
BACKUP_VOLUME_NAME=game_pgbackups
+27 -2
View File
@@ -6,7 +6,7 @@ on:
- main
env:
VERSION: 1.13.0
VERSION: 1.15.1
jobs:
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
@@ -51,9 +51,34 @@ jobs:
docker push git.codeanddice.ru/toutsu/gmrelay-web:latest
docker push git.codeanddice.ru/toutsu/gmrelay-web:${{ env.VERSION }}
# ЧАСТЬ 1.5: Сканируем собранные образы на уязвимости
scan-images:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Install Trivy
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
- name: Scan Bot image
run: |
trivy image \
--severity HIGH,CRITICAL \
--exit-code 1 \
--format table \
git.codeanddice.ru/toutsu/gmrelay-bot:${{ env.VERSION }}
- name: Scan Web image
run: |
trivy image \
--severity HIGH,CRITICAL \
--exit-code 1 \
--format table \
git.codeanddice.ru/toutsu/gmrelay-web:${{ env.VERSION }}
# ЧАСТЬ 2: Запускаем эти образы на самом сервере
deploy:
needs: build-and-push
needs: scan-images
runs-on: ubuntu-latest # Тот же локальный раннер
steps:
- name: Checkout repository
+48 -3
View File
@@ -20,14 +20,59 @@ jobs:
- name: Restore dependencies
run: dotnet restore
- 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)
- name: Build Bot (compile check, includes SAST)
run: dotnet build src/GmRelay.Bot/GmRelay.Bot.csproj --no-restore
- name: Build Web (compile check)
- 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
run: dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal
BIN
View File
Binary file not shown.
+6 -1
View File
@@ -1,10 +1,15 @@
<Project>
<PropertyGroup>
<Version>1.13.0</Version>
<Version>1.15.1</Version>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SecurityCodeScan.VS2019" Version="5.6.7" PrivateAssets="all" />
</ItemGroup>
</Project>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Toutsu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+40 -2
View File
@@ -4,7 +4,7 @@
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
**Текущая версия:** `v1.10.2`.
**Текущая версия:** `v1.15.0`.
---
@@ -16,7 +16,7 @@
- **⚡ Быстрые повторы расписания**: Для регулярной кампании можно указать одну дату, количество игр и интервал, а бот сам развернёт повторяющийся batch.
- **✋ Интерактивная запись и выход**: Игроки записываются на конкретные даты и самостоятельно снимают запись нажатием одной кнопки.
- **👥 Лимит мест и лист ожидания**: ГМ задаёт максимальный состав, бот не переполняет сессию, автоматически ведёт очередь ожидания и освобождённое место отдаёт первому ожидающему.
- **📁 Поддержка Форумов (Telegram Topics)**: Бот автоматически создает тему во вложенных чатах Telegram под каждую новую пачку игр.
- **📁 Поддержка Форумов (Telegram Topics)**: Если `/newsession` запущен в теме форума Telegram, расписание и групповые уведомления остаются в этой теме; при запуске из корня форума бот создает отдельную тему и сообщает о необходимости прав admin/Manage Topics, если их не хватает.
- **❌ Управление сессиями**: Owner и назначенные co-GM могут создавать, отменять, удалять и переносить игры из Telegram через `/listsessions`; публичный пост записи показывает только кнопки игроков.
- **🔄 Голосование за перенос**: Быстрый поиск свободного места с через свободное недель и кнопками новых времени и дедлайном.
- **🔔 Уведомления**: Игрок получают за 24 часа, напоминание за 1 час, ссылку перед игрой, отмены и переносы; групповые уведомления при этом остаются.
@@ -105,6 +105,44 @@ docker compose up -d
2. Создайте группу через `/newgroup`.
3. Откройте Mini App или Web Dashboard для расширенного управления.
## 💾 Backup и восстановление
Проект включает автоматический ежедневный backup PostgreSQL через сервис `db-backup` в Docker Compose.
### Как это работает
- **Каждый день в 03:00** выполняется `pg_dump` базы `gmrelay_db`.
- Дампы сжимаются (`gzip`) и сохраняются в volume `pgbackups` (`/backups`).
- Формат имени: `gmrelay_db_YYYYMMDD_HHMMSS.sql.gz`.
- Ротация: по умолчанию хранятся последние **7 дней** (настраивается через `BACKUP_RETENTION_DAYS`).
### Проверка бэкапов
```bash
docker compose exec db-backup ls -la /backups
```
### Ручное создание дампа
```bash
docker compose exec db-backup sh -c "pg_dump -h db -U gmrelay -d gmrelay_db | gzip > /backups/gmrelay_db_manual.sql.gz"
```
### Восстановление из бэкапа
```bash
# Использовать последний автоматический бэкап
./scripts/restore.sh
# Или указать конкретный файл
./scripts/restore.sh backups/gmrelay_db_20260512_030000.sql.gz
```
> [!WARNING]
> Восстановление **перезаписывает текущую базу данных**. Убедитесь, что вы понимаете последствия, прежде чем запускать `restore.sh`.
### Переменные окружения (опциональные)
```env
BACKUP_RETENTION_DAYS=7
BACKUP_VOLUME_NAME=game_pgbackups
```
---
## 🗂 Структура репозитория
+36 -2
View File
@@ -16,8 +16,40 @@ services:
timeout: 3s
retries: 10
db-backup:
image: postgres:17-alpine
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
POSTGRES_USER: gmrelay
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
POSTGRES_DB: gmrelay_db
PGPASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
volumes:
- pgbackups:/backups
networks:
- gmrelay
entrypoint: ["sh", "-c"]
command:
- |
cat > /usr/local/bin/backup.sh << 'EOF'
#!/bin/sh
set -e
TMPFILE="/tmp/backup_$$.sql"
pg_dump -h db -U gmrelay -d gmrelay_db > "$TMPFILE"
gzip "$TMPFILE"
mv "$TMPFILE.gz" "/backups/gmrelay_db_$(date +%Y%m%d_%H%M%S).sql.gz"
find /backups -name 'gmrelay_db_*.sql.gz' -type f -mtime +${BACKUP_RETENTION_DAYS} -delete
EOF
chmod +x /usr/local/bin/backup.sh
echo "0 3 * * * /usr/local/bin/backup.sh" | crontab -
crond -f
bot:
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.13.0
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.15.1
restart: always
depends_on:
db:
@@ -30,7 +62,7 @@ services:
- gmrelay
web:
image: git.codeanddice.ru/toutsu/gmrelay-web:1.13.0
image: git.codeanddice.ru/toutsu/gmrelay-web:1.15.1
restart: always
depends_on:
db:
@@ -52,6 +84,8 @@ volumes:
name: ${POSTGRES_VOLUME_NAME:-game_pgdata}
web_keys:
name: ${WEB_KEYS_VOLUME_NAME:-gmrelay_web_keys}
pgbackups:
name: ${BACKUP_VOLUME_NAME:-game_pgbackups}
networks:
gmrelay:
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# GM-Relay PostgreSQL Backup Restore Script
# Usage: ./scripts/restore.sh [backup_file]
# If no file is provided, uses the most recent backup.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Check required env
if [ -z "${POSTGRES_PASSWORD:-}" ]; then
if [ -f "${PROJECT_ROOT}/.env" ]; then
# shellcheck source=/dev/null
set -a
. "${PROJECT_ROOT}/.env"
set +a
fi
fi
if [ -z "${POSTGRES_PASSWORD:-}" ]; then
echo "ERROR: POSTGRES_PASSWORD is not set. Please set it in your environment or .env file."
exit 1
fi
BACKUP_DIR="${PROJECT_ROOT}/backups"
# Determine backup file
if [ $# -ge 1 ]; then
BACKUP_FILE="$1"
else
BACKUP_FILE=$(find "${BACKUP_DIR}" -name 'gmrelay_db_*.sql.gz' -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n1 | cut -d' ' -f2-)
if [ -z "${BACKUP_FILE}" ]; then
echo "ERROR: No backup files found in ${BACKUP_DIR}."
exit 1
fi
fi
if [ ! -f "${BACKUP_FILE}" ]; then
echo "ERROR: Backup file not found: ${BACKUP_FILE}"
exit 1
fi
echo "=================================================="
echo " GM-Relay PostgreSQL Restore"
echo "=================================================="
echo ""
echo "Backup file: ${BACKUP_FILE}"
echo "Database: gmrelay_db"
echo "User: gmrelay"
echo ""
read -p "This will OVERWRITE the current database. Are you sure? [y/N] " CONFIRM
if [[ ! "${CONFIRM}" =~ ^[Yy]$ ]]; then
echo "Restore cancelled."
exit 0
fi
echo ""
echo "Restoring database from ${BACKUP_FILE}..."
# Restore using docker compose exec to leverage the running postgres container
COMPOSE_ARGS="-f ${PROJECT_ROOT}/compose.yaml"
docker compose ${COMPOSE_ARGS} exec -T -e PGPASSWORD="${POSTGRES_PASSWORD}" db psql \
-U gmrelay \
-d gmrelay_db \
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" 2>/dev/null || true
gunzip -c "${BACKUP_FILE}" | docker compose ${COMPOSE_ARGS} exec -T -e PGPASSWORD="${POSTGRES_PASSWORD}" db psql \
-U gmrelay \
-d gmrelay_db
echo ""
echo "=================================================="
echo " Restore completed successfully!"
echo "=================================================="
+687
View File
@@ -0,0 +1,687 @@
{
"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"
}
},
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
},
"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=="
}
}
}
}
+2
View File
@@ -33,5 +33,7 @@ WORKDIR /app
# Копируем только AOT-результаты из билда
COPY --from=build /app/publish .
USER $APP_UID
# Запуск скомпилированного AOT бинарного файла напрямую
ENTRYPOINT ["./GmRelay.Bot"]
@@ -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);
}
@@ -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);
@@ -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);
@@ -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<CancelSessionInfoDto>(
"""
@@ -117,9 +118,14 @@ public sealed class CancelSessionHandler(
ct);
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия отменена!", cancellationToken: ct);
// Опционально: написать отдельное сообщение в чат
await bot.SendMessage(command.ChatId, $"❌ <b>Внимание!</b> Сессия \"{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: $"❌ <b>Внимание!</b> Сессия \"{System.Net.WebUtility.HtmlEncode(session.Title)}\" отменена.",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
cancellationToken: ct);
var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
if (mode.ShouldSendDirectMessages())
@@ -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<Guid>(
"""
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
},
@@ -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<int>(
"SELECT COUNT(*) FROM sessions WHERE batch_id = @BatchId",
new { BatchId = session.BatchId }, transaction);
var remainingInTopic = session.ThreadId.HasValue
? await connection.ExecuteScalarAsync<int>(
"""
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;
}
@@ -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Используйте формат:\n<code>25.04.2026 19:30\n26.04.2026 18:00\nДедлайн: 25.04.2026 12:00</code>",
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📅 Новое время: <b>{newTime.ToOffset(TimeSpan.FromHours(3)).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"))}</b> (МСК)\n\n<i>Участников нет — голосование не требуется.</i>",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
cancellationToken: ct);
@@ -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}» и дедлайн голосования.
@@ -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);
@@ -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);
}
@@ -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);
@@ -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;
+689
View File
@@ -0,0 +1,689 @@
{
"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"
}
},
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
},
"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=="
}
}
}
}
@@ -0,0 +1,181 @@
{
"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)"
}
},
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
},
"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"
}
}
}
}
}
+1 -1
View File
@@ -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();
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"dependencies": {
"net10.0": {
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
}
}
}
}
@@ -56,7 +56,7 @@
</button>
</form>
<div class="nav-version">v1.13.0</div>
<div class="nav-version">v1.15.1</div>
</div>
</Authorized>
<NotAuthorized>
@@ -79,4 +79,4 @@
private void ToggleMenu() => isOpen = !isOpen;
private void CloseMenu() => isOpen = false;
}
}
+3 -1
View File
@@ -18,8 +18,10 @@ 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 .
RUN mkdir -p /app/dataprotection-keys && chown -R $APP_UID:$APP_UID /app/dataprotection-keys
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
USER $APP_UID
ENTRYPOINT ["dotnet", "GmRelay.Web.dll"]
+38 -17
View File
@@ -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" +
$"👥 Мест: <b>{(maxPlayers.HasValue ? maxPlayers.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : "без лимита")}</b>";
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,
$"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
chatId: session.TelegramChatId,
messageThreadId: session.ThreadId,
text: $"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{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,
$"🚪 <b>{System.Net.WebUtility.HtmlEncode(participant.DisplayName)}</b> удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
chatId: session.TelegramChatId,
messageThreadId: session.ThreadId,
text: $"🚪 <b>{System.Net.WebUtility.HtmlEncode(participant.DisplayName)}</b> удален(а) из сессии «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
if (promoted is not null)
{
await bot.SendMessage(
session.TelegramChatId,
$"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{System.Net.WebUtility.HtmlEncode(session.Title)}».",
chatId: session.TelegramChatId,
messageThreadId: session.ThreadId,
text: $"⬆️ <b>{System.Net.WebUtility.HtmlEncode(promoted.DisplayName)}</b> переведен(а) из листа ожидания в основной состав «{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(
$"🗓 Новое начало: <b>{firstScheduledAt.FormatMoscow()}</b> (МСК)\n" +
$"↔️ Шаг: <b>{intervalDays} дн.</b>";
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<Guid>(
"""
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
},
+250
View File
@@ -0,0 +1,250 @@
{
"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=="
},
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
},
"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"
}
}
}
}
@@ -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<string> 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}'.");
}
}
@@ -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);
}
}
@@ -0,0 +1,46 @@
using System;
using System.IO;
using Xunit;
namespace GmRelay.Bot.Tests.Project;
public class LicenseFileTests
{
private static string GetRepoRoot()
{
var dir = AppContext.BaseDirectory;
while (!string.IsNullOrEmpty(dir) && !File.Exists(Path.Combine(dir, "Directory.Build.props")))
{
dir = Directory.GetParent(dir)?.FullName;
}
return dir ?? throw new InvalidOperationException("Could not find repo root");
}
[Fact]
public void LicenseFile_ExistsInRepoRoot()
{
var repoRoot = GetRepoRoot();
var licensePath = Path.Combine(repoRoot, "LICENSE");
Assert.True(File.Exists(licensePath), "LICENSE file should exist in repo root");
}
[Fact]
public void LicenseFile_ContainsMitLicenseText()
{
var repoRoot = GetRepoRoot();
var licensePath = Path.Combine(repoRoot, "LICENSE");
Assert.True(File.Exists(licensePath), "LICENSE file should exist");
var content = File.ReadAllText(licensePath);
Assert.Contains("MIT License", content);
}
[Fact]
public void Readme_ReferencesLicenseFile()
{
var repoRoot = GetRepoRoot();
var readmePath = Path.Combine(repoRoot, "README.md");
Assert.True(File.Exists(readmePath), "README.md should exist");
var content = File.ReadAllText(readmePath);
Assert.Contains("./LICENSE", content);
}
}
+771
View File
@@ -0,0 +1,771 @@
{
"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"
}
},
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
"resolved": "5.6.7",
"contentHash": "WIE9RJswdSc2j+rLz2gW6U+gMUjMHzY2j7C/CL8/R2olXNM/+twarfMnWqm+rZodDBvaYDApJyxM8mVYf9FGrQ=="
},
"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.15.0, )",
"GmRelay.Shared": "[1.15.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.15.0, )",
"GmRelay.Shared": "[1.15.0, )",
"Npgsql": "[10.0.2, )",
"Telegram.Bot": "[22.9.6.1, )"
}
}
}
}
}