Skip to content

Problematic Campaigns: Moderation workflow, auto-suspension, admin controls, appeals, and logging #71

Description

@grantfox-oss

Description

Add a robust moderation workflow for campaigns: automatic suspension triggers (low verification, fraud reports, policy violations), admin suspend/reinstate actions, owner appeals submission/review, suspension reason logging, and user messaging.

Background

We currently handle problematic campaigns ad-hoc. This feature enables consistent containment of harm, an auditable trail, and a clear appeals process.

Scope

  • In-scope: DB schema + Prisma migration, backend APIs (owner + admin), background worker(s), audit records, notifications, tests, migration & rollout plan.
  • Out-of-scope: Admin frontend UI (API contract provided).

Goals

  • Auto-suspend campaigns that meet configured risk rules.
  • Allow admins to suspend/reinstate with recorded reasons.
  • Allow campaign owners to appeal; allow admins to resolve appeals.
  • Persist immutable audit trail for suspensions and appeals.
  • Notify owners (and donors where relevant) with clear next steps.

Acceptance Criteria

  • Campaigns meeting configured thresholds are automatically suspended and a suspensions record is created.
  • Admins can suspend/reinstate campaigns; actions recorded in suspensions.
  • Owners can submit appeals; admins can approve/deny appeals; appeal history persists.
  • Owners receive suspension notifications with appeal instructions.
  • Campaign listing / GET /campaigns/:id returns status, suspensionSummary, and canAppeal.
  • Unit + integration tests for main flows and edge cases.
  • Migration applied and existing campaigns populated with ACTIVE by default.

Functional requirements

  • Auto-suspension triggers:
    • Low verification score below threshold X for Y days.
    • N independent fraud reports within a time window.
    • Policy violation flags (automated/manual).
  • Admin actions:
    • Suspend: immediate suspension with reason and optional evidence.
    • Reinstate: reinstate with notes (stored).
  • Appeals:
    • Owners submit appeal while suspended.
    • Admin reviews appeal and approves/denies.
    • Appeal history linked to the suspension.
  • Suspension metadata:
    • Record reason code (enum), reason text, actor (system/admin id), source (AUTO|ADMIN), evidence links, timestamp.
  • Messaging:
    • Owner notifications + websocket events; optional donor notifications for fraud suspensions.

Non-functional requirements

  • Configurable thresholds via environment/config.
  • Idempotent suspend operations.
  • Efficient batched background evaluations; near-real-time handling for critical thresholds.
  • Admin-only access for review endpoints.
  • Secure storage/serving of evidence; access-controlled.

Data model (Prisma + DB)

  • Add status enum on Campaign (e.g., ACTIVE, SUSPENDED, PENDING_REVIEW, DELETED).
  • Campaign fields to add: status, suspendedAt (timestamp), suspensionMetadata (json), suspensionReasonId (FK optional).
  • New tables:
    • suspensions:
      • id, campaignId, actorId (nullable for system), source (AUTO|ADMIN), reasonCode, reasonText, evidence (json), createdAt
    • appeals:
      • id, suspensionId, campaignOwnerId, message, status (OPEN|UNDER_REVIEW|APPROVED|DENIED), adminNotes, createdAt, updatedAt
    • fraud_reports:
      • id, campaignId, reporterId (nullable), type, details, createdAt
  • Files to update: schema.prisma, relevant model seed scripts.

API contract (server-side)

  • Public/owner:
    • GET /campaigns/:id — include status, suspensionSummary, canAppeal
    • POST /campaigns/:id/appeals — Body: { message, attachments?: string[] }
    • GET /campaigns/:id/appeals — owner view
    • POST /campaigns/:id/reports — Body: { type, details }
  • Admin:
    • POST /admin/campaigns/:id/suspend — Body: { reasonCode, reasonText?, evidence?: string[] }
    • POST /admin/campaigns/:id/reinstate — Body: { adminNotes? }
    • GET /admin/campaigns/:id/suspensions — list suspensions/appeals
    • GET /admin/appeals — list+filter appeals
    • POST /admin/appeals/:id/resolve — Body: { decision: 'APPROVE'|'DENY', adminNotes? }

Suggested implementation files:

  • Controllers/routes: campaign.controller.ts or src/controllers/moderation.controller.ts; update campaign.routes.ts.
  • Services: extend src/services/* (e.g., notification.service.ts, campaign.service.ts).
  • Worker: src/workers/moderation.worker.ts (or extend existing workers).

Background worker / strategy

  • Periodic job (daily/cron) to evaluate rules in batch.
  • Event-driven: evaluate immediate thresholds on new fraud_reports.
  • Steps:
    • Query candidate campaigns
    • Run rule checks
    • Create suspensions record and update Campaign.statusSUSPENDED
    • Trigger notifications

Logging & audit

  • Every status change must write to suspensions (immutable).
  • Log events with logger.ts.
  • Do not delete suspensions or appeals; append-only admin notes only.

Notifications

  • Owner email and websocket event via notification.service.ts and websocket/socket.server.ts.
  • Donor notifications (configurable) for fraud-related suspensions.
  • Message template includes reason summary, evidence summary, appeal instructions, expected review timeframe.

Testing

  • Unit tests: service logic, rule evaluation, controller auth checks (follow existing *.test.ts patterns).
  • Integration tests: end-to-end flow (auto-suspend → appeal → admin resolve).
  • Edge cases: concurrent reports, repeated suspend attempts, appeal race conditions.

Migration & rollout

  1. Add Prisma schema changes and migration.
  2. Deploy migration to staging; run script to set Campaign.status = 'ACTIVE' when absent.
  3. Deploy backend and worker behind feature flag.
  4. Gradually enable auto-suspension rules; monitor metrics/logs.

Commands:

npx prisma migrate dev --name add_campaign_suspensions
node prisma/seed.js

Security & privacy

  • Admin-only endpoints require admin role checks.
  • Evidence storage must be access-controlled; treat as sensitive data.
  • Rate-limit report submissions.

Implementation notes & suggestions

  • Reuse existing services: notification.service.ts, redis.ts for counters, logger.ts.
  • Centralize suspension reason enum in index.ts.
  • Feature flag env var for toggling auto-suspension.
  • Store thresholds in config (index.ts or environment vars).

Checklist (GitHub task list)

  • Design DB schema + enums (schema.prisma)
  • Create Prisma migration
  • Implement suspensions, appeals, fraud_reports models
  • Add Campaign.status and populate existing campaigns
  • Implement moderation worker (src/workers/moderation.worker.ts)
  • Implement report endpoint (POST /campaigns/:id/reports)
  • Implement owner appeal endpoints
  • Implement admin suspend/reinstate + appeal resolve endpoints
  • Add notification templates + websocket events
  • Add audit logging using logger.ts
  • Add unit + integration tests
  • Deploy migration to staging and run rollout plan

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official CampaignenhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions