Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c49f2df0 | |||
| 9deccd3a9d | |||
| 81d4ec2c97 | |||
| c0a5482e1a | |||
| 5a18cacb2e | |||
| 121272fdfe | |||
| ccf11457ca | |||
| e492d4fc2d |
@@ -15,3 +15,10 @@ POSTGRES_PASSWORD=StrongPasswordForDatabase
|
|||||||
|
|
||||||
# Локальный порт веб-интерфейса GM-Relay
|
# Локальный порт веб-интерфейса GM-Relay
|
||||||
GMRELAY_WEB_PORT=8080
|
GMRELAY_WEB_PORT=8080
|
||||||
|
|
||||||
|
# === Backup ===
|
||||||
|
# Сколько дней хранить дампы PostgreSQL (default: 7)
|
||||||
|
BACKUP_RETENTION_DAYS=7
|
||||||
|
|
||||||
|
# Имя Docker volume для резервных копий БД
|
||||||
|
BACKUP_VOLUME_NAME=game_pgbackups
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: 1.14.0
|
VERSION: 1.15.1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.14.0</Version>
|
<Version>1.15.1</Version>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
|
||||||
|
|
||||||
**Текущая версия:** `v1.14.0`.
|
**Текущая версия:** `v1.15.0`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -105,6 +105,44 @@ docker compose up -d
|
|||||||
2. Создайте группу через `/newgroup`.
|
2. Создайте группу через `/newgroup`.
|
||||||
3. Откройте Mini App или Web Dashboard для расширенного управления.
|
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
@@ -16,8 +16,40 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 10
|
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:
|
bot:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.14.0
|
image: git.codeanddice.ru/toutsu/gmrelay-bot:1.15.1
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -30,7 +62,7 @@ services:
|
|||||||
- gmrelay
|
- gmrelay
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-web:1.14.0
|
image: git.codeanddice.ru/toutsu/gmrelay-web:1.15.1
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -52,6 +84,8 @@ volumes:
|
|||||||
name: ${POSTGRES_VOLUME_NAME:-game_pgdata}
|
name: ${POSTGRES_VOLUME_NAME:-game_pgdata}
|
||||||
web_keys:
|
web_keys:
|
||||||
name: ${WEB_KEYS_VOLUME_NAME:-gmrelay_web_keys}
|
name: ${WEB_KEYS_VOLUME_NAME:-gmrelay_web_keys}
|
||||||
|
pgbackups:
|
||||||
|
name: ${BACKUP_VOLUME_NAME:-game_pgbackups}
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
gmrelay:
|
gmrelay:
|
||||||
|
|||||||
@@ -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 "=================================================="
|
||||||
@@ -83,6 +83,12 @@
|
|||||||
"System.IO.Hashing": "10.0.3"
|
"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": {
|
"Aspire.Hosting": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "13.2.1",
|
"resolved": "13.2.1",
|
||||||
|
|||||||
@@ -95,6 +95,12 @@
|
|||||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.0"
|
"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": {
|
"Telegram.Bot": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[22.9.5.3, )",
|
"requested": "[22.9.5.3, )",
|
||||||
|
|||||||
@@ -66,6 +66,12 @@
|
|||||||
"OpenTelemetry.Api": "[1.15.3, 2.0.0)"
|
"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": {
|
"Microsoft.Extensions.AmbientMetadata.Application": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "10.2.0",
|
"resolved": "10.2.0",
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net10.0": {}
|
"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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="nav-version">v1.14.0</div>
|
<div class="nav-version">v1.15.1</div>
|
||||||
</div>
|
</div>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble AS final
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends 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 .
|
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
|
ENV ASPNETCORE_URLS=http://+:8080
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER $APP_UID
|
USER $APP_UID
|
||||||
|
|||||||
@@ -32,6 +32,12 @@
|
|||||||
"resolved": "10.0.2",
|
"resolved": "10.0.2",
|
||||||
"contentHash": "q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg=="
|
"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": {
|
"Telegram.Bot": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[22.9.6.1, )",
|
"requested": "[22.9.6.1, )",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,12 @@
|
|||||||
"Microsoft.TestPlatform.TestHost": "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": {
|
"xunit": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.9.3, )",
|
"requested": "[2.9.3, )",
|
||||||
@@ -726,8 +732,8 @@
|
|||||||
"Aspire.Npgsql": "[13.2.2, )",
|
"Aspire.Npgsql": "[13.2.2, )",
|
||||||
"Dapper": "[2.1.72, )",
|
"Dapper": "[2.1.72, )",
|
||||||
"Dapper.AOT": "[1.0.48, )",
|
"Dapper.AOT": "[1.0.48, )",
|
||||||
"GmRelay.ServiceDefaults": "[1.14.0, )",
|
"GmRelay.ServiceDefaults": "[1.15.0, )",
|
||||||
"GmRelay.Shared": "[1.14.0, )",
|
"GmRelay.Shared": "[1.15.0, )",
|
||||||
"Microsoft.Extensions.Hosting": "[10.0.5, )",
|
"Microsoft.Extensions.Hosting": "[10.0.5, )",
|
||||||
"Npgsql": "[10.0.2, )",
|
"Npgsql": "[10.0.2, )",
|
||||||
"Telegram.Bot": "[22.9.5.3, )",
|
"Telegram.Bot": "[22.9.5.3, )",
|
||||||
@@ -754,8 +760,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Aspire.Npgsql": "[13.2.2, )",
|
"Aspire.Npgsql": "[13.2.2, )",
|
||||||
"Dapper": "[2.1.72, )",
|
"Dapper": "[2.1.72, )",
|
||||||
"GmRelay.ServiceDefaults": "[1.14.0, )",
|
"GmRelay.ServiceDefaults": "[1.15.0, )",
|
||||||
"GmRelay.Shared": "[1.14.0, )",
|
"GmRelay.Shared": "[1.15.0, )",
|
||||||
"Npgsql": "[10.0.2, )",
|
"Npgsql": "[10.0.2, )",
|
||||||
"Telegram.Bot": "[22.9.6.1, )"
|
"Telegram.Bot": "[22.9.6.1, )"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user