Skip to content

Commit afb5df3

Browse files
Merge pull request #116 from blockful/dev
Dev
2 parents 6b1c4ee + d5fbfc7 commit afb5df3

120 files changed

Lines changed: 6412 additions & 3232 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy.yaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [dev, main]
6+
7+
jobs:
8+
changes:
9+
runs-on: ubuntu-latest
10+
outputs:
11+
triggers: ${{ steps.filter.outputs.triggers }}
12+
dispatcher: ${{ steps.filter.outputs.dispatcher }}
13+
subscriptions: ${{ steps.filter.outputs.subscriptions }}
14+
consumers: ${{ steps.filter.outputs.consumers }}
15+
steps:
16+
- uses: actions/checkout@v3
17+
- uses: dorny/paths-filter@v2
18+
id: filter
19+
with:
20+
base: ${{ github.ref_name }}
21+
filters: |
22+
triggers:
23+
- 'apps/logic-system/src/**/*.ts'
24+
- 'packages/**/*.ts'
25+
dispatcher:
26+
- 'apps/dispatcher/src/**/*.ts'
27+
- 'packages/**/*.ts'
28+
subscriptions:
29+
- 'apps/subscription-server/src/**/*.ts'
30+
consumers:
31+
- 'apps/consumers/src/**/*.ts'
32+
- 'packages/**/*.ts'
33+
34+
deploy:
35+
runs-on: ubuntu-latest
36+
needs: changes
37+
container: ghcr.io/railwayapp/cli:latest
38+
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'dev' }}
39+
env:
40+
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
41+
steps:
42+
- uses: actions/checkout@v3
43+
- name: Determine environment
44+
id: env
45+
run: |
46+
if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.base_ref }}" == "main" ]]; then
47+
echo "environment=production" >> $GITHUB_OUTPUT
48+
else
49+
echo "environment=dev" >> $GITHUB_OUTPUT
50+
fi
51+
- name: Deploy triggers
52+
if: ${{ needs.changes.outputs.triggers == 'true' }}
53+
run: railway up -s logic-system -e ${{ steps.env.outputs.environment }} --ci
54+
- name: Deploy subscriptions
55+
if: ${{ needs.changes.outputs.subscriptions == 'true' }}
56+
run: railway up -s subscription-server -e ${{ steps.env.outputs.environment }} --ci
57+
- name: Deploy dispatcher
58+
if: ${{ needs.changes.outputs.dispatcher == 'true' || needs.changes.outputs.subscriptions == 'true' }}
59+
run: railway up -s dispatcher -e ${{ steps.env.outputs.environment }} --ci
60+
- name: Deploy consumers
61+
if: ${{ needs.changes.outputs.consumers == 'true' }}
62+
run: railway up -s consumer-telegram -e ${{ steps.env.outputs.environment }} --ci

CLAUDE.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
6+
## Repository Overview
7+
8+
This is an event-driven notification system for DAO governance built with microservices architecture. The system monitors blockchain proposals and delivers notifications via Telegram (with extensibility for other channels).
9+
10+
## Architecture
11+
12+
The system consists of 4 microservices connected via RabbitMQ:
13+
14+
1. **Logic System** - Monitors AntiCapture API for proposal events and triggers notifications
15+
2. **Dispatcher** - Processes events, fetches subscribers, and creates notification messages
16+
3. **Subscription Server** - REST API for managing user preferences and tracking notifications
17+
4. **Consumer** - Telegram bot that delivers notifications and handles user interactions
18+
19+
## Key Commands
20+
21+
### Development
22+
```bash
23+
# Install dependencies (requires pnpm)
24+
pnpm install
25+
26+
# Start all services with Docker Compose
27+
pnpm dev
28+
29+
# Build all services
30+
pnpm build
31+
32+
# Run tests across all services
33+
pnpm test
34+
35+
# Format code
36+
pnpm format
37+
```
38+
39+
### Service-Specific Commands
40+
```bash
41+
# Run commands for specific services
42+
pnpm logic-system <command>
43+
pnpm dispatcher <command>
44+
pnpm subscription-server <command>
45+
pnpm consumer <command>
46+
47+
# Example: Run tests for logic-system
48+
pnpm logic-system test
49+
50+
# Example: Start a single service in dev mode
51+
pnpm logic-system dev
52+
```
53+
54+
### Testing
55+
```bash
56+
# Run all tests
57+
pnpm test
58+
59+
# Run tests for a specific service
60+
pnpm --filter @notification-system/logic-system test
61+
62+
# Run a single test file
63+
pnpm --filter @notification-system/logic-system test -- path/to/test.spec.ts
64+
```
65+
66+
## Project Structure
67+
68+
```
69+
apps/
70+
├── logic-system/ # Event monitoring and triggering
71+
├── dispatcher/ # Message processing and routing
72+
├── subscription-server/ # User preference API
73+
├── consumers/ # Telegram bot delivery
74+
└── integrated-tests/ # End-to-end testing
75+
76+
packages/
77+
├── anticapture-client/ # GraphQL client for DAO data
78+
└── rabbitmq-client/ # Shared RabbitMQ utilities
79+
```
80+
81+
## Key Technologies
82+
83+
- **Runtime**: Node.js 18+, TypeScript 5.8.2
84+
- **Frameworks**: Fastify (APIs), Express (legacy)
85+
- **Database**: PostgreSQL with Knex.js migrations
86+
- **Message Queue**: RabbitMQ for service communication
87+
- **Testing**: Jest with ts-jest
88+
- **Build**: Turbo monorepo with pnpm workspaces
89+
90+
## Message Flow
91+
92+
1. Logic System polls AntiCapture API every 30 seconds
93+
2. On new proposals, sends `TriggerEvent` to dispatcher queue
94+
3. Dispatcher fetches relevant subscribers and creates notifications
95+
4. Consumer receives notifications and delivers via Telegram
96+
5. Delivery status tracked in Subscription Server
97+
98+
## Database Schema
99+
100+
The Subscription Server manages:
101+
- `users`: User profiles with Telegram/Discord IDs
102+
- `daos`: DAO registry from AntiCapture
103+
- `user_dao_subscriptions`: Many-to-many subscription relationships
104+
- `user_notifications`: Notification delivery tracking for deduplication
105+
106+
## Environment Configuration
107+
108+
Each service requires specific environment variables:
109+
- Copy `env.example` to `.env` at project root
110+
- Key variables: `DATABASE_URL`, `RABBITMQ_URL`, `TELEGRAM_BOT_TOKEN`
111+
- AntiCapture API endpoint configuration
112+
113+
## Testing Strategy
114+
115+
- Unit tests for business logic in each service
116+
- Integration tests for API endpoints
117+
- End-to-end tests in `integrated-tests` app
118+
- Jest configuration in each service's `jest.config.js`
119+
120+
## Deployment
121+
122+
- GitHub Actions workflow in `.github/workflows/`
123+
- Deploys to Railway on push to `dev` or `main`
124+
- Path-based deployment triggers for specific services
125+
- Docker Compose for local development mimics production
126+
=======
127+
## Project Overview
128+
129+
This is an event-driven notification system for DAO governance built with microservices architecture. The system monitors blockchain data for DAO proposals and delivers real-time notifications to users via Telegram. It uses RabbitMQ for message queuing and PostgreSQL for persistence.
130+
131+
## Architecture
132+
133+
The system consists of 4 main microservices:
134+
135+
1. **Logic System** (`apps/logic-system/`) - Monitors AntiCapture GraphQL API and triggers events
136+
2. **Dispatcher** (`apps/dispatcher/`) - Processes trigger events and coordinates notification delivery
137+
3. **Subscription Server** (`apps/subscription-server/`) - REST API for user preferences and subscription management
138+
4. **Consumer** (`apps/consumers/`) - Telegram bot and notification delivery service
139+
140+
## Development Commands
141+
142+
### Root Level Commands
143+
- `pnpm dev` - Start all services with Docker Compose
144+
- `pnpm build` - Build all apps using Turbo
145+
- `pnpm test` - Run tests across all apps
146+
- `pnpm format` - Format code with Prettier
147+
148+
### Per-Service Commands
149+
Use the filter pattern to run commands in specific services:
150+
- `pnpm --filter @notification-system/logic-system <command>`
151+
- `pnpm --filter @notification-system/dispatcher <command>`
152+
- `pnpm --filter @notification-system/subscription-server <command>`
153+
- `pnpm --filter @notification-system/consumer <command>`
154+
155+
### Testing and Quality
156+
- **Tests**: Each service uses Jest with TypeScript (`jest.config.js` or `jest.config.ts`)
157+
- **Linting**: Logic system has ESLint configured (`pnpm logic-system lint`)
158+
- **Type Checking**: Consumers service has type checking (`pnpm consumer check-types`)
159+
160+
## Message Flow Architecture
161+
162+
1. **Logic System** polls AntiCapture API → sends trigger events to RabbitMQ
163+
2. **Dispatcher** consumes trigger events → fetches subscribers → creates notifications → publishes to Consumer queue
164+
3. **Consumer** delivers notifications via Telegram → tracks delivery status in Subscription Server
165+
166+
## Key Implementation Patterns
167+
168+
### Trigger System
169+
New trigger types follow this pattern:
170+
- Extend base `Trigger` class in Logic System
171+
- Implement `fetchData()` and `process()` methods
172+
- Register in Logic System's `App` class
173+
- Create corresponding handler in Dispatcher's trigger handlers
174+
- Register handler in `TriggerProcessorService`
175+
176+
### RabbitMQ Integration
177+
- Logic System publishes trigger events
178+
- Dispatcher consumes trigger events and publishes notification events
179+
- Consumer consumes notification events for delivery
180+
181+
### Database Migrations
182+
Subscription Server uses Knex.js for database operations with migrations in `db/migrations/`
183+
184+
## Technology Stack
185+
186+
- **Runtime**: Node.js 18+ with pnpm workspaces
187+
- **Build Tool**: Turbo for monorepo builds
188+
- **Messaging**: RabbitMQ with custom client abstractions
189+
- **Database**: PostgreSQL with Knex.js migrations
190+
- **API**: Fastify for REST endpoints
191+
- **GraphQL**: Custom AntiCapture client in packages/
192+
- **Testing**: Jest with ts-jest preset
193+
- **Containerization**: Docker with multi-service compose
194+
195+
## Environment Requirements
196+
197+
Essential environment variables:
198+
- `TELEGRAM_BOT_TOKEN` - Telegram bot authentication
199+
- `ANTICAPTURE_GRAPHQL_ENDPOINT` - DAO data source
200+
- `DATABASE_URL` - PostgreSQL connection
201+
- `RABBITMQ_URL` - Message broker connection
202+
203+
## Extension Points
204+
205+
- **New Trigger Types**: Follow the pattern documented in `apps/logic-system/add-trigger-logic.md`
206+
- **Notification Channels**: Add new delivery mechanisms in Consumer service
207+
- **DAO Data Sources**: Extend AntiCapture client or add new data providers

apps/consumers/src/app.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { session } from 'telegraf/session';
33
import { AxiosInstance } from 'axios';
44
import { TelegramBotService } from './services/telegram-bot.service';
55
import { DAOService } from './services/dao.service';
6+
import { WalletService } from './services/wallet.service';
67
import { AnticaptureClient } from '@notification-system/anticapture-client';
78
import { SubscriptionAPIService } from './services/subscription-api.service';
89
import { ContextWithSession } from './interfaces/bot.interface';
@@ -22,9 +23,10 @@ export class App {
2223
const subscriptionApi = new SubscriptionAPIService(subscriptionServerUrl);
2324
const anticaptureClient = new AnticaptureClient(httpClient);
2425
const daoService = new DAOService(anticaptureClient, subscriptionApi);
26+
const walletService = new WalletService(subscriptionApi);
2527
const bot = new Telegraf<ContextWithSession>(telegramBotToken);
2628
bot.use(session());
27-
this.telegramBotService = new TelegramBotService(bot, daoService);
29+
this.telegramBotService = new TelegramBotService(bot, daoService, walletService);
2830
this.rabbitmqUrl = rabbitmqUrl;
2931
}
3032

@@ -33,7 +35,7 @@ export class App {
3335
this.rabbitmqUrl,
3436
this.telegramBotService
3537
);
36-
this.telegramBotService.launch();
38+
await this.telegramBotService.launch();
3739
console.log('Telegram bot is running!');
3840
}
3941

apps/consumers/src/interfaces/bot.interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ import { Context } from 'telegraf';
77
export interface ContextWithSession extends Context {
88
session: {
99
daoSelections: Set<string>;
10+
walletAction?: 'add' | 'remove';
11+
walletsToRemove?: Set<string>;
12+
awaitingWalletInput?: boolean;
1013
};
1114
}

apps/consumers/src/messages.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ const generateCommandList = () => {
1111

1212
export const WELCOME_MESSAGE = `
1313
🔔 Welcome to the Anticapture notification system!
14-
1514
Spotting the "oh no" before it hits your treasury.
1615
17-
To start using the system, you'll need to go through the following steps:
18-
19-
📊 Add the DAOs you want to receive notifications from by clicking on "DAOs"`;
16+
➡️ To start using the system, you'll need to add the DAOs you want to receive notifications from by clicking on "DAOs".
17+
➡️ After that, click on "My Wallets" and add your wallet address to receive custom notifications.`;
2018

2119
export const HELP_MESSAGE = `
2220
<b>What is Anticapture?</b>
@@ -43,4 +41,18 @@ export const EDIT_DAOS_MESSAGE = 'You can edit this list by clicking on 🌐 DAO
4341

4442
// Static buttons messages
4543
export const DAOS_BUTTON_TEXT = '🌐 DAOs';
46-
export const LEARN_MORE_BUTTON_TEXT = '💡 Learn More';
44+
export const LEARN_MORE_BUTTON_TEXT = '💡 Learn More';
45+
export const MY_WALLETS_BUTTON_TEXT = '📝 My Wallets';
46+
47+
// Wallet-related messages
48+
export const WALLET_SELECTION_MESSAGE = `Here's the wallets you have added to receive custom notifications:`;
49+
export const ADD_WALLET_BUTTON_TEXT = '➕ Add wallet';
50+
export const REMOVE_WALLET_BUTTON_TEXT = '❌ Remove wallet';
51+
export const WALLET_INPUT_MESSAGE = '👉 Please enter your wallet address.';
52+
export const WALLET_PROCESSING_MESSAGE = '⏱️ Hang tight, we\'re just connecting your data…';
53+
export const WALLET_SUCCESS_MESSAGE = '✅ All set! Your wallet has been added.';
54+
export const WALLET_ERROR_MESSAGE = '❌ Invalid wallet address. Please try again.';
55+
export const WALLET_REMOVE_CONFIRMATION_MESSAGE = 'Select the wallets you want to remove:';
56+
export const WALLET_REMOVE_CONFIRM_BUTTON_TEXT = '🗑️ Confirm removal';
57+
export const WALLET_REMOVE_SUCCESS_MESSAGE = '✅ Selected wallets have been removed.';
58+
export const NO_WALLETS_MESSAGE = 'You haven\'t added any wallets yet. Click "Add wallet" to get started.';

0 commit comments

Comments
 (0)