# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a microservices-based Telegram bot system for text monitoring and notifications. The system consists of 4 main services that communicate via RabbitMQ: - **telegram-listener**: Scans open Telegram channels and chats for messages - **telegram-client**: Bot interface for user interactions - **text-matcher**: Matches incoming messages against user subscriptions - **users**: Manages users and their preferences **Important:** This repository is the parent project containing all services as **git submodules**. Each service (telegram-listener, telegram-client, text-matcher, users) is a separate repository added as a submodule. Git tags are created at the parent repository level, which triggers the Drone CI/CD pipeline to automatically build and publish NuGet packages with versions based on the git tag. ## Architecture The services follow a message bus pattern using RabbitMQ for async communication: - TelegramListener publishes: - **MessageReceived** events (for new messages) - **MessageEdited** events (for edited messages) - TextMatcher subscribes to MessageReceived and MessageEdited, stores match history in database, and publishes: - TextSubscriptionMatched events (for first-time matches) - TextSubscriptionUpdated events (for updates to previously matched messages) - TelegramClient subscribes to both TextSubscriptionMatched and TextSubscriptionUpdated events and notifies users **Note:** WTelegram sends both `UpdateNewChannelMessage` and `UpdateEditChannelMessage` for the same message. TelegramListener publishes separate events to avoid duplicate notifications downstream. Each service follows Clean Architecture with separate projects for: - **Host** (API/entry point) - ASP.NET Core application, controllers, startup configuration - **AppServices** (business logic) - Use cases, business logic, command/query handlers - **Core** (shared utilities) - Domain entities, interfaces, shared logic - **Api.Contracts** (REST API contracts) - DTOs, request/response models for REST endpoints (published as NuGet) - **Async.Api.Contracts** (event contracts) - Event DTOs for RabbitMQ messaging (published as NuGet) - **Persistence** (database layer) - EF Core DbContext, repositories, data access (text-matcher, users only) - **Migrator** (database migrations) - EF Core migrations, migration scripts (text-matcher, users only) **Project Structure Examples:** - telegram-listener: Host, AppServices, Core, Async.Api.Contracts (no database) - telegram-client: Host, AppServices, Core (no database, no published contracts) - text-matcher: Host, AppServices, Core, Api.Contracts, Async.Api.Contracts, Persistence, Migrator - users: Host, AppServices, Core, Api.Contracts, Persistence, Migrator ## Development Commands ### Running the System ```bash # IMPORTANT: Before building with Docker Compose, prepare the build environment ./prepare-build.sh # Start all services with Docker Compose docker-compose up # Start individual services for development (no preparation needed) cd telegram-client && dotnet run --project src/Nocr.TelegramClient.Host cd telegram-listener && dotnet run --project src/Nocr.TelegramListener.Host cd text-matcher && dotnet run --project src/Nocr.TextMatcher.Host cd users && dotnet run --project src/Nocr.Users.Host ``` **Note:** The `prepare-build.sh` script copies `nuget.config` to all submodule roots, which is required for Docker builds. This step is automatic in CI/CD but must be run manually for local Docker Compose builds. ### Building ```bash # Build individual services cd && dotnet build # Build specific projects dotnet build src/Nocr..Host ``` ### Testing ```bash # Run all tests in text-matcher cd text-matcher && dotnet test # Run tests for a specific project cd text-matcher && dotnet test tests/Nocr.TextMatcher.AppServices.UnitTests/Nocr.TextMatcher.AppServices.UnitTests.csproj # Run tests with verbose output cd text-matcher && dotnet test --verbosity detailed # Run tests with code coverage (if configured) cd text-matcher && dotnet test --collect:"XPlat Code Coverage" ``` ### Database Migrations For text-matcher and users services: ```bash # Add new migration (from service root directory) cd text-matcher && ./src/Nocr.TextMatcher.Migrator/AddMigration.sh MyMigrationName cd users && ./src/Nocr.Users.Migrator/AddMigration.sh MyMigrationName # Apply migrations (handled automatically by migrator containers in docker-compose) ``` ### Working with Git Submodules ```bash # Initialize submodules after cloning the repository git submodule update --init --recursive # Update all submodules to their latest commits on main ./update-submodules.sh # Commit and push changes to all submodules at once ./commit-all.sh "Your commit message" # Update a single submodule cd telegram-listener git pull origin main cd .. git add telegram-listener git commit -m "Update telegram-listener submodule" # Check status of all submodules git submodule status ``` ## Configuration **📖 See [CONFIGURATION.md](CONFIGURATION.md) for detailed configuration guide.** ### Quick Start The system uses ASP.NET Core's layered configuration with **Environment Variables having highest priority**. **Configuration priority (lowest → highest):** 1. `appsettings.json` (base settings, committed) 2. `appsettings.{Environment}.json` (environment-specific, some committed via `.example` files) 3. User Secrets (Development only) 4. **Environment Variables (ALWAYS WINS)** ### Three Deployment Modes 1. **VS Code (Local Development)** - Copy `appsettings.Development.json.example` → `appsettings.Development.json` in each service - Or use environment variables in `.vscode/launch.json` - Start infrastructure: `docker-compose up nocr-rabbitmq nocr-text-matcher-db nocr-users-db -d` 2. **Docker Compose (Local Full Stack)** - Copy `.nocr.env.example` → `.nocr.env` in project root - Fill in your Telegram API credentials and Bot token - Run: `docker-compose up` 3. **Kubernetes (Production)** - Create K8s Secrets (do NOT use `.nocr.env`) - Reference secrets in deployment manifests via `envFrom` ### Debug Mode Enable configuration debug logging on startup: ```bash export NOCR_DEBUG_MODE=true ``` This prints masked configuration values to help troubleshoot issues. ### Important Notes - ❌ **Never commit secrets** - use `.example` files as templates - ✅ Environment variables override all appsettings files - ✅ All services use `.example` files for documentation - ✅ Docker Compose uses `.nocr.env` file (gitignored) ## Service Ports When running with docker-compose: - telegram-client: 5050 (http://localhost:5050/health) - telegram-listener: 5040 (http://localhost:5040/health) - text-matcher: 5041 (http://localhost:5041/health) - users: 5042 (http://localhost:5042/health) - RabbitMQ: 5672 (AMQP), 15672 (Management UI - http://localhost:15672, admin/admin) - MariaDB: 3316 (text-matcher), 3326 (users) ## Key Technologies - **.NET 8** - All services built on .NET 8 - **ASP.NET Core Web APIs** - REST endpoints and hosting - **Entity Framework Core with MariaDB** - ORM for text-matcher and users databases - **WTelegramClient** - MTProto API client for telegram-listener - **Telegram.Bot** - Bot API client for telegram-client - **Rebus** - Message bus abstraction over RabbitMQ - **RabbitMQ** - Message broker for async event-driven communication - **Docker & Docker Compose** - Local development and containerization - **Kaniko** - Container image building in CI/CD - **Drone CI** - CI/CD pipeline on Kubernetes ## NuGet Package Management The project uses **Central Package Management (CPM)** with **Package Source Mapping** to manage NuGet dependencies: ### Package Sources - **nuget.org**: All public packages (Microsoft.*, Serilog.*, etc.) - **musk** (private): Internal `Nocr.*` contract packages ### Configuration The `nuget.config` file in the project root defines package source mapping: ```xml ``` ### How It Works 1. **Local Development**: Copy `nuget.config` to each submodule root when needed 2. **CI/CD**: Drone automatically copies `nuget.config` to `/root/.nuget/NuGet/NuGet.Config` 3. **Docker Builds**: Kaniko copies `nuget.config` to submodule root before building This eliminates NuGet warning NU1507 and ensures consistent package resolution across all environments. ## CI/CD Pipeline **📖 See [_deploy/README.md](_deploy/README.md) for full CI/CD documentation.** The project uses Drone CI with 5 specialized pipelines: ### 1. Feature Validation (`feature/*`, `fix/*` branches) **Purpose**: Test feature branches before merging to main **How to trigger**: ```bash # Create feature branch and push git checkout -b feature/my-new-feature # ... make changes ... git add . git commit -m "Add new feature" git push origin feature/my-new-feature ``` **What happens**: - Runs build + tests with Testcontainers support - Provides fast feedback before merge - Duration: ~3-5 minutes ### 2. Main Validation (`main` branch) **Purpose**: Ensure main branch stays healthy after merge **How to trigger**: ```bash # Merge feature to main git checkout main git merge feature/my-new-feature git push origin main ``` **What happens**: - Same checks as feature validation - Ensures main branch builds and tests pass - Duration: ~3-5 minutes ### 3. Contracts-Only Publish **Purpose**: Publish NuGet contracts without building Docker images (fast iteration) **How to trigger**: ```bash # Update contracts in a submodule cd telegram-listener # ... update Async.Api.Contracts ... git add . && git commit -m "Add MessageEdited event" git push # From parent repo, tag with contracts_only marker cd .. git add telegram-listener git commit -m "contracts_only:telegram_listener - Add MessageEdited event" git tag 0.7.41 git push && git push --tags ``` **What happens**: - Publishes only the specified service's NuGet contracts - Skips Docker image builds - Duration: ~2 minutes **Services**: `telegram_listener`, `text_matcher`, `users` ### 4. Full Release **Purpose**: Complete release - contracts + images + deployment **How to trigger**: ```bash # Make your changes, commit to main git add . git commit -m "Implement new feature" git push # Tag for full release (no special markers in commit message) git tag v1.3.0 git push --tags ``` **What happens**: 1. Publishes all NuGet contracts (parallel) 2. Builds all Docker images with Kaniko (parallel, after contracts) 3. Deploys to Kubernetes (automatic for `v*` tags) - Duration: ~8-10 minutes ### 5. Deploy-Only **Purpose**: Deploy existing images without rebuilding (rollbacks, hotfixes) **How to trigger**: ```bash # Deploy already-built images (e.g., rollback to previous version) git commit --allow-empty -m "deploy_only: Rollback to v1.2.9" git tag deploy-v1.2.9 git push && git push --tags # Or deploy latest images git commit --allow-empty -m "deploy_only: Deploy latest" git tag deploy-latest git push && git push --tags ``` **What happens**: - Skips building entirely - Deploys specified tag's images (or `latest`) - Duration: ~1 minute ## Development Workflows ### Making Changes to a Single Service When working on a specific service (e.g., telegram-listener): ```bash # 1. Navigate to the submodule cd telegram-listener # 2. Create a feature branch in the submodule git checkout -b feature/new-feature # 3. Make your changes to the code # ... edit files ... # 4. Test locally (if tests exist) dotnet test # 5. Commit and push in the submodule git add . git commit -m "Add new feature" git push origin feature/new-feature # 6. Return to parent repo and update submodule reference cd .. git add telegram-listener git commit -m "Update telegram-listener: Add new feature" git push origin main ``` ### Updating Contract Packages When you change event contracts (Async.Api.Contracts) or REST contracts (Api.Contracts): ```bash # 1. Make changes to contracts in the submodule cd text-matcher # ... edit Nocr.TextMatcher.Async.Api.Contracts ... git add . && git commit -m "Add new event contract" git push origin main # 2. Return to parent repo and publish contracts only cd .. git add text-matcher git commit -m "contracts_only:text_matcher - Add new event contract" git tag 0.8.6 git push && git push --tags # 3. Other services can now reference the new contract version # Update their .csproj or Directory.Packages.props to use version 0.8.6 ``` ### Working Across Multiple Services When implementing a feature that spans multiple services: ```bash # 1. Update each submodule in sequence cd telegram-listener git checkout -b feature/cross-service-feature # ... make changes ... git commit -m "Part 1: Update listener" git push origin feature/cross-service-feature cd ../text-matcher git checkout -b feature/cross-service-feature # ... make changes ... git commit -m "Part 2: Update matcher" git push origin feature/cross-service-feature # 2. Update parent repo to reference all changes cd .. git add telegram-listener text-matcher git commit -m "Implement cross-service feature" git push origin main # 3. Tag for full release git tag v1.5.0 git push --tags ``` ## Common CI/CD Workflows ### Test a Feature Branch ```bash git checkout -b feature/add-logging # ... make changes ... git add . && git commit -m "Add structured logging" git push origin feature/add-logging # ✅ Drone runs feature-validation pipeline ``` ### Publish Contracts After API Changes ```bash cd text-matcher # Update Nocr.TextMatcher.Async.Api.Contracts git add . && git commit -m "Add UserDeleted event" git push cd .. git add text-matcher git commit -m "contracts_only:text_matcher - Add UserDeleted event" git tag 0.8.5 git push && git push --tags # ✅ Drone publishes text-matcher contracts to NuGet ``` ### Full Release Workflow ```bash # Work on main branch git add . git commit -m "Implement payment processing" git push # ✅ Drone runs main-validation # Tag for release git tag v2.0.0 git push --tags # ✅ Drone runs full-release pipeline: # 1. Publishes all contracts # 2. Builds all Docker images # 3. Deploys to Kubernetes ``` ### Emergency Rollback ```bash # Rollback to last known good version git commit --allow-empty -m "deploy_only: Emergency rollback to v1.9.5" git tag rollback-v1.9.5 git push && git push --tags # ✅ Drone deploys v1.9.5 images immediately (~1 minute) ``` ### Deployment Scripts Located in `_deploy/scripts/`: - **deploy.sh** - Deploy services to Kubernetes with health checks - **rollback.sh** - Rollback deployments to previous version - **health-check.sh** - Check health of all services ### Key Optimizations - **Shared NuGet cache** across pipeline steps (~60% faster builds) - **Parallel execution** for independent operations - **Proper dependency order**: Contracts → Images → Deploy - **Kaniko layer caching** for faster Docker builds - **Docker-in-Docker** support for Testcontainers in tests ## Troubleshooting ### Submodule Issues **Problem:** Submodule directories are empty after cloning ```bash # Solution: Initialize submodules git submodule update --init --recursive ``` **Problem:** Submodule is in detached HEAD state ```bash # Solution: Check out the main branch in the submodule cd git checkout main git pull origin main cd .. ``` **Problem:** Changes in submodule not reflected in parent repo ```bash # Solution: Update submodule reference in parent cd .. # Return to parent repo git add git commit -m "Update reference" ``` ### Build Issues **Problem:** NuGet package restore fails with NU1507 warning ```bash # Solution: Copy nuget.config to submodule root cp nuget.config telegram-listener/ cd telegram-listener && dotnet restore ``` **Problem:** Cannot find Nocr.* contract packages ```bash # Solution: Ensure you have access to the private NuGet feed # Check nuget.config has the correct source configuration # Verify credentials for the "musk" package source ``` ### Docker Compose Issues **Problem:** Service cannot connect to RabbitMQ ```bash # Solution: Use the Docker network hostname, not localhost # In docker-compose environment: nocr-rabbitmq:5672 # In local development: localhost:5672 ``` **Problem:** Database connection fails ```bash # Solution: Wait for database health checks to pass # Check docker-compose logs for database container docker-compose logs nocr-text-matcher-db # Database takes 10-30 seconds to be ready on first start ``` ### CI/CD Issues **Problem:** Pipeline doesn't trigger on tag ```bash # Solution: Ensure tag matches expected patterns # Feature validation: feature/*, fix/*, issues/* # Main validation: main branch # Contracts: Tag with commit message containing "contracts_only:" # Full release: Any tag without special markers # Deploy-only: Tag with commit message containing "deploy_only:" ``` **Problem:** Docker image build fails in Kaniko ```bash # Solution: Check if nuget.config is correctly copied # Verify _deploy/deploy.sh contains nuget.config copy step # Check Drone logs for nuget.config presence in build context ```