feat(admin): add read and moderation endpoints over merchants - #56
Conversation
Adds the admin dashboard's merchant surface. No schema change is required —
every field served here already exists on Merchant, Invoice, MerchantAnalytics
and Subscription.
GET /admin/merchants lists merchants with limit/offset pagination reusing the
DEFAULT_LIMIT/MAX_LIMIT convention from invoice.validation.ts, filters on
active, verified, category and a case-insensitive search across businessName,
email and address, and sorts by createdAt, merchantId or businessName in either
direction, defaulting to createdAt desc. Booleans are parsed strictly: a query
string carries no real boolean, so only the literals "true" and "false" are
accepted rather than coercing anything truthy and silently filtering on the
wrong value.
GET /admin/merchants/:id serves the merchant detail through sanitizeMerchant,
which already withholds the OTP columns an admin has no reason to see.
GET /admin/merchants/:id/invoices delegates to the existing
listInvoices(merchantId, filters, pagination) and parses its query with
parseInvoiceListQuery, so the admin-scoped response shape and accepted filters
cannot drift from the merchant-facing route. The merchant is resolved first so
an unknown id is a 404 rather than an empty page.
GET /admin/merchants/:id/analytics adds getMerchantAdminAnalytics: per-token
volume, fees and transaction counts from MerchantAnalytics, plus live
status-grouped invoice and subscription counts. Subscription.merchantId is a
direct scalar, so the subscription grouping needs no join through
SubscriptionPlan. BigInt counters are serialized as strings, matching how
analytics.services.ts already reports them.
POST /admin/merchants/:id/block replaces the previous PATCH route and is now
gated by requireSuperAdmin, so a non-superadmin admin gets a 403. It sets
Merchant.active = false and records exactly one merchant.blocked AdminLog entry,
carrying an optional { reason } in the metadata. This is off-chain only: the
contract's set_merchant_status(admin, merchant_id, status) requires the on-chain
admin's signature, which this backend cannot produce, so reconciling the
on-chain status is deferred to separate work rather than silently skipped —
the same off-chain-first pattern used for invoice amendment.
Unblocking is deliberately not implemented. Only blocking was in scope; a test
asserts no unblock route answers, so its absence is explicit rather than an
oversight.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughChangesAdmin merchant endpoints
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🔵 Low · up to Merchant blocking now changes the persisted off-chain status, but the moderation audit record can still be missing if audit storage fails after the status update succeeds. The PR is mergeable with explicit owner awareness that audit completeness should be made transactional or reliably recoverable. Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Admin
participant MerchantRoutes
participant AdminMerchantControllers
participant MerchantServices
participant Prisma
Admin->>MerchantRoutes: Request merchant admin endpoint
MerchantRoutes->>AdminMerchantControllers: Apply authentication and invoke controller
AdminMerchantControllers->>MerchantServices: Validate query and request data
MerchantServices->>Prisma: Query merchant, invoice, subscription, or analytics data
Prisma-->>MerchantServices: Return requested data
MerchantServices-->>AdminMerchantControllers: Return service result
AdminMerchantControllers-->>Admin: Return JSON response
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Linked Issues checkExplanation The changes implement the linked issue objectives [ Full details: Docstring CoverageExplanation No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 6 files. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
codebestia
left a comment
There was a problem hiding this comment.
LGTM!
Thank you for your contribution.
Closes #42
Adds
GET /admin/merchants(filter/sort/paginate),GET /admin/merchants/:id,GET /admin/merchants/:id/invoices(delegates to the existinglistInvoices),GET /admin/merchants/:id/analytics, andPOST /admin/merchants/:id/blockbehindrequireSuperAdmin. No schema change.Two things worth a reviewer's attention:
PATCH(any admin) toPOST+requireSuperAdmin, per the issue. That is a breaking change for any existingPATCHcaller; the old integration test was rewritten accordingly.set_merchant_statusneeds the on-chain admin's signature, which this backend cannot produce — the deferral is noted in code, not silently skipped.Unblocking is out of scope; a test asserts no unblock route answers.
Full suite green: 47 suites / 441 tests.
tsc --noEmitclean,eslint0 errors.Summary by CodeRabbit
POST.