PublicMarketplaceComments - BevvyTech/BrewskiDocs GitHub Wiki
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 ofimage/jpeg,image/png, orimage/webp. Tokens expire after 24 hours if not attached to a published comment.
- Auth: Storefront JWT required.
-
Body:
multipart/form-data(filefield 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:
-
400COMMENT_UPLOAD_REQUIRED– No file provided. -
413COMMENT_UPLOAD_TOO_LARGE– File exceeds 5 MB. -
415COMMENT_UPLOAD_UNSUPPORTED– Unsupported MIME. -
429RATE_LIMITED– Upload quota exceeded.
-
- 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 return400 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_LIMITEDwhen the per-customer quota is exceeded.
- 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(default1),pageSize(default20, max50). -
sort(newest|oldest|rating|helpful),direction(optional). -
approvedOnly(defaulttrue). Authenticated authors automatically see their pending comments.
-
- Response 200:
{
"comments": [ /* serialized comments as shown above */ ],
"meta": {
"page": 1,
"pageSize": 20,
"total": 4,
"pages": 1
}
}- Auth: Storefront JWT required.
-
Query Parameters:
page,pageSize(same limits as list). - Response 200: Paginates the caller’s comments (pending, hidden, and approved).
- 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_FOUNDwhen hidden/deleted/not visible.
- Auth: Storefront JWT required.
-
Body: Any subset of
rating,title,body,attachments. Passingnullforratingclears 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.
-
- Auth: Storefront JWT required.
-
Behaviour: Soft deletes the comment (sets
deletedAt+ hides it). Any moderation queue entry is resolved asrejectedwith reason “Comment deleted by author”.
- 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 markedapproved, new reports default toapproved=trueautomatically; 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_LIMITEDwhen the caller exceeds the hourly reporting quota.
- 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.
- 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.thresholdindicates how many total votes (up + down) are required before a reviewer’s street creds are surfaced in comment payloads.
- Links, spammy language, aggressive marketing copy, profanity, excessive uppercase, and oversized images cause a comment to be flagged for manual review (
aiReviewStatus = flagged, queue statuspending). - Clean submissions are auto-approved (
aiReviewStatus = approved) and recorded in the moderation queue with statusapproved. - 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.
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, andcommentBlockedReasonon the customer.
- Auth: Bearer token tied to a super/support team (routed through the Admin proxy).
-
Query Parameters:
-
status(enum, defaultpending) — one ofpending,approved, orrejected. -
subjectType(optional, enum) —beer,merch,article, orother. -
page(int ≥ 1, default 1) andpageSize(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..." ] } -
resultsandcommentsare identical arrays; newer clients should readresults. -
total/pagescapture the filtered moderation queue size so the Admin UI can paginate consistently.
- 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).