daa59335cc
- 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>
145 lines
7.3 KiB
Markdown
145 lines
7.3 KiB
Markdown
# 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:**
|
|
```bash
|
|
dotnet build
|
|
```
|
|
|
|
**Build individual projects (the CI does this to include SAST via SecurityCodeScan):**
|
|
```bash
|
|
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:**
|
|
```bash
|
|
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --verbosity normal
|
|
```
|
|
|
|
**Run a single test class or method:**
|
|
```bash
|
|
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName~YourTestClassName"
|
|
```
|
|
|
|
**Lint and format:**
|
|
```bash
|
|
dotnet format --verify-no-changes --verbosity diagnostic # CI enforcement
|
|
dotnet format # Apply fixes
|
|
```
|
|
|
|
**Check for vulnerable packages:**
|
|
```bash
|
|
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):**
|
|
```bash
|
|
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):**
|
|
```bash
|
|
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 Renderer** — `TelegramSessionBatchRenderer` 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.
|