ReviewOS

stacks/bench-review

trust: report/flag a review for community moderation

#27
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Right now only the author or an admin can act on a review. Readers have no path to surface a problematic one — no reporting flow, no admin queue for community-flagged content. This is table stakes for any public review platform; trust collapses fast without it.

Scope

  • New review_flags table: id, judge_review_id, user_id (nullable for anonymous), reason, details, status (open/dismissed/actioned), created_at.
  • API: POST /api/reviews/{id}/flag (auth-gated, rate-limited so the form can't be weaponized to spam-flag).
  • UI: small "Report" link in the article footer + a modal with reason picker (off-topic / harassment / spam / privacy-violation / other) and optional details.
  • Admin: /admin/reviews?flagged=1 tab in ReviewsTable.stx; flagged reviews bubble to the top with the flag count and most recent reason.
  • Email: notify moderators on new flags (best-effort, same pattern as the moderation emails already wired).

Acceptance

  • Reader can flag a review and gets a confirmation toast; can't flag the same review twice.
  • Admin sees the flagged review in their queue with the flag context.
  • Flagging doesn't surface the reporter's identity to the author.
  • Dismissed flags stay in DB for audit but drop out of the active queue.

Files to touch

  • database/migrations/ — new SQL migration
  • app/Models/ReviewFlag.ts — new
  • app/Actions/Reviews/FlagReviewAction.ts — new
  • routes/api.ts — add the flag route
  • resources/components/Bench/ArticleView.stx — add the Report link
  • resources/components/Bench/Admin/ReviewsTable.stx — flagged tab
9 days ago

First slice shipped — readers can now flag a review.

Schema — database/migrations/0000000109-create-review_flags-table.sql:

  • id, judge_review_id, user_id (nullable), reason, details, status (default 'open'), moderator_id, moderator_note, created_at, updated_at
  • Partial unique index (judge_review_id, user_id) WHERE user_id IS NOT NULL — signed-in users can flag a given review at most once; anonymous flags can repeat (rate-limit handles abuse at the action layer).
  • Status index + review index for the admin queue lookups.

API — app/Actions/Reviews/FlagReviewAction.ts + POST /api/reviews/{id}/flag:

  • Anonymous-friendly (signed-out callers ok — friction-free reporting).
  • Reasons whitelist: off_topic / harassment / spam / privacy / inaccurate / other.
  • Optional details up to 2000 chars.
  • 404s on missing review.
  • Idempotent for signed-in users: re-flagging the same review returns { ok: true, duplicate: true } silently (no 4xx, no enumeration).

UI — resources/components/Bench/ArticleView.stx:

  • Small "Report" link in the article footer, next to "← All reviews". Hidden when isMine (authors don't flag themselves; they have edit/delete on the banner).
  • Modal dialog: 6 radio reasons + free-text details box + "Send report".
  • Submitting shows a green "Thanks — a moderator will take a look" confirmation, then auto-dismisses after 2s.

Deferred — admin queue surfacing

The flagged-reviews tab on /admin/reviews is the natural follow-up. It needs:

  • Extend Admin/Reviews/ReviewIndexAction.ts to LEFT JOIN review_flags + return flag count + most-recent reason per row.
  • Filter pill on ReviewsTable.stx: ?flagged=1 mode that sorts flagged reviews to the top.
  • Per-row moderator actions: "Dismiss flags" (set status='dismissed' on every open flag for that review), "Take action" (flips review status + sets flag status='actioned').
  • (Optional) email notify moderators on new flag creation.

That's ~half a day on its own — worth tracking as a sibling issue if you want the surface separated. For now, flags accumulate quietly in the table and you'd inspect via SQL or by extending the admin view next pass.

Sign in to comment on this issue.