Skip to content

Commit c2e1115

Browse files
Merge pull request #223 from blockful/dev
Dev
2 parents 7961edc + 3758a30 commit c2e1115

52 files changed

Lines changed: 6207 additions & 385 deletions

Some content is hidden

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

AGENTS.md

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# AGENTS.md
2+
3+
> Project context for AI coding assistants. See [agents.md](https://agents.md) for the open standard.
4+
5+
## Architecture Overview
6+
7+
Event-driven notification system for DAO governance, built as a **pnpm monorepo** with 4 microservices connected via RabbitMQ:
8+
9+
1. **Logic System** (`apps/logic-system/`) - Polls AntiCapture GraphQL API every Xs, detects governance events, publishes trigger events
10+
2. **Dispatcher** (`apps/dispatcher/`) - Consumes trigger events, fetches subscribers with temporal filtering, routes notifications
11+
3. **Subscription Server** (`apps/subscription-server/`) - Fastify REST API for user preferences, PostgreSQL persistence, Slack OAuth
12+
4. **Consumer** (`apps/consumers/`) - Delivers notifications via Telegram (telegraf) and Slack (@slack/bolt) bots
13+
14+
Supporting packages: `anticapture-client` (GraphQL), `messages` (templates), `rabbitmq-client` (AMQP wrapper).
15+
16+
Dashboard (`apps/dashboard/`) provides read-only metrics via Next.js.
17+
18+
## Essential Commands
19+
20+
```bash
21+
pnpm install # Install all dependencies
22+
pnpm dev # Start all services with Docker Compose
23+
pnpm build # Build all services (via Turbo)
24+
pnpm test # Run all tests (via Turbo)
25+
26+
# Service-specific (filter shortcuts)
27+
pnpm logic-system <cmd> # e.g., pnpm logic-system test
28+
pnpm dispatcher <cmd>
29+
pnpm subscription-server <cmd>
30+
pnpm consumer <cmd>
31+
32+
# Testing specific services/patterns
33+
pnpm --filter @notification-system/logic-system test
34+
pnpm --filter @notification-system/integrated-tests test -- --testNamePattern="voting"
35+
NODE_ENV=test pnpm --filter @notification-system/integrated-tests test
36+
37+
# Type checking and linting
38+
pnpm consumer check-types
39+
pnpm logic-system lint
40+
41+
# GraphQL code generation (requires ANTICAPTURE_GRAPHQL_ENDPOINT)
42+
pnpm client codegen
43+
```
44+
45+
## Notification Pipeline
46+
47+
```
48+
Logic System (polls API every 30s)
49+
-> publishes TriggerEvent to dispatcher-queue
50+
-> Dispatcher consumes, fetches subscribers with temporal filtering
51+
-> publishes NotificationPayload to notifications.exchange (topic)
52+
-> Consumer binds notifications.<channel>.* per platform
53+
-> delivers via Telegram/Slack -> marks as sent in Subscription Server
54+
```
55+
56+
## Adding New Trigger Types
57+
58+
Step-by-step guide for agents and developers. Use the same **trigger id** (e.g. `my-trigger`) in Logic System and Dispatcher so routing works.
59+
60+
### 1. Logic System – detect event and publish
61+
62+
- **Create trigger** in `apps/logic-system/src/triggers/`: extend `Trigger<T>` (see `base-trigger.ts`), implement `fetchData()` and `process(data[], lastTimestamp?)`.
63+
- **Optional:** If the trigger needs a dedicated data layer, add a repository in `apps/logic-system/src/repositories/` and use it from the trigger.
64+
- **Register** the trigger in `App.setupTriggers()` in `apps/logic-system/src/app.ts`.
65+
66+
### 2. Dispatcher – handle event and route notifications
67+
68+
- **Create handler** in `apps/dispatcher/src/services/triggers/`: extend `BaseTriggerHandler`, use the same trigger id as in Logic System.
69+
- **Register** the handler in `TriggerProcessorService` via `addHandler()` in `apps/dispatcher/src/app.ts`.
70+
71+
### 3. Messages – templates and buttons
72+
73+
- **Add message templates** in `packages/messages/src/triggers/` (e.g. `my-trigger.ts`) with `{{placeholder}}` syntax; export from `packages/messages/src/index.ts`.
74+
- **Add buttons config** in `packages/messages/src/triggers/buttons.ts` if the notification needs CTAs (e.g. explorer links).
75+
76+
### 4. Unit tests
77+
78+
- **Logic System:** Add tests in `apps/logic-system/tests/` (e.g. next to or mirroring the trigger). Use **stubs or fakes** for the RabbitMQ dispatcher and repositories (preferred over mocks); assert `fetchData()`/`process()` behavior.
79+
- **Dispatcher:** Add or extend co-located tests (e.g. `my-trigger.service.test.ts` in `apps/dispatcher/src/services/triggers/`). Use **stubs or fakes** for `ISubscriptionClient`, `INotificationClient`, and dependencies (preferred over mocks); assert handler logic and payload shape.
80+
- **Messages:** Add tests in `packages/messages` for new templates (placeholder replacement, edge cases) if non-trivial.
81+
82+
### 5. Integration tests
83+
84+
- **Add or extend** tests in `apps/integrated-tests/`: cover the new trigger flow (Logic System → Dispatcher → Consumer) using real RabbitMQ (testcontainers), in-memory DB, and stubs/fakes (or mocks during transition) for Telegram/Slack. Run with `NODE_ENV=test` and optionally filter: `pnpm --filter @notification-system/integrated-tests test -- --testNamePattern="my-trigger"`.
85+
86+
### Checklist
87+
88+
| Step | Location | Action |
89+
|------|----------|--------|
90+
| 1a | Logic System `src/triggers/` | New class extending `Trigger<T>`, implement `fetchData()` and `process()` |
91+
| 1b | Logic System `src/repositories/` | (Optional) New repository if trigger needs dedicated data access |
92+
| 1c | Logic System `src/app.ts` | Register trigger in `App.setupTriggers()` |
93+
| 2a | Dispatcher `src/services/triggers/` | New handler extending `BaseTriggerHandler` |
94+
| 2b | Dispatcher `src/app.ts` | Register handler with `TriggerProcessorService.addHandler()` |
95+
| 3a | Messages `src/triggers/` | New template file; export from `index.ts` |
96+
| 3b | Messages `src/triggers/buttons.ts` | Add button config if trigger has CTAs |
97+
| 4 | Logic System, Dispatcher, Messages | Add/update unit tests |
98+
| 5 | `apps/integrated-tests/` | Add or extend integration test for the new trigger flow |
99+
100+
## Database Schema (Subscription Server)
101+
102+
Key tables:
103+
- `users` - User profiles with `channel`, `channel_user_id`
104+
- `user_preferences` - DAO subscriptions with `is_active`, `created_at` (temporal filtering)
105+
- `user_notifications` - Delivery tracking for deduplication
106+
- `user_addresses` - Wallet addresses for personalized notifications
107+
- `channel_workspaces` - Slack workspace metadata
108+
- `slack_workspaces` - Encrypted OAuth tokens (AES-256-CBC)
109+
110+
Migrations in `apps/subscription-server/db/migrations/` (Knex.js).
111+
112+
## Testing Strategies
113+
114+
**Unit tests:** Prefer **stubs and fakes** over mocks. The codebase still has many mocks; new tests and refactors should use stubs/fakes where possible (e.g. in-memory or fake implementations of interfaces) to improve maintainability and avoid over-coupling to implementation details.
115+
116+
**Integration tests** (`apps/integrated-tests/`): Uses `@testcontainers/rabbitmq` for real RabbitMQ, SQLite in-memory DB, and stubs/fakes (or temporary mocks) for Telegram/Slack. Run with `NODE_ENV=test`. Prefer stubs and fakes for external service boundaries as we migrate away from mocks.
117+
118+
**Dashboard tests:** Node.js built-in `test` module (`tsx --test`).
119+
120+
## Environment Configuration
121+
122+
Required `.env` variables:
123+
```
124+
DATABASE_URL=postgresql://user:pass@localhost/dbname
125+
RABBITMQ_URL=amqp://localhost
126+
ANTICAPTURE_GRAPHQL_ENDPOINT=https://...
127+
TELEGRAM_BOT_TOKEN=...
128+
SLACK_SIGNING_SECRET=...
129+
TOKEN_ENCRYPTION_KEY=... # 64-char hex for AES-256-CBC
130+
```
131+
132+
## Code Conventions
133+
134+
- **Language:** TypeScript (strict mode) across all services
135+
- **Validation:** Zod schemas for environment variables and API inputs
136+
- **Monorepo:** pnpm workspaces + Turbo for builds
137+
- **Testing:** Jest with ts-jest (most services), Node.js test runner (dashboard)
138+
- **Package manager:** pnpm 10.x, Node.js >= 18
139+
140+
## Deployment
141+
142+
- GitHub Actions deploys to Railway on push to `dev` or `main`
143+
- Path-based triggers for selective service deployment
144+
- Docker Compose in `docker-compose.yml` for local development
145+
- Each service has its own `Dockerfile`
146+
147+
## Manual Notification Testing (Database Inserts)
148+
149+
To test without real blockchain events, insert mock data into the AntiCapture API database. The Logic System polls this data and triggers notifications.
150+
151+
### Prerequisites
152+
1. Identify the correct schema (check `information_schema.schemata`)
153+
2. Find an active user in subscription-server with `is_active = true`
154+
3. Get the user's wallet address from `user_addresses` table
155+
4. Run the indexer locally (`pnpm serve`) or just use the onde deployed on dev.
156+
157+
### Critical Notes
158+
- **Always disable triggers before INSERT**: Tables have `live_query` triggers that fail on manual inserts
159+
- **Use real `proposal_id`** for votes: Fake IDs cause API 500 errors
160+
- **Prefix mock tx_hash with `0xmock`**: Makes cleanup easy
161+
- **Use current timestamp**: `extract(epoch from now())::bigint` in SQL
162+
163+
### Vote Confirmation Insert
164+
```sql
165+
SET search_path TO "<schema_uuid>";
166+
ALTER TABLE votes_onchain DISABLE TRIGGER ALL;
167+
168+
INSERT INTO votes_onchain (tx_hash, dao_id, voter_account_id, proposal_id, support, voting_power, reason, timestamp)
169+
VALUES (
170+
'0xmock_vote_' || extract(epoch from now())::bigint,
171+
'ENS',
172+
'<user_wallet_address>',
173+
'<real_proposal_id_from_proposals_onchain>',
174+
'1', -- 0=Against, 1=For, 2=Abstain
175+
1000000000000000000,
176+
'Mock vote for testing',
177+
extract(epoch from now())::bigint
178+
);
179+
180+
ALTER TABLE votes_onchain ENABLE TRIGGER ALL;
181+
```
182+
183+
### Voting Power Change Insert (Delegation Received)
184+
```sql
185+
SET search_path TO "<schema_uuid>";
186+
ALTER TABLE voting_power_history DISABLE TRIGGER ALL;
187+
ALTER TABLE delegations DISABLE TRIGGER ALL;
188+
189+
INSERT INTO voting_power_history (transaction_hash, dao_id, account_id, voting_power, delta, delta_mod, timestamp, log_index)
190+
VALUES (
191+
'0xmock_vp_' || extract(epoch from now())::bigint,
192+
'ENS',
193+
'<user_wallet_address>',
194+
5000000000000000000,
195+
1000000000000000000,
196+
1000000000000000000,
197+
extract(epoch from now())::bigint,
198+
1
199+
);
200+
201+
INSERT INTO delegations (transaction_hash, dao_id, delegate_account_id, delegator_account_id, delegated_value, previous_delegate, timestamp, log_index)
202+
VALUES (
203+
'0xmock_vp_' || extract(epoch from now())::bigint,
204+
'ENS',
205+
'<user_wallet_address>',
206+
'0x1111111111111111111111111111111111111111',
207+
1000000000000000000,
208+
'0x0000000000000000000000000000000000000000',
209+
extract(epoch from now())::bigint,
210+
1
211+
);
212+
213+
ALTER TABLE voting_power_history ENABLE TRIGGER ALL;
214+
ALTER TABLE delegations ENABLE TRIGGER ALL;
215+
```
216+
217+
### Cleanup Mock Data
218+
```sql
219+
SET search_path TO "<schema_uuid>";
220+
ALTER TABLE votes_onchain DISABLE TRIGGER ALL;
221+
ALTER TABLE voting_power_history DISABLE TRIGGER ALL;
222+
ALTER TABLE delegations DISABLE TRIGGER ALL;
223+
224+
DELETE FROM votes_onchain WHERE tx_hash LIKE '0xmock%';
225+
DELETE FROM voting_power_history WHERE transaction_hash LIKE '0xmock%';
226+
DELETE FROM delegations WHERE transaction_hash LIKE '0xmock%';
227+
228+
ALTER TABLE votes_onchain ENABLE TRIGGER ALL;
229+
ALTER TABLE voting_power_history ENABLE TRIGGER ALL;
230+
ALTER TABLE delegations ENABLE TRIGGER ALL;
231+
```
232+
233+
### Finding Test Data
234+
```sql
235+
-- Get real proposal IDs
236+
SELECT id, LEFT(description, 50), status FROM proposals_onchain ORDER BY timestamp DESC LIMIT 5;
237+
238+
-- Check for orphan votes (will cause API 500)
239+
SELECT v.* FROM votes_onchain v LEFT JOIN proposals_onchain p ON v.proposal_id = p.id WHERE p.id IS NULL;
240+
241+
-- List mock records
242+
SELECT 'VOTE' as type, tx_hash, timestamp FROM votes_onchain WHERE tx_hash LIKE '0xmock%'
243+
UNION ALL SELECT 'VP', transaction_hash, timestamp FROM voting_power_history WHERE transaction_hash LIKE '0xmock%'
244+
UNION ALL SELECT 'DELEG', transaction_hash, timestamp FROM delegations WHERE transaction_hash LIKE '0xmock%';
245+
```

CLAUDE.md

Lines changed: 0 additions & 135 deletions
This file was deleted.

0 commit comments

Comments
 (0)