Skip to content

Commit 79d7798

Browse files
author
Bavithbabu
committed
Done with all Reporting system
1 parent a888318 commit 79d7798

5 files changed

Lines changed: 610 additions & 14 deletions

File tree

Reporting-API.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Reporting API
2+
3+
## Overview
4+
5+
| Property | Value |
6+
|---|---|
7+
| **Base URL (Production)** | `https://inscriptions.cdacb.in/api` |
8+
| **Authentication** | `Authorization: Bearer <jwt-token>` — required on all endpoints |
9+
10+
---
11+
12+
## Endpoints at a Glance
13+
14+
| Method | Endpoint | Description |
15+
|---|---|---|
16+
| `POST` | `/report` | Submit a new report |
17+
| `GET` | `/reports` | Fetch all reports |
18+
| `POST` | `/moderate/{id}` | Moderate a specific report |
19+
20+
---
21+
22+
## 1. `POST /report`
23+
24+
> Submit a report against a Post, Comment, or User for moderation review.
25+
26+
**Auth:** `Required`
27+
**Roles:** `user` · `admin`
28+
29+
### Request
30+
31+
```http
32+
POST https://inscriptions.cdacb.in/api/report
33+
Authorization: Bearer <jwt-token>
34+
Content-Type: application/json
35+
```
36+
37+
### Request Body
38+
39+
```json
40+
{
41+
"targetType": "POST",
42+
"targetId": "6817a9d9f2b7b12c34d56789",
43+
"reason": "SPAM",
44+
"details": "This post is repeatedly promoting unrelated links."
45+
}
46+
```
47+
48+
### Fields
49+
50+
| Field | Type | Required | Validation |
51+
|---|---|---|---|
52+
| `targetType` | `enum` | Yes | `POST` \| `COMMENT` \| `USER` |
53+
| `targetId` | `string` | Yes | Must be a valid existing resource ID |
54+
| `reason` | `enum` | Yes | See Reason Values below |
55+
| `details` | `string` | Yes | Max 1000 characters |
56+
57+
### `reason` Values
58+
59+
| Value | Description |
60+
|---|---|
61+
| `SPAM` | Unsolicited or repetitive content |
62+
| `HATE_SPEECH` | Content promoting hatred or discrimination |
63+
| `MISINFORMATION` | False or misleading information |
64+
| `HARASSMENT` | Targeting or bullying another user |
65+
| `EXPLICIT_CONTENT` | Inappropriate or adult content |
66+
| `OTHER` | Any other violation not listed above |
67+
68+
### Examples
69+
70+
**Report a Post for Spam**
71+
72+
```json
73+
{
74+
"targetType": "POST",
75+
"targetId": "6817a9d9f2b7b12c34d56789",
76+
"reason": "SPAM",
77+
"details": "This post is repeatedly promoting unrelated links."
78+
}
79+
```
80+
81+
**Report a User for Harassment**
82+
83+
```json
84+
{
85+
"targetType": "USER",
86+
"targetId": "6817a9d9f2b7b12c34d56000",
87+
"reason": "HARASSMENT",
88+
"details": "This user has been sending threatening messages to multiple members."
89+
}
90+
```
91+
92+
---
93+
94+
## 2. `GET /reports`
95+
96+
> Fetch all moderation reports. Supports optional filtering by report status.
97+
98+
**Auth:** `Required`
99+
**Roles:** `admin` · `moderator` · `human_moderator` · `ai_moderator`
100+
101+
### Request
102+
103+
```http
104+
GET https://inscriptions.cdacb.in/api/reports
105+
Authorization: Bearer <jwt-token>
106+
```
107+
108+
### Query Parameters
109+
110+
| Parameter | Type | Required | Description |
111+
|---|---|---|---|
112+
| `status` | `enum` | No | Filter reports by status. Omit to return all reports. |
113+
114+
### `status` Allowed Values
115+
116+
| Value | Description |
117+
|---|---|
118+
| `PENDING` | Report filed but not yet picked up by AI screening |
119+
| `AI_SCREENING` | AI is currently evaluating the report |
120+
| `ESCALATED` | AI flagged it — waiting for a human moderator |
121+
| `RESOLVED` | Final decision made, report is closed |
122+
123+
### Example Requests
124+
125+
```http
126+
GET /reports
127+
Authorization: Bearer <jwt-token>
128+
```
129+
Returns **all reports** regardless of status.
130+
131+
```http
132+
GET /reports?status=ESCALATED
133+
Authorization: Bearer <jwt-token>
134+
```
135+
Returns only reports that are **waiting for human review**.
136+
137+
```http
138+
GET /reports?status=PENDING
139+
Authorization: Bearer <jwt-token>
140+
```
141+
Returns reports that are **queued for AI screening**.
142+
143+
---
144+
145+
## 3. `POST /moderate/{id}`
146+
147+
> Moderate an escalated report by taking a moderation action on the reported content or user.
148+
149+
**Auth:** `Required`
150+
**Roles:** `admin` · `moderator` · `human_moderator` · `ai_moderator`
151+
152+
### Request
153+
154+
```http
155+
POST https://inscriptions.cdacb.in/api/moderate/{id}
156+
Authorization: Bearer <jwt-token>
157+
Content-Type: application/json
158+
```
159+
160+
### Path Parameter
161+
162+
| Parameter | Type | Required | Description |
163+
|---|---|---|---|
164+
| `id` | `string` | Yes | The `_id` of the report to moderate (MongoDB ObjectId) |
165+
166+
### Request Body
167+
168+
```json
169+
{
170+
"action": "REMOVE_CONTENT",
171+
"note": "Content clearly violates community spam guidelines."
172+
}
173+
```
174+
175+
### Body Fields
176+
177+
| Field | Type | Required | Validation |
178+
|---|---|---|---|
179+
| `action` | `enum` | **Yes** (for `ESCALATED` reports) | See Allowed Actions below |
180+
| `note` | `string` | No | Moderator's reason or remarks. Max 1000 chars. |
181+
182+
> **Important:** When the report status is `ESCALATED`, the `action` field is **required**. Omitting it or sending an invalid value will return `400 Bad Request`.
183+
184+
### Allowed Actions
185+
186+
> These are the only actions a human moderator can submit. The AI uses `ESCALATE` and `NONE` internally — do not pass those.
187+
188+
| Action | What it does |
189+
|---|---|
190+
| `WARN` | Issues a warning to the content author. Content remains visible. |
191+
| `REMOVE_CONTENT` | Deletes the reported post or comment. Increments author report count. |
192+
| `BAN_AUTHOR` | Deletes the content and permanently bans the content author. |
193+
| `BAN_REPORTER` | Blacklists the reporter (used when the report is false/abusive). Restores the reported content. |
194+
| `DISMISS` | Dismisses the report as invalid. Restores the reported content. |
195+
196+
### Notes
197+
198+
- This endpoint is for **human moderation only**. It is called when a report has `status: ESCALATED` (i.e., the AI could not auto-resolve it).
199+
- A moderator **cannot moderate their own content**. If the moderator's ID matches the content author's ID, the request will be rejected with `400`.
200+
- The `action` field is **required for `ESCALATED` reports**. Only these values are accepted: `WARN`, `REMOVE_CONTENT`, `BAN_AUTHOR`, `BAN_REPORTER`, `DISMISS`.
201+
- Every action is permanently recorded in `auditEntries` — this is the full audit trail for the report.
202+
- **Side effects by action:**
203+
- `REMOVE_CONTENT` — deletes the post or comment from the platform.
204+
- `BAN_AUTHOR` — deletes the content and marks the author as blacklisted.
205+
- `BAN_REPORTER` — blacklists the reporter and restores the reported content to `ACCEPTED` status.
206+
- `DISMISS` — restores the reported content to `ACCEPTED` status, no penalty applied.
207+
- `WARN` — restores the content and increments the author's report count.
208+
- Once a report is `RESOLVED`, calling this endpoint again will return `400 Bad Request`.

src/main/java/com/cadac/stone_inscription/post/service/PostServiceImp.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.cadac.stone_inscription.repository.InscriptionPostRepo;
4242
import com.cadac.stone_inscription.repository.PublicPostDescriptionRepo;
4343
import com.cadac.stone_inscription.repository.UserRepository;
44+
import com.cadac.stone_inscription.user.service.BlacklistGuardService;
4445
import com.cadac.stone_inscription.util.UserResponse;
4546

4647
@Service
@@ -69,6 +70,9 @@ public class PostServiceImp implements PostService {
6970
@Autowired
7071
private ContentDeleteService contentDeleteService;
7172

73+
@Autowired
74+
private BlacklistGuardService blacklistGuardService;
75+
7276
@Value("${app.backend.url}")
7377
private String backendUrl;
7478

@@ -79,6 +83,7 @@ public ResponseEntity<?> addPostWithFile(InscriptionPostDto inscriptionPostDto,
7983
String usernameFromToken) {
8084

8185
User user = userRepository.findByEmail(usernameFromToken);
86+
blacklistGuardService.ensureCanCreateOrModifyContent(user);
8287
List<ImageMetaAndInfo> ls = validateAndExtractImages(files, user.getId(), Collections.emptySet(), true);
8388

8489
// Below Line To use for Threshold similarty
@@ -232,6 +237,7 @@ public ResponseEntity<?> getAllUserPost(String usernameFromToken) {
232237
public ResponseEntity<?> addPoastDiscription(String usernameFromToken, String postId, String discription) {
233238

234239
User user = userRepository.findByEmail(usernameFromToken);
240+
blacklistGuardService.ensureCanCreateOrModifyContent(user);
235241
InscriptionPost post = inscriptionPostRepo.findById(new ObjectId(postId))
236242
.orElseThrow(() -> new StoneInscriptionException("Unprocesable request", HttpStatus.BAD_REQUEST));
237243

@@ -255,6 +261,7 @@ public ResponseEntity<?> getPostDiscription(String postId) {
255261
@Override
256262
public ResponseEntity<?> updatePostDiscription(String usernameFromToken, String postId, String discription) {
257263
User user = userRepository.findByEmail(usernameFromToken);
264+
blacklistGuardService.ensureCanCreateOrModifyContent(user);
258265
Optional<PublicPostDescription> postDiscription = publicPostDescriptionRepo.findById(new ObjectId(postId));
259266

260267
if (postDiscription.isEmpty()) {
@@ -396,6 +403,7 @@ public ResponseEntity<?> updatePost(String usernameFromToken, InscriptionPostDto
396403

397404
InscriptionPost post = getOwnedPost(usernameFromToken, postId);
398405
User user = userRepository.findByEmail(usernameFromToken);
406+
blacklistGuardService.ensureCanCreateOrModifyContent(user);
399407
List<String> existingImageIds = getExistingImageIds(post);
400408
List<String> imagesToDelete = validateDeletedImageIds(existingImageIds, deletedImageIds, false);
401409
Set<String> deletableImageIds = new HashSet<>(imagesToDelete);
@@ -441,6 +449,7 @@ public ResponseEntity<?> updatePost(String usernameFromToken, InscriptionPostDto
441449
public ResponseEntity<?> addImagesToPost(String usernameFromToken, String postId, MultipartFile[] files) {
442450
InscriptionPost post = getOwnedPost(usernameFromToken, postId);
443451
User user = userRepository.findByEmail(usernameFromToken);
452+
blacklistGuardService.ensureCanCreateOrModifyContent(user);
444453
List<ImageMetaAndInfo> newImages = validateAndExtractImages(files, user.getId(), Collections.emptySet(), true);
445454

446455
List<String> updatedImageIds = getExistingImageIds(post);

src/main/java/com/cadac/stone_inscription/report/service/ReportService.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.cadac.stone_inscription.report.specification.Specification;
2727
import com.cadac.stone_inscription.repository.UserAuthRepository;
2828
import com.cadac.stone_inscription.repository.UserRepository;
29+
import com.cadac.stone_inscription.user.service.BlacklistGuardService;
2930
import com.cadac.stone_inscription.util.UserResponse;
3031

3132
import lombok.RequiredArgsConstructor;
@@ -43,18 +44,25 @@ public class ReportService {
4344
private final AiModerationHandler aiModerationHandler;
4445
private final HumanModerationHandler humanModerationHandler;
4546
private final List<Specification<ReportValidationContext>> reportSpecifications;
47+
private final BlacklistGuardService blacklistGuardService;
4648

4749
public ResponseEntity<?> createReport(String reporterEmail, CreateReportRequest request) {
4850
User reporter = getUserByEmail(reporterEmail);
51+
blacklistGuardService.ensureCanReport(reporter);
4952
ResolvedReportTarget target = reportTargetResolver.resolve(request.getTargetType(), request.getTargetId());
5053

5154
validateReportRequest(reporter, target);
5255

5356
ModerationReport report = reportFactory.create(reporter, target, request);
5457
moderationReportRepository.save(report);
5558
reportActionService.markTargetUnderReview(target, reporter, request.getDetails());
59+
moderateReportInternal(report, target, null, null);
5660

57-
return UserResponse.responseHandler("Report created successfully", HttpStatus.CREATED, report);
61+
String message = report.getStatus() == ReportStatus.ESCALATED
62+
? "Report created and escalated for human moderation"
63+
: "Report created and moderated successfully";
64+
65+
return UserResponse.responseHandler(message, HttpStatus.CREATED, report);
5866
}
5967

6068
public ResponseEntity<?> getReports(String requesterEmail, ReportStatus status) {
@@ -82,19 +90,7 @@ public ResponseEntity<?> moderateReport(String actorEmail, String reportId, Mode
8290
ensureModerator(actorEmail);
8391
}
8492

85-
ModerationHandler moderationPipeline = buildModerationPipeline();
86-
ModerationExecutionContext context = ModerationExecutionContext.builder()
87-
.report(report)
88-
.target(target)
89-
.actor(actor)
90-
.actorLabel(actor.getId().toHexString())
91-
.requestedAction(request == null ? null : request.getAction())
92-
.note(request == null ? null : request.getNote())
93-
.reportActionService(reportActionService)
94-
.build();
95-
96-
moderationPipeline.handle(context);
97-
moderationReportRepository.save(report);
93+
moderateReportInternal(report, target, actor, request);
9894

9995
String message = report.getStatus() == ReportStatus.ESCALATED
10096
? "Report escalated for human moderation"
@@ -125,6 +121,26 @@ private ModerationHandler buildModerationPipeline() {
125121
return aiModerationHandler;
126122
}
127123

124+
private void moderateReportInternal(
125+
ModerationReport report,
126+
ResolvedReportTarget target,
127+
User actor,
128+
ModerateReportRequest request) {
129+
ModerationHandler moderationPipeline = buildModerationPipeline();
130+
ModerationExecutionContext context = ModerationExecutionContext.builder()
131+
.report(report)
132+
.target(target)
133+
.actor(actor)
134+
.actorLabel(actor == null || actor.getId() == null ? null : actor.getId().toHexString())
135+
.requestedAction(request == null ? null : request.getAction())
136+
.note(request == null ? null : request.getNote())
137+
.reportActionService(reportActionService)
138+
.build();
139+
140+
moderationPipeline.handle(context);
141+
moderationReportRepository.save(report);
142+
}
143+
128144
private User getUserByEmail(String email) {
129145
User user = userRepository.findByEmail(email);
130146
if (user == null) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.cadac.stone_inscription.user.service;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.stereotype.Service;
5+
6+
import com.cadac.stone_inscription.entity.User;
7+
import com.cadac.stone_inscription.exception.StoneInscriptionException;
8+
9+
@Service
10+
public class BlacklistGuardService {
11+
12+
public void ensureCanCreateOrModifyContent(User user) {
13+
ensureNotBlacklisted(user, "Blacklisted users cannot create or modify content.");
14+
}
15+
16+
public void ensureCanReport(User user) {
17+
ensureNotBlacklisted(user, "Blacklisted users cannot file reports.");
18+
}
19+
20+
private void ensureNotBlacklisted(User user, String message) {
21+
if (user != null && Boolean.TRUE.equals(user.getBlackListed())) {
22+
throw new StoneInscriptionException(message, HttpStatus.FORBIDDEN);
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)