Files
Toutsu daa59335cc fix(discord): resolve permission checking for /newsession command
- DiscordPermissionChecker: removed dead-code userRoles overload;
  now only uses resolvedPermissions bitflag (Administrator = 0x8).
- DiscordNewSessionCommand: computes resolved permissions from guild
  user roles via Context.Guild.Users[Id].RoleIds + guild.Roles.
- DiscordNewSessionHandler: updated signature to accept ulong
  resolvedPermissions instead of unused userRoles.
- Added ILogger to command for diagnostics on unexpected errors.
- Added test: regular user with ManageServer (but not Admin) is rejected.

Refs issue #28

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:30:25 +03:00

7.3 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build, Test, and Development Commands

This is a .NET 10 solution using the modern XML-based .slnx format. The global SDK version is 10.0.100 with rollForward: latestFeature.

Build the solution:

dotnet build

Build individual projects (the CI does this to include SAST via SecurityCodeScan):

dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore
dotnet build src/GmRelay.Bot/GmRelay.Bot.csproj --no-restore
dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore

Run all tests:

dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal

Run a single test class or method:

dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName~YourTestClassName"

Lint and format:

dotnet format --verify-no-changes --verbosity diagnostic   # CI enforcement
dotnet format                                                  # Apply fixes

Check for vulnerable packages:

dotnet list package --vulnerable --include-transitive

Restore with lock file verification: The repo enforces RestorePackagesWithLockFile=true. After adding or updating packages, commit the updated packages.lock.json files or the Trivy scan in CI will fail.

Run locally with Aspire (dev orchestration):

dotnet run --project src/GmRelay.AppHost/GmRelay.AppHost.csproj

This automatically starts PostgreSQL in a container, the Bot, and the Web dashboard.

Run locally with Docker Compose (production-like):

cp .env.example .env
# Edit .env with your TELEGRAM_BOT_TOKEN, TELEGRAM_BOT_USERNAME, POSTGRES_PASSWORD
docker compose up -d

High-Level Architecture

Project Roles and Runtime Model

Project Runtime Key Trait
GmRelay.Bot Microsoft.NET.Sdk.Worker Native AOT binary. Telegram long polling bot + stateless scheduler.
GmRelay.Web Microsoft.NET.Sdk.Web Blazor Server dashboard. Cookie auth via Telegram Login Widget / Mini App initData.
GmRelay.Shared Plain library Domain models and platform-neutral view builders. Must not depend on Telegram.Bot.
GmRelay.ServiceDefaults Aspire shared project OpenTelemetry, health checks, HTTP resilience. Referenced by both Bot and Web.
GmRelay.AppHost Aspire orchestrator Dev-only. Spins up PostgreSQL and wires Bot + Web with service discovery.

Important: README.md references GmRelay.Migrator and GmRelay.Worker, but these projects do not exist. Migrations (DbUp) and background workers (BackgroundService) live inside GmRelay.Bot.

Vertical Slice Architecture with Explicit DI

Each use case is a self-contained vertical slice: a C# record (Command/Query) + Handler class with all logic (SQL, Telegram API calls, validation). There are no abstract repository interfaces or service layers.

Because the Bot is compiled as Native AOT (PublishAot=true, EnableTrimAnalyzer=true), all DI registrations are explicit in src/GmRelay.Bot/Program.cs. There is no assembly scanning or reflection-based discovery. When adding a new handler, you must register it manually in Program.cs.

Database Access: Npgsql + Dapper.AOT + DbUp

No EF Core — it is incompatible with Native AOT. The stack is:

  • Npgsql ADO.NET for connections.
  • Dapper 2.1.72 with Dapper.AOT 1.0.48 for compile-time source-generated mapping (AOT-safe).
  • DbUp 7.0.1 for migrations. SQL scripts are embedded resources in src/GmRelay.Bot/Migrations/ (V001 through V015).
  • DbMigrator.MigrateUp() runs on every Bot startup.

Both Bot and Web share the same PostgreSQL database. Web registers NpgsqlDataSource via builder.AddNpgsqlDataSource("gmrelaydb") (Aspire integration), while Bot registers it manually to avoid reflection-based Aspire configuration at AOT time.

Platform-Neutral Rendering (ADR-002)

Rendering is split into two stages:

  1. View Builder (GmRelay.Shared) — platform-agnostic view model from domain DTOs.
  2. Platform RendererTelegramSessionBatchRenderer lives in both GmRelay.Bot and GmRelay.Web (temporary duplication until a third Telegram consumer justifies extracting GmRelay.Shared.Telegram).

This means GmRelay.Shared must remain free of Telegram.Bot types. If you need to add rendering logic that produces InlineKeyboardMarkup, it belongs in the Bot or Web project, not Shared.

Stateless Scheduling

The session scheduler (SessionSchedulerService) is a BackgroundService with a PeriodicTimer(TimeSpan.FromMinutes(1)). On each tick it queries PostgreSQL for sessions needing action (T-24h confirmation, T-5min join link) and updates their status. There is no in-memory state — the database is the single source of truth. This design was chosen specifically because Quartz.NET is incompatible with Native AOT.

Health Checks

  • Bot: Custom BotHealthCheckHostedService listens on port 8081. The Docker health check hits localhost:8081/health.
  • Web: Standard ASP.NET Core health checks on /health (JSON response with status and timestamp) and /alive (liveness probe tag filter). Exposed via GmRelay.ServiceDefaults.

Authentication and Security

  • Telegram Login Widget and Mini App initData verification via HMAC-SHA256. Cookie auth is hardened (HttpOnly, SecurePolicy.Always, SameSite.Strict).
  • Web Data Protection keys are persisted to /app/dataprotection-keys (Docker volume web_keys).
  • Security headers middleware (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) is applied globally in Web.
  • SecurityCodeScan.VS2019 (5.6.7) is included in all projects via Directory.Build.props for SAST at build time.
  • Connection string passwords are redacted in logs via SecretRedactor.

CI/CD Pipeline

.gitea/workflows/pr-checks.yml runs on every PR to main:

  1. dotnet restore
  2. Verify packages.lock.json files exist for Trivy
  3. dotnet format --verify-no-changes
  4. dotnet list package --vulnerable
  5. Trivy filesystem scan (vuln,misconfig,secret, HIGH/CRITICAL)
  6. Build Shared → Bot → Web
  7. Run tests

.gitea/workflows/deploy.yml runs on push to main:

  1. Build and push gmrelay-bot and gmrelay-web images to git.codeanddice.ru/toutsu/...
  2. Trivy image scan on both images (HIGH/CRITICAL, exit-code 1)
  3. Create .env from secrets and run docker compose up -d

Environment Configuration

Key environment variables (see .env.example):

  • TELEGRAM_BOT_TOKEN, TELEGRAM_BOT_USERNAME, TELEGRAM_MINI_APP_URL
  • POSTGRES_PASSWORD
  • GMRELAY_WEB_PORT (default 8080)
  • ConnectionStrings__gmrelaydb — used by both Bot and Web

The Bot reads config as Telegram:BotToken (colon) which maps from Telegram__BotToken (double underscore) via environment variables.

Docker Images

  • Bot: Multi-stage Dockerfile. Build stage uses sdk:10.0-noble with clang and zlib1g-dev for AOT compilation. Final stage uses runtime-deps:10.0-noble. Exposes 8081.
  • Web: Multi-stage Dockerfile. Build stage uses sdk:10.0-noble. Final stage uses aspnet:10.0-noble with libgssapi-krb5-2 and wget. Exposes 8080.

Both images are built for multi-arch (linux/amd64, linux/arm64) to support Raspberry Pi 5 (ARM64) deployment.