555 lines
17 KiB
Markdown
555 lines
17 KiB
Markdown
# 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 <service-name> && dotnet build
|
|
|
|
# Build specific projects
|
|
dotnet build src/Nocr.<ServiceName>.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
|
|
<packageSourceMapping>
|
|
<packageSource key="musk">
|
|
<package pattern="Nocr.*" />
|
|
</packageSource>
|
|
<packageSource key="nuget.org">
|
|
<package pattern="*" />
|
|
</packageSource>
|
|
</packageSourceMapping>
|
|
```
|
|
|
|
### 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 <submodule-name>
|
|
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 <submodule-name>
|
|
git commit -m "Update <submodule-name> 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:<service>"
|
|
# 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
|
|
``` |