PublicMarketplaceComments - BevvyTech/BrewskiDocs GitHub Wiki

Public Marketplace Comments

Authenticated storefront customers can create, edit, and react to marketplace comments through the /public/marketplace/comments surface. All endpoints listed here require a valid storefront JWT (aud = public). Guests may only read approved comments.

Method Path Description
POST /public/marketplace/comments/uploads Upload a comment attachment (image) and receive a short-lived token.
POST /public/marketplace/comments Create a new marketplace comment.
GET /public/marketplace/comments List comments for a subject (beer, merch, etc.).
GET /public/marketplace/comments/mine List the authenticated customer’s comments.
GET /public/marketplace/comments/:commentId Fetch a single comment when it is visible to the current viewer.
PATCH /public/marketplace/comments/:commentId Update a customer’s comment.
DELETE /public/marketplace/comments/:commentId Soft-delete a customer’s comment.
POST /public/marketplace/comments/:commentId/reactions Upvote/downvote (or clear) a comment reaction.
GET /public/marketplace/comments/summary Retrieve per-subject rating aggregates and street-cred snapshot.

⚠️ Attachments are limited to 5 MB and must be one of image/jpeg, image/png, or image/webp. Tokens expire after 24 hours if not attached to a published comment.


POST /public/marketplace/comments/uploads

  • Auth: Storefront JWT required.
  • Body: multipart/form-data (file field containing the image).
  • Response 201:
{
  "uploadId": "ed3eb44c-d2d0-44ef-b6d4-2cd44c3f2d24",
  "url": "https://cdn.brewskiapp.com/comments/uploads/client-1/1234.jpg",
  "thumbUrl": "https://cdn.brewskiapp.com/comments/uploads/client-1/1234-thumb.jpg",
  "width": 1024,
  "height": 768,
  "contentType": "image/jpeg",
  "filesize": 203421,
  "expiresAt": "2025-10-28T12:34:56.000Z"
}
  • Errors:
    • 400 COMMENT_UPLOAD_REQUIRED – No file provided.
    • 413 COMMENT_UPLOAD_TOO_LARGE – File exceeds 5 MB.
    • 415 COMMENT_UPLOAD_UNSUPPORTED – Unsupported MIME.
    • 429 RATE_LIMITED – Upload quota exceeded.

POST /public/marketplace/comments

  • Auth: Storefront JWT required.
  • Body:
{
  "subjectType": "beer",
  "subjectId": "f778b914-4bf6-4a28-9a91-9bd085249491",
  "rating": 4,
  "title": "Cracking IPA",
  "body": "Loved the dry-hop punch and clean finish.",
  "attachments": [
    "ed3eb44c-d2d0-44ef-b6d4-2cd44c3f2d24"
  ]
}
  • Response 201: Returns the serialized comment:
{
  "comment": {
    "id": "48be1dbb-77f3-4a82-b282-dab1485578d6",
    "subjectType": "beer",
    "subjectId": "f778b914-4bf6-4a28-9a91-9bd085249491",
    "rating": 4,
    "title": "Cracking IPA",
    "body": "Loved the dry-hop punch and clean finish.",
    "attachments": [
      {
        "id": "5b4384d3-6715-4fb3-b8aa-63c2a2650136",
        "url": "https://cdn.brewskiapp.com/comments/uploads/client-1/1234.jpg",
        "thumbUrl": "https://cdn.brewskiapp.com/comments/uploads/client-1/1234-thumb.jpg",
        "width": 1024,
        "height": 768,
        "contentType": "image/jpeg",
        "filesize": 203421
      }
    ],
    "reactions": {
      "upvotes": 0,
      "downvotes": 0,
      "userVote": null
    },
    "author": {
      "id": "client-1",
      "displayName": "Jordan Brewer",
      "location": "Manchester, UK",
      "streetCreds": null,
      "streetCredsVotes": 0,
      "canComment": true
    },
    "approved": true,
    "hidden": false,
    "pendingModeration": false,
    "aiReviewStatus": "approved",
    "streetCredsSnapshot": 0,
    "userCanEdit": true,
    "userCanDelete": true,
    "createdAt": "2025-10-27T11:12:13.000Z",
    "updatedAt": "2025-10-27T11:12:13.000Z",
    "edited": false
  }
}
  • Notes:
    • Attachments must reference valid upload tokens owned by the caller.
    • Marketplace currently supports subjectType = beer; other types return 400 COMMENT_SUBJECT_UNSUPPORTED.
    • A heuristics pass evaluates every submission; clean comments are auto-approved. Flagged comments remain pending in the moderation queue until reviewed.
    • Errors: 429 RATE_LIMITED when the per-customer quota is exceeded.

GET /public/marketplace/comments

  • Auth: Optional. Guests receive only approved, non-hidden comments.
  • Query Parameters:
    • subjectType (beer | merch | article | other) – required.
    • subjectId (UUID) – required.
    • rating, minRating, maxRating – optional star filters.
    • page (default 1), pageSize (default 20, max 50).
    • sort (newest | oldest | rating | helpful), direction (optional).
    • approvedOnly (default true). Authenticated authors automatically see their pending comments.
  • Response 200:
{
  "comments": [ /* serialized comments as shown above */ ],
  "meta": {
    "page": 1,
    "pageSize": 20,
    "total": 4,
    "pages": 1
  }
}

GET /public/marketplace/comments/mine

  • Auth: Storefront JWT required.
  • Query Parameters: page, pageSize (same limits as list).
  • Response 200: Paginates the caller’s comments (pending, hidden, and approved).

GET /public/marketplace/comments/:commentId

  • Auth: Optional.
  • Behaviour: Returns the comment when:
    • It is approved and not hidden; OR
    • It belongs to the requesting customer (pending comments remain visible to the author).
  • Errors: 404 COMMENT_NOT_FOUND when hidden/deleted/not visible.

PATCH /public/marketplace/comments/:commentId

  • Auth: Storefront JWT required.
  • Body: Any subset of rating, title, body, attachments. Passing null for rating clears it.
  • Behaviour: Replaces attachments when a new array is provided. Updated comments re-enter the moderation heuristics pipeline and revert to pending.
  • Errors:
    • 403 COMMENT_FORBIDDEN – attempting to edit another customer’s comment.
    • 400 COMMENT_ATTACHMENT_INVALID – stale or unknown attachment tokens.

DELETE /public/marketplace/comments/:commentId

  • Auth: Storefront JWT required.
  • Behaviour: Soft deletes the comment (sets deletedAt + hides it). Any moderation queue entry is resolved as rejected with reason “Comment deleted by author”.

POST /public/marketplace/comments/:commentId/report

  • Auth: Storefront JWT required.
  • Body:
{ "reason": "Contains spam links" }
  • Behaviour: Creates a comment report tied to the caller. Duplicate reports from the same customer return 409 COMMENT_ALREADY_REPORTED. If the comment already has a report marked approved, new reports default to approved=true automatically; otherwise they remain pending.
  • Response 201:
{
  "report": {
    "id": "report-123",
    "commentId": "48be1dbb-77f3-4a82-b282-dab1485578d6",
    "reporterId": "client-1",
    "reason": "Contains spam links",
    "approved": null,
    "createdAt": "2025-10-27T12:00:00.000Z"
  },
  "status": "pending"
}
  • Errors: 429 RATE_LIMITED when the caller exceeds the hourly reporting quota.

POST /public/marketplace/comments/:commentId/reactions

  • Auth: Storefront JWT required.
  • Body:
{ "reaction": "upvote" } // or "downvote", "clear"
  • Behaviour: Upserts the user vote, refreshes tallies, and recalculates street-cred snapshots. Clearing removes the user’s vote.
  • Response 200: Returns the updated comment payload.

GET /public/marketplace/comments/summary

  • Auth: Optional.
  • Query Parameters: subjectType, subjectId.
  • Response 200:
{
  "summary": {
    "subjectType": "beer",
    "subjectId": "f778b914-4bf6-4a28-9a91-9bd085249491",
    "totalComments": 14,
    "averageRating": 4.2,
    "ratingBuckets": {
      "5": 9,
      "4": 3,
      "3": 2,
      "2": 0,
      "1": 0
    },
    "streetCredSummary": {
      "threshold": 5,
      "average": 6.5,
      "max": 12
    }
  }
}
  • streetCredSummary.threshold indicates how many total votes (up + down) are required before a reviewer’s street creds are surfaced in comment payloads.

Moderation Heuristics (Phase 1)

  • Links, spammy language, aggressive marketing copy, profanity, excessive uppercase, and oversized images cause a comment to be flagged for manual review (aiReviewStatus = flagged, queue status pending).
  • Clean submissions are auto-approved (aiReviewStatus = approved) and recorded in the moderation queue with status approved.
  • A flagged comment still appears to its author with a “Pending review” badge; other users cannot see it until an admin resolves the queue entry.

Admin Moderation Endpoints

These routes are available to authenticated admin/support staff via the admin proxy. All paths below are relative to /marketplace/comments.

Method Path Purpose
GET /review List comments by moderation status (pending, approved, rejected) including reports and queue metadata.
PATCH /:commentId/approve Approve a comment, clear the queue entry, and mark associated reports as approved.
PATCH /:commentId/reject Reject/hide a comment, update the queue entry, and mark reports as rejected.
PATCH /:commentId/block-author Block the original commenter from future submissions with an audit reason.
  • All responses include the updated comment or client payload so admin UIs can refresh state without another fetch.
  • Rejection requires a reason string; approvals accept an optional note stored in the queue.
  • Blocking records commentBlockedBy, commentBlockedAt, and commentBlockedReason on the customer.

GET /marketplace/comments/review

  • Auth: Bearer token tied to a super/support team (routed through the Admin proxy).
  • Query Parameters:
    • status (enum, default pending) — one of pending, approved, or rejected.
    • subjectType (optional, enum)beer, merch, article, or other.
    • page (int ≥ 1, default 1) and pageSize (int ≥ 1, default 50, max 100).
  • Response 200:
    {
      "page": 1,
      "pageSize": 50,
      "total": 17,
      "pages": 1,
      "results": [
        {
          "id": "c510...",
          "subjectType": "beer",
          "subjectId": "beer-123",
          "rating": 4,
          "title": "Hazy goodness",
          "body": "Soft peach and mango, would buy again.",
          "approved": false,
          "hidden": false,
          "aiReviewStatus": "flagged",
          "client": {
            "id": "client-1",
            "name": "Lantern Taproom",
            "canComment": true,
            "commentBlockedAt": null,
            "commentBlockedReason": null
          },
          "moderationQueue": {
            "status": "pending",
            "reason": "External links detected",
            "attempts": 1,
            "createdAt": "2025-02-12T09:45:00.000Z"
          },
          "attachments": [],
          "reports": []
        }
      ],
      "comments": [
        "...same array for legacy clients..."
      ]
    }
  • results and comments are identical arrays; newer clients should read results.
  • total/pages capture the filtered moderation queue size so the Admin UI can paginate consistently.

Rate limiting

  • Comment creation: 5 requests every 10 minutes per authenticated customer (configurable via RATE_LIMIT_COMMENT_CREATE_MAX / RATE_LIMIT_COMMENT_CREATE_WINDOW).
  • Attachment uploads: 5 uploads every 5 minutes (RATE_LIMIT_COMMENT_UPLOAD_MAX, RATE_LIMIT_COMMENT_UPLOAD_WINDOW).
  • Comment reports: 3 reports every hour (RATE_LIMIT_COMMENT_REPORT_MAX, RATE_LIMIT_COMMENT_REPORT_WINDOW).

Clients exceeding a limit receive 429 responses of the form:

{
  "code": "RATE_LIMITED",
  "message": "Too many requests. Please try again shortly.",
  "retryAfter": 120
}

Rate limiting is backed by an in-memory store by default. Supplying RATE_LIMIT_REDIS_URL switches the limiter to Redis for multi-instance deployments. Set RATE_LIMIT_DISABLED=1 to bypass limits (e.g., in local development).

⚠️ **GitHub.com Fallback** ⚠️