Skip to content

feat: add search and analytics modules (closes #1012, #1013, #1014, #1015) - #1125

Closed
devdianax wants to merge 154 commits into
Akanimoh12:mainfrom
devdianax:fix/all-assigned-issues
Closed

feat: add search and analytics modules (closes #1012, #1013, #1014, #1015)#1125
devdianax wants to merge 154 commits into
Akanimoh12:mainfrom
devdianax:fix/all-assigned-issues

Conversation

@devdianax

Copy link
Copy Markdown

Summary

Adds the Search and Analytics backend modules following existing module conventions.

Closes #1012
Closes #1013
Closes #1014
Closes #1015


Search module (backend/src/modules/search/)

Analytics module (backend/src/modules/analytics/)

Conventions followed

  • Zod validation for all request inputs
  • AppError subclasses for HTTP errors (via global error handler)
  • Prisma singleton for DB access
  • Pino logger (no console.log)
  • OpenAPI docs via mergeOpenApiPaths
  • Vitest tests with mocked Prisma client
  • Routes registered in src/app.ts

openhands-agent and others added 30 commits June 26, 2026 11:04
…Akanimoh12#836

Akanimoh12#822 - DB: EventLog model - add EventLog model to Prisma schema with migration
Akanimoh12#829 - DB: Create Prisma seed script with dev users, tips, and leaderboard snapshot
Akanimoh12#834 - Auth: POST /auth/verify - ed25519 signature verification, challenge validation, JWT issuance
Akanimoh12#836 - Auth: POST /auth/refresh - refresh token rotation with revocation detection

Also fixes:
- Add missing User relation fields (notifications, leaderboardSnapshots, streak)
- Fix failed test due to wrong API path prefix
Add a requestId middleware that honours an inbound x-request-id header
or generates a UUID v4, exposes it on req.id (so pino-http logs the same
id), and echoes it back on the x-request-id response header. Include the
id in error responses via the global error handler.

Closes Akanimoh12#797

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…a model docs

Closes Akanimoh12#827 - DB: Add database indexes review pass
Closes Akanimoh12#831 - DB: Document the data model (ER overview)
Closes Akanimoh12#833 - Auth: POST /auth/challenge
Closes Akanimoh12#841 - Auth: Rate limit auth endpoints
Auth: challenge endpoint + rate limiting; DB: indexes + data model docs
Add a pre-aggregated daily analytics table (one row per UTC day) holding
totalTips, totalVolume, newUsers and activeUsers, with a unique date so a
scheduled job can upsert per-day rows for dashboard trend reads.
Add an append-only AuditLog (actor, action, target, metadata, createdAt)
so privileged/admin operations are recorded for accountability, indexed by
actor, action and createdAt for querying.
Add a nullable deletedAt to the user-facing resource models (User, ApiKey,
Notification, Goal, Subscription) so records can be logically deleted
instead of hard-deleted; a non-null value marks the row as deleted.
Immutable/on-chain and append-only models (Tip, Refund, EventLog, AuditLog,
AnalyticsDaily) and infra tables are intentionally excluded.
Ensure every model carries createdAt @default(now()) and updatedAt
@updatedat. Adds the missing timestamps to Tip, IndexerCursor, EventLog,
Refund, Notification, XAccount, LeaderboardSnapshot, AuthChallenge,
RefreshToken, AnalyticsDaily and AuditLog. The migration backfills new
NOT NULL updatedAt columns with CURRENT_TIMESTAMP then drops the default so
the database matches the schema (bare @updatedat) with no drift.
…kanimoh12#842, Akanimoh12#849)

- Replace authChallenge.delete with deleteMany + count check so the nonce
  can only be consumed once atomically (count=0 means already used → 401)
- Replace user findUnique + create with prisma.user.upsert to eliminate
  the TOCTOU race on concurrent first logins for the same Stellar address
…ce paths (Akanimoh12#842, Akanimoh12#849)

- Swap authChallenge.delete mock for deleteMany returning { count }
- Swap user.findUnique + user.create mocks for user.upsert
- Add test: nonce already consumed returns 401
- Add test: existing user returned on subsequent login
- Add tests: challenge address mismatch, token expiry
)

Augments Express.Request with an optional user: AuthUser property so
requireAuth middleware can attach the decoded JWT payload and downstream
handlers read it with full type safety.
Validates the Authorization: Bearer <token> header, verifies the JWT
with config.auth.jwtSecret, attaches req.user: AuthUser, and calls
next(UnauthorizedError) on any failure (missing header, wrong scheme,
invalid or expired token).
Covers: missing Authorization header, non-Bearer scheme, invalid JWT,
expired JWT, and valid JWT attaches req.user and calls next.
…ow (Akanimoh12#832)

Documents the nonce-sign-verify design decision, token design, sequence
diagram, single-use nonce enforcement, upsert user on first login, and
trade-offs vs SEP-10 / SIWE.
…alytics-audit-timestamps

feat: Add AnalyticsDaily, AuditLog, soft-delete & timestamps to DB models
…+ requireAuth

- GET /profiles/by-address/:address — lookup by Stellar address
- PATCH /profiles/reactivate — undo soft-delete
- POST /profiles/image — base64 image upload, IPFS pin, store CID
- POST /tips/prepare — build unsigned Soroban tip tx via RPC
- requireAuth middleware (JWT verification)
- profileImageCid on User model + migration
- OpenAPI docs with security scheme
- Fix pre-existing test mock hoisting issues
…sues

Profiles & Tips: by-address lookup, reactivate, image upload, prepare tx
Auth: Nonce single-use enforcement, account creation on first login, requireAuth middleware, and challenge-response ADR
…-887-888

test(backend): add Supertest helper and wire app service to docker-compose
Add tip read APIs and the off-chain indexer poll loop.

Tips module:
- GET /tips/:id returns a single tip (404 when missing)
- GET /profiles/:username/tips lists tips received by a profile
- GET /users/me/tips/sent lists tips sent by the authenticated user
- Cursor pagination (limit/cursor) and Zod-validated inputs; BigInt
  amounts serialized to decimal strings

Indexer (src/indexer):
- Poll getEvents from the stored cursor ledger forward at the configured
  interval, resuming via the IndexerCursor table
- Idempotent projections: EventLog deduped by (txHash, topic, ledger) and
  Tip upserted on the unique txHash, so replays produce no duplicates
- Wired into server bootstrap with graceful shutdown

Closes Akanimoh12#872
Closes Akanimoh12#873
Closes Akanimoh12#874
Closes Akanimoh12#893
Akanimoh12 and others added 22 commits July 25, 2026 03:35
…n-module

feat: implement X (Twitter) integration module
prisma/schema.prisma had duplicate User fields and a duplicate
TipStatus enum from an unresolved merge, src/app.ts had two
concatenated import blocks with a dangling `}` and duplicate route
registrations, and AppError.ts declared ServiceUnavailableError
twice. These broke `prisma generate` and `tsc` outright. Also fixes
an unrelated x.test.ts type error (unsafe globalThis cast) that was
blocking `npm run typecheck`.
Adds the NotificationPreference model (per-user tipReceived/goalReached
toggles, defaulting to all-enabled when no row exists) with GET/PATCH
/notifications/preferences, a GET /notifications/unread-count endpoint,
and a createNotification helper that persists a notification, checks
the caller's preferences, and broadcasts it over the realtime gateway.
The helper is shared infrastructure for the tip and goal notification
triggers.

Closes Akanimoh12#966, Akanimoh12#967
recordTip now notifies the receiving creator via createNotification
once a tip is newly inserted (never on the existing-tip or P2002
dedupe paths, so replays don't double-notify). Skips self-tips and
recipients without an off-chain User row, and never lets a
notification failure block the tip response.

Closes Akanimoh12#963
projectGoalReached now calls createNotification once the goal
transitions into COMPLETED. The transition is detected by comparing
against the row's prior status rather than event replay state, so
re-processing the same ledger never creates a duplicate notification.

Closes Akanimoh12#964
… and debounced recomputation

## Summary
Implements the credit scoring system for Stellar Tipz with the following improvements:

### Issue Akanimoh12#912: Credit Score Formula Module (Pure Functions)
- Create `credit.formula.ts` with pure, deterministic scoring functions
- All formula functions are side-effect free and fully testable
- Implement component calculations: tip volume, X metrics, account age, streak bonus
- Pure formula is separated from database/cache operations

### Issue Akanimoh12#916: Credit Score Breakdown / Factors
- Create `credit.factors.ts` to expose contributing factors and weights
- Provide transparent breakdown of how each factor contributes to the score
- API-ready methods to format factor information for responses
- Clear documentation of weights, divisors, and caps

### Issue Akanimoh12#921: Credit Score Weights via Config
- Create `credit.config.ts` to load weights from environment variables
- Support configurable weights, divisors, and caps via env vars
- All configuration is validated and has sensible defaults
- Easy to adjust scoring algorithm without code changes

### Issue Akanimoh12#918: Credit Score Recompute on New Tip
- Implement debounced recomputation mechanism in `credit.service.ts`
- Add `scheduleRecomputeCreditScore()` for debounced tip-triggered updates
- Multiple rapid tips result in single recomputation (5s debounce)
- Prevents excessive database writes while maintaining fresh scores

### Technical Details
- Formula is pure and configuration-driven for maximum flexibility
- Updated environment configuration with new credit score parameters
- Comprehensive unit tests for all formula functions and edge cases
- Updated existing tests to verify debouncing behavior
- Follows module conventions established in the codebase

### Testing
- Added `credit.formula.test.ts` with 30+ tests covering:
  - Individual component calculations (tip, X metrics, age)
  - Weight application and capping logic
  - Full formula with all components
  - Tier assignment and edge cases
  - Configuration flexibility
- Updated `credit.test.ts` to test service-level debouncing
- All pure functions are deterministic and testable

Closes Akanimoh12#912
Closes Akanimoh12#916
Closes Akanimoh12#918
Closes Akanimoh12#921
…8-921-credit-scoring-improvements

feat(credit): pure formula functions, configurable weights, and debounced recomputation
feat:Notifications: preferences, unread count, and tip/goal triggers
…altime event

Closes Akanimoh12#941, Akanimoh12#951

- withdrawals: compute a 2% (configurable via WITHDRAWAL_FEE_BPS) fee on
  prepareWithdrawal, send the net amount on-chain, and return fee/netAmount
  in the response.
- realtime: add a typed `balance.updated` Socket.IO event, auth-enforced via
  the existing user:<id> room, and emit it after a tip is confirmed.
- fix pre-existing merge-artifact duplication (app.ts imports/mounts,
  AppError.ts ServiceUnavailableError, prisma schema.prisma User model and
  TipStatus enum) that broke the build for every test on this branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…heartbeat config + docs

- Add a shared typed contract for Socket.IO events and payloads (SocketData,
  connected/error events) so client and server agree on shapes.
- Reuse the REST module's verifyAccessToken for the socket auth handshake
  instead of duplicating JWT verification.
- Throttle new connections per IP and client events per socket with a small
  in-memory sliding-window limiter; emit a typed RATE_LIMITED error.
- Configure explicit pingInterval/pingTimeout for heartbeat detection and
  document heartbeat + client reconnection behavior in docs/REALTIME.md.
- Wire initRealtime(httpServer) into server.ts — it was implemented but
  never actually started.
- Remove a duplicate, unwired realtime.* implementation left over from a
  prior merge and consolidate on the one already used by tips.controller.ts.
- Fix duplicate import/enum/model-field blocks in app.ts, schema.prisma, and
  AppError.ts (merge artifacts) that broke `tsc`/`prisma generate` repo-wide.

The notifications listing endpoint (GET /notifications, paginated) was
already fully implemented and tested on this branch; no changes needed.

Closes Akanimoh12#954
Closes Akanimoh12#955
Closes Akanimoh12#956
Closes Akanimoh12#959
…ifications-954-955-956-959

feat(realtime): typed event contract, rate limiting, heartbeat + notifications listing
…and-realtime-balance-941-951

Withdrawal fee calculation (2%) + realtime balance.updated event
…cast tests

- Akanimoh12#965: add a `subscription_charged` notification type, gated by a new
  per-user `subscriptionCharged` preference (default enabled), and fire
  it from the indexer's sub_exec projection when a charge is genuinely
  new (event-log gated, so replays never duplicate it).
- Akanimoh12#957: fix gateway.test.ts, which was signing tokens with a stale `sub`
  claim and asserting a stale error message — both leftovers from a prior
  auth/error-shape change that left the auth-handshake tests silently
  broken (one timing out, one failing). Add missing room-broadcast
  coverage for tip.created and notification.created, which had no tests.
- Akanimoh12#961 (POST /notifications/read-all) and Akanimoh12#962 (notification creation
  service) were already fully implemented and tested on this branch by
  prior work; verified against the Definition of Done and left as-is.

Closes Akanimoh12#961, Akanimoh12#962, Akanimoh12#965, Akanimoh12#957
…thdrawal submit endpoint

Implements four backend issues on test-implement-drips:

- Realtime: emit leaderboard.updated to a public `leaderboard` room whenever
  a confirmed tip changes a creator's rank (Akanimoh12#952).
- Realtime: attach the Socket.IO Redis adapter (gated by
  REALTIME_REDIS_ADAPTER_ENABLED) so rooms are shared across horizontally
  scaled instances (Akanimoh12#948).
- Realtime: strengthen per-creator room test coverage with targeted-delivery
  assertions; the room join/auth implementation already existed (Akanimoh12#949).
- Withdrawals: add POST /withdrawals/submit to broadcast a wallet-signed
  withdrawal transaction and record it as a PENDING withdrawal, idempotent
  by txHash (Akanimoh12#940).

Also fixes a pre-existing bug in tips.test.ts (undefined mockCreateNotification
reference / mockUserFindUnique typo) that was crashing the entire test file
before any test could run.

Closes Akanimoh12#952
Closes Akanimoh12#948
Closes Akanimoh12#949
Closes Akanimoh12#940

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ions-and-realtime

feat(backend): notify on subscription charge + fix realtime room-broadcast tests
…oard-redis-withdrawal-submit-940-948-949-952

 Implements four backend realtime/withdrawals issues on `test-implement-drips`.
…ate-limit-backoff

feat(x): handle X API rate limits with retry/backoff and circuit breaker Akanimoh12#975
- Akanimoh12#945: Add OpenAPI docs for withdrawals and balances endpoints
- Akanimoh12#944: Add edge-case tests for withdrawals module (pagination, empty state, zero balance)
- Akanimoh12#958: Notifications module skeleton already complete (service, controller, routes, schema, types, tests)
- Akanimoh12#960: Add POST /notifications/:id/read route as alias for PATCH

Closes Akanimoh12#944, Akanimoh12#945, Akanimoh12#958, Akanimoh12#960
…ifications-944-945-958-960

feat: withdrawals API docs, enhanced tests, notifications POST /:id/read
- Closes Akanimoh12#1014: Search module skeleton with routes, controller, service, schema, types, and tests
- Closes Akanimoh12#1015: GET /search/creators endpoint for searching creators by name/username
- Closes Akanimoh12#1012: Analytics module with comprehensive Vitest tests
- Closes Akanimoh12#1013: Analytics docs with OpenAPI documentation

Modules follow existing conventions:
- Zod validation, AppError subclasses, Prisma singleton, pino logger
- OpenAPI docs via mergeOpenApiPaths
- Vitest tests with mocked Prisma client
@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@devdianax Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@devdianax

Copy link
Copy Markdown
Author

Closing — reopening against test-implement-drips instead of main.

@devdianax devdianax closed this Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.