Skip to content

feat: multi-tenancy, OpenAPI docs, blockchain listener, event sourcing#1

Open
temma02 wants to merge 5 commits into
mainfrom
feat/235-237-238-228-multi-tenancy-docs-listener-eventsourcing
Open

feat: multi-tenancy, OpenAPI docs, blockchain listener, event sourcing#1
temma02 wants to merge 5 commits into
mainfrom
feat/235-237-238-228-multi-tenancy-docs-listener-eventsourcing

Conversation

@temma02
Copy link
Copy Markdown
Owner

@temma02 temma02 commented Apr 27, 2026

Summary

This PR implements four features across four commits.


Bonizozo#235 — Multi-Tenancy Support

Migration 0042_tenancy_canonical: tenants table (id, name, slug, max_creators, max_tips_per_day, is_active), tenant_configs table, and tenant_id FK columns on creators and tips (all idempotent).

Model src/models/tenant.rs: Tenant, CreateTenantRequest (slug validation), UpdateTenantRequest, TenantResponse.

Controller src/controllers/tenant_controller.rs: CRUD + analytics + usage queries. Provisioning runs in a single transaction.

Routes src/routes/tenants.rs: POST/GET /tenants, GET/PUT/DELETE /tenants/:id, GET /tenants/:id/analytics, GET /tenants/:id/usage. Wired into both /api/v1 and /api/v2.

Tenancy layer: fixed duplicate pub mod declarations in src/tenancy/mod.rs; fixed analytics.rs to join on creator_username instead of non-existent columns. Fixed pre-existing missing usage_analytics in src/routes/mod.rs.

Closes Bonizozo#235


Bonizozo#237 — API Documentation with OpenAPI

Extended the existing utoipa + SwaggerUi setup:

  • #[utoipa::path] annotations with request/response examples on all tenant routes and health routes (/health, /ready).
  • ToSchema added to CreateTenantRequest, UpdateTenantRequest, TenantResponse, TenantAnalytics, TenantUsage.
  • ApiDoc extended with health + teams + tenants paths, all new schemas, new tags, ReportMessageRequest (was missing), and multi-tenancy header note.

Interactive explorer at /swagger-ui, raw spec at /api-docs/openapi.json.

Closes Bonizozo#237


Bonizozo#238 — Blockchain Event Listener

  • EventProcessor.persist_event: now inserts full row (event_type, transaction_hash, ledger_sequence parsed from paging token) with ON CONFLICT DO NOTHING.
  • indexer::spawn(): reads HORIZON_URL / STELLAR_CONTRACT_ID from env, starts BlockchainListener as a background Tokio task, gracefully skips when Redis is unavailable.
  • main.rs: indexer::spawn() called at startup.

Listener streams Horizon SSE with exponential-backoff retry (2 s base, 60 s cap), filters by contract ID, classifies tip/withdraw/unknown events, records Prometheus metrics.

Closes Bonizozo#238


Bonizozo#228 — Event Sourcing Architecture

  • Event versioning: version: i32 on every Event variant with #[serde(default)] for backward compat with stored events that predate versioning.
  • Migration 0043: events.version column + event_snapshots table (aggregate_id, sequence_number, snapshot_data JSONB).
  • EventStore: append stores version; new load_from, load_up_to; write_snapshot auto-fires every 50 events per aggregate; load_latest_snapshot returns (seq, CreatorProjection).
  • Replayer.creator_state: snapshot + delta replay — O(delta) not O(n).
  • Replayer.creator_state_at: was broken (double load, ignored DB sequence numbers); now uses load_up_to with DB-side WHERE sequence_number <= $2.
  • CreatorProjection: added Serialize/Deserialize for snapshot storage.
  • Wiring: EventStore added to AppState; CommandBus built post-state-construction to avoid circular dependency, injected as axum Extension.

Closes Bonizozo#228


What was tested

  • Static code review (Cargo not available in the dev container).
  • All migrations are idempotent (IF NOT EXISTS, ON CONFLICT DO NOTHING).
  • Existing unit tests in events/types.rs and events/projections.rs cover serialization round-trips and projection correctness.

temma02 added 5 commits April 27, 2026 16:08
- Add migration 0042: tenants table (id, name, slug, max_creators,
  max_tips_per_day, is_active), tenant_configs table, and tenant_id
  FK columns on creators and tips
- Add Tenant model with CreateTenantRequest / UpdateTenantRequest DTOs
- Add TenantAnalyticsService with correct creator_username join
- Add tenant_controller: CRUD + analytics + usage queries
- Add /tenants REST routes (GET/POST list, GET/PUT/DELETE by id,
  GET analytics, GET usage) wired into v1 and v2 routers
- Fix tenancy/mod.rs duplicate pub mod declarations
- Add missing usage_analytics to routes/mod.rs

Closes Bonizozo#235
- Extend ApiDoc with health, teams, and tenants paths
- Add #[utoipa::path] annotations to health (liveness + readiness)
  and all tenant routes (CRUD + analytics + usage)
- Add ToSchema to CreateTenantRequest, UpdateTenantRequest,
  TenantResponse, TenantAnalytics, TenantUsage
- Register all new schemas and response examples in ApiDoc components
- Add health / teams / tenants tags with descriptions
- Document multi-tenancy X-Tenant-ID header in API overview
- Interactive explorer available at /swagger-ui

Closes Bonizozo#237
- Fix EventProcessor to persist full event data: event_type,
  transaction_hash, and ledger_sequence (parsed from paging_token)
  into indexed_events table via ON CONFLICT DO NOTHING for idempotency
- Add indexer::spawn() — starts BlockchainListener as a background
  Tokio task; reads HORIZON_URL and STELLAR_CONTRACT_ID from env;
  gracefully skips when Redis is unavailable (cursor falls back to 0)
- Wire indexer::spawn() into main.rs after monitoring_service::spawn

The listener streams Horizon SSE transactions with exponential-backoff
retry (2s base, 60s cap), filters by contract ID, classifies events
as tip/withdraw/unknown, and records Prometheus metrics for processed
events, failures, retry attempts, and indexer lag.

Closes Bonizozo#238
- Add version field to all Event variants (default=1, serde default
  for backward compat with stored events that predate versioning)
- Migration 0043: add events.version column and event_snapshots table
  (aggregate_id, sequence_number, snapshot_data JSONB)
- EventStore: append now stores version; add load_from, load_up_to
  for scoped queries; write_snapshot auto-fires every 50 events;
  load_latest_snapshot returns (seq, CreatorProjection)
- Replayer.creator_state: snapshot + delta replay (O(delta) not O(n))
- Replayer.creator_state_at: uses load_up_to with DB-scoped sequence
  filter instead of the previous broken in-memory take_while
- Add Serialize/Deserialize to CreatorProjection for snapshot storage
- Add EventStore to AppState; build CommandBus after state construction
  to avoid circular dependency; inject CommandBus as axum Extension

Closes Bonizozo#228
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant