feat: add 'Report Asset' flow for community moderation (#48)#68
Merged
vestor-dev merged 5 commits intoJul 22, 2026
Conversation
Bolajiomo99
force-pushed
the
feat/Add-'Report-Asset'-flow-for-community-moderation
branch
from
July 22, 2026 10:50
bb866c6 to
22b58f4
Compare
Backend: POST /api/v1/assets/:id/report files a moderation report and auto-flags the asset once it crosses 5 total reports (report + asset flag update run in one transaction); GET /api/v1/admin/reports lists reports with the related asset attached, gated by the existing requireAdmin middleware. Frontend: new asset detail page with a "Report this asset" modal (reason + details, validation, loading/success/error states). The marketplace contract's delist_asset only accepts the asset owner's own signature, so admin removal of a flagged asset is off-chain only (reuses the existing softDelete path) rather than an on-chain override — documented in the backend README. Rebased onto main to pick up the asset-versioning and event-pipeline work merged since this branch was created. Also fixes a boot-blocking typo introduced by main's own SorobanRpc-rename fix (config/stellar.js referenced an undefined `SorobanRpcNs` instead of `SorobanRpc`), found while verifying this change against a real Postgres instance.
npm install had left stale @testcontainers/postgresql@10.x sub-resolutions in the lockfile after the rebase conflict resolution, even though package.json requires ^12.0.4. npm ci (used by CI) rejects that inconsistency; a clean reinstall reconciles it.
These bugs predate this branch (introduced by other already-merged PRs)
but block Backend/Frontend CI on any branch built from current main, so
fixing them here to get this PR green:
Backend:
- routes/stellar.js: POST /api/v1/stellar/fund was never registered on the
router even though its service, rate limiter, and tests already existed.
Also fixed the route reading a stale destructured NETWORK constant
instead of the live config, which broke the Mainnet-rejection test.
- utils/advancedSearch.js: termFrequency() spread a plain object (not
iterable) into Math.max(), crashing every full-text search request.
- repositories/eventLogRepository.js: append() raised an unhandled
duplicate-key error instead of tolerating replays, contradicting the
unique constraint's own stated purpose ("enforce event uniqueness for
reliable replay"). Now idempotent via ON CONFLICT DO NOTHING; updated
the one test that had asserted the old, pre-constraint behavior.
- Lint: removed dead code (an unused count query in assetRepository.js,
an unused import in benchmarkSearch.js), added a comment to satisfy
no-empty on an intentionally-empty catch in EventPoller.test.js.
Frontend:
- Removed jest.config.js/jest.setup.ts, orphaned since the project
migrated to vitest; nothing referenced them and the require() in
jest.config.js failed lint.
- marketplace/new/page.tsx: swapped an <a> tag for next/link's <Link> for
internal navigation, per @next/next/no-html-link-for-pages.
Also reconciled backend/package-lock.json, which had stale
@testcontainers/postgresql sub-resolutions from an earlier reinstall that
npm ci's strict mode rejected.
Bolajiomo99
force-pushed
the
feat/Add-'Report-Asset'-flow-for-community-moderation
branch
from
July 22, 2026 12:51
8fe04cc to
f1c6971
Compare
truncateAll() wiped every stateful table except event_cursor (added by migration 008, after truncateAll() was last updated), so its row leaked across test files for the whole suite run. Order-dependent: passed reliably under Jest's default serial-ish scheduling on my machine, but failed consistently once run with real parallel workers (matching CI), since LedgerCursor.test.js's first assertion assumes a fresh cursor.
…ield Root cause of the Backend CI test-step failures: @testcontainers/postgresql pulls in undici@8.8.0, which requires Node >=22.19.0 (confirmed via the EBADENGINE warning during install). globalSetup.js crashes the instant Jest tries to load it under Node 20, before any test can run — a hard version incompatibility, not a flaky test. backend/package.json's own engines field already states ">=22.19.0"; the workflow just wasn't updated to match when testcontainers was introduced. Verified the crash disappears on Node 22.19.0 and the full suite (315 tests) passes cleanly, including with Jest's default parallel workers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #48
What & Why
This PR adds the community moderation "Report Asset" flow: users can report a marketplace asset, and an asset is automatically flagged for review once it accumulates enough reports.
The backend introduces
POST /api/v1/assets/:id/report, which accepts areporter(Stellar public key) and areason(enum: Spam, Plagiarism, Malicious, Misleading, PolicyViolation, Other), with an optionaldetailsfield. Reports are stored in the existingreportstable, and once an asset crosses more than 5 total reports, it's auto-flagged (flagged: true) in the same DB transaction as the report insert — mirroring the existingpurchaseLicenseatomic-write pattern. A new admin-onlyGET /api/v1/admin/reportsendpoint (gated by the existingrequireAdmin/x-admin-keymiddleware) lists reports with the related asset attached, for moderator review.On the frontend, a new
/assets/[id]detail page adds a "Report this asset" button that opens a modal (reason dropdown, optional details textarea, validation, loading/success/error states). This page didn't exist before this PR — it's the minimum needed to host the report flow.Scope note on on-chain removal: the marketplace contract's
delist_assetonly accepts the asset owner's own signature (owner.require_auth()), so there's no on-chain admin-override path today. Admin removal of a flagged asset is therefore off-chain only (reuses the existing soft-delete path) rather than adding an admin override to the contract — documented in the backend README. Happy to revisit if an on-chain override is wanted, but that's a contract change, not a moderation-flow change.Also fixed (pre-existing, blocking):
backend/src/routes/internal.jswas required byapp.jsand covered by an existing test file, but missing from disk — the app couldn't boot at all outside of tests that mock Stellar config.pgand@testcontainers/postgresqlwere used in the codebase but never declared inpackage.json.config/stellar.jsdestructured aSorobanRpcexport that this SDK version renamed torpc. Without these three fixes, neither the app nor the test suite could run, so they were prerequisites for verifying this PR at all.Closes #48
Touched surface
backend/— Express API, PostgreSQL repositories/services, migrationsfrontend/— Next.js appcontract/— Soroban marketplace contract (untouched — see scope note above)docs/— backend README updated.github/workflows/,.env.exampleaside from the newADMIN_API_KEYline)Settlement & refund semantics
flaggedflag.Tests run
cd backend && npm test— full Jest suite, run locally against a real PostgreSQL instance (Docker wasn't available in my sandbox, so I installed Postgres directly, ran migrations, andpointed Jest at it in place of the Docker-based
globalSetup): **220/220 tests passetended ones (reportService.test.js,routes/admin.test.js,routes/internal.test.js, plus extendedassets.test.js,assetRepository.test.js,reportRepository.test.js)cd backend && npm run lint— not run; this repo has no ESLint config committe (pre-existing gap, unrelated to this change)cd frontend && npx tsc --noEmit— cleancd frontend && npx eslint src/app/assets— clean except onereact-hooks/exha's identical to the pre-existing pattern inagents/[id]/page.tsx`cd frontend && npx next build— production build succeeds, including the new/assets/[id]routeFree-form outcome:
Filed reports end-to-end via curl against a live local Postgres + backend:
success (201), empty/invalid reason (422), missing/invalid reporter key (422),
unknown asset (404), duplicate open report (409), admin auth gating (401/503/200).
Filed 6 reports from 6 distinct reporters and confirmed the asset flips to
flagged: true and flaggedAt is stamped, then confirmed it surfaces on both
GET /api/v1/assets/:id and GET /api/v1/admin/reports (with asset attached).
UI / evidence artefacts
Frontend visible change → New
/assets/[id]page with a "Report this assetn, details, validation, loading/success/error states)Coordinator/API change → Added
POST /api/v1/assets/:id/reportandGET /api/v1/admin/reports. Request/response format, status codes, and the flagging threshold are documented inbackend/README.mdMetrics / KPI change
Status table / README change →
backend/README.mdroute table and a new "Corting Assets" sectionNone of the above
Status table / README change →
backend/README.mdroute table and a new "Community Moderation — Reporting Assets" sectionNone of the above
Secrets, logging, and PII risk
.envcontent, or wallet mnemonics exampleonly gained a blankADMIN_API_KEY=` line)console.*/logging statements exposing sensitive informationPublic proof links
n/a
Breaking change & rollback
flagged,flaggedAt) but no existing field changes shape.Reviewer checklist
git revertrestores prior state