Skip to content

Commit 6abe894

Browse files
authored
Merge pull request #30 from Jalen-Stephens/5-api-implement-analyzecontroller-endpoints
[API] Implement AnalyzeController Endpoints #5
2 parents 1c309e8 + 301d355 commit 6abe894

File tree

7 files changed

+223
-236
lines changed

7 files changed

+223
-236
lines changed

citations.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,4 +1463,61 @@ Assisted with configuring the JaCoCo and PMD reporting outputs, improving test c
14631463

14641464
> Portions of this commit or configuration were generated with assistance from OpenAI ChatGPT (GPT-5) on October 23, 2025. All AI-generated content was reviewed, verified, and finalized by the development team.
14651465
1466+
---
1467+
1468+
### **Commit / Ticket Reference**
1469+
1470+
* **Commit:** `feat(API): implement full AnalyzeController endpoints and wire to AnalyzeService (refs #5)`
1471+
* **Ticket:** `#5 — Implement AnalyzeController endpoints`
1472+
* **Date:** October 23, 2025
1473+
* **Team Member:** Jalen Stephens
1474+
1475+
---
1476+
1477+
### **AI Tool Information**
1478+
1479+
* **Tool Used:** OpenAI ChatGPT (GPT-5)
1480+
* **Access Method:** ChatGPT Web (.edu academic access)
1481+
* **Configuration:** Default model settings
1482+
* **Cost:** $0 (no paid API calls)
1483+
1484+
---
1485+
1486+
### **Purpose of AI Assistance**
1487+
1488+
The AI was used to help design and implement the REST controller layer for the image analysis pipeline, ensuring correct delegation to `AnalyzeService`, aligning DTO usage, structuring endpoint semantics, and clarifying the expected Supabase interaction and ownership validation flow.
1489+
1490+
---
1491+
1492+
### **Prompts / Interaction Summary**
1493+
1494+
* Asked for a revised controller ticket that references Supabase-backed storage and the new service-layer pipeline.
1495+
* Requested a full `AnalyzeController.java` implementation aligned with the existing `AnalyzeService`.
1496+
* Discussed C2PA integration, error handling, and JSON output expectations.
1497+
* Verified controller behavior for analysis start, metadata retrieval, confidence polling, and compare stub behavior.
1498+
1499+
---
1500+
1501+
### **Resulting Artifacts**
1502+
1503+
* `AnalyzeController.java` created/rewritten with full HTTP endpoint implementations
1504+
* Wiring and validations integrated with `AnalyzeService`
1505+
* Service changes to propagate errors correctly to the controller layer
1506+
* Adjustments in `C2paToolInvoker` and Supabase logic to improve behavior consistency
1507+
1508+
---
1509+
1510+
### **Verification**
1511+
1512+
* Application boot & manual lifecycle testing via HTTP requests
1513+
* Verified successful `analysisId` return flow and database persistence
1514+
* Verified manifest return and Supabase storage fetch path correctness
1515+
* Verified structured JSON error responses during C2PA failures
1516+
1517+
---
1518+
1519+
### **Attribution Statement**
1520+
1521+
> Portions of this commit or configuration were generated with assistance from OpenAI ChatGPT (GPT-5) on October 23, 2025. All AI-generated content was reviewed, verified, and finalized by the development team.
1522+
14661523
---
Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package dev.coms4156.project.metadetect.c2pa;
22

33
import java.io.File;
4-
import java.io.FileWriter;
54
import java.io.IOException;
65
import java.io.InputStream;
76
import java.nio.charset.StandardCharsets;
87
import java.util.Scanner;
98

10-
119
/** Invokes the C2PA command-line tool to extract manifests from images.*/
1210
public class C2paToolInvoker {
1311

@@ -26,52 +24,33 @@ public C2paToolInvoker(String c2paToolPath) {
2624
*
2725
*/
2826
public String extractManifest(File imageFile) throws IOException {
29-
// Create a temporary file for the output
30-
31-
32-
33-
// Command to invoke the C2PA tool
34-
ProcessBuilder processBuilder = new ProcessBuilder(
27+
ProcessBuilder pb = new ProcessBuilder(
3528
c2paToolPath,
3629
imageFile.getAbsolutePath(),
37-
"-d"
30+
"-d" // your tool version expects this
3831
);
39-
40-
// Start the process
41-
Process process = processBuilder.start();
42-
String output;
43-
String returnString;
44-
try {
45-
// Wait for the process to complete
46-
int exitCode = process.waitFor();
47-
if (exitCode != 0) {
48-
throw new IOException("C2PA tool failed with exit code " + exitCode);
49-
}
50-
51-
// Read the output file and return its contents as a string
52-
try (InputStream is = process.getInputStream();
53-
Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) {
54-
output = scanner.useDelimiter("\\A").next();
55-
56-
returnString = output;
57-
58-
} catch (Exception e) {
59-
throw new IOException("Failed to read C2PA tool output", e);
32+
pb.redirectErrorStream(false);
33+
34+
Process proc = pb.start();
35+
try (InputStream out = proc.getInputStream();
36+
InputStream err = proc.getErrorStream();
37+
Scanner so = new Scanner(out, StandardCharsets.UTF_8);
38+
Scanner se =
39+
new Scanner(err, StandardCharsets.UTF_8)) {
40+
41+
int exit = proc.waitFor();
42+
String stdout = so.useDelimiter("\\A").hasNext() ? so.next() : "";
43+
String stderr = se.useDelimiter("\\A").hasNext() ? se.next() : "";
44+
45+
if (exit != 0) {
46+
String msg = "C2PA tool failed with exit code " + exit
47+
+ (stderr.isBlank() ? "" : " | stderr: " + stderr);
48+
throw new IOException(msg);
6049
}
61-
62-
} catch (InterruptedException e) {
63-
throw new IOException("C2PA tool execution was interrupted", e);
64-
}
65-
66-
try (FileWriter file = new FileWriter("output.json")) {
67-
file.write(output);
68-
file.flush();
69-
70-
return returnString;
71-
72-
} catch (IOException e) {
73-
throw new IOException("Failed to write output to file", e);
50+
return stdout; // should already be JSON from -d
51+
} catch (InterruptedException ie) {
52+
Thread.currentThread().interrupt();
53+
throw new IOException("C2PA tool execution was interrupted", ie);
7454
}
75-
7655
}
7756
}
Lines changed: 81 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,81 @@
1-
//package dev.coms4156.project.metadetect.controller;
2-
//
3-
//import dev.coms4156.project.metadetect.dto.Dtos;
4-
//import dev.coms4156.project.metadetect.service.AnalyzeService;
5-
//import java.io.IOException;
6-
//import java.io.InputStream;
7-
//import java.time.Instant;
8-
//import java.util.Map;
9-
//import org.springframework.http.HttpStatus;
10-
//import org.springframework.http.ResponseEntity;
11-
//import org.springframework.web.bind.annotation.GetMapping;
12-
//import org.springframework.web.bind.annotation.PathVariable;
13-
//import org.springframework.web.bind.annotation.PostMapping;
14-
//import org.springframework.web.bind.annotation.RequestBody;
15-
//import org.springframework.web.bind.annotation.RequestMapping;
16-
//import org.springframework.web.bind.annotation.RequestParam;
17-
//import org.springframework.web.bind.annotation.RequestPart;
18-
//import org.springframework.web.bind.annotation.RestController;
19-
//import org.springframework.web.multipart.MultipartFile;
20-
//import org.springframework.web.server.ResponseStatusException;
21-
//
22-
///**
23-
// * Endpoints for analysis, metadata, confidence, compare.
24-
// */
25-
//@RestController
26-
//@RequestMapping("/api")
27-
//public class AnalyzeController {
28-
//
29-
// private final AnalyzeService analyzeService;
30-
//
31-
// public AnalyzeController(AnalyzeService analyzeService) {
32-
// this.analyzeService = analyzeService;
33-
// }
34-
// /**
35-
// * Handles submission of a new image for analysis.
36-
// * This endpoint accepts a multipart/form-data request containing an image file
37-
// * and optional analysis options. It validates and queues the image for
38-
// * processing, returning an {@link Dtos.AnalyzeResponse} with the job ID and
39-
// * current status.
40-
// *
41-
// * @param image the uploaded image file to be analyzed (required)
42-
// * @param options optional parameters controlling which analysis modules to run
43-
// * @return a {@link ResponseEntity} containing the analysis job ID, confidence
44-
// * placeholder, and initial status
45-
// */
46-
//
47-
// @PostMapping(value = "/analyze", consumes = {"multipart/form-data"})
48-
// public ResponseEntity<Dtos.AnalyzeResponse> analyze(
49-
// @RequestPart("image") MultipartFile image,
50-
// @RequestPart(value = "options", required = false) Dtos.AnalyzeOptions options) {
51-
// // TODO: validate mime/size; persist; enqueue/compute; return id + status
52-
// Dtos.AnalyzeResponse stub =
53-
// new Dtos.AnalyzeResponse("stub-id", 0.42, "PENDING", Instant.now(), null);
54-
// return ResponseEntity.status(HttpStatus.ACCEPTED).body(stub);
55-
// }
56-
//
57-
//
58-
// // /**
59-
// // * Extracts the C2PA manifest from an uploaded image file.
60-
// // * This endpoint accepts a multipart/form-data request containing an image file.
61-
// // * It processes the file using the C2PA tool to extract the manifest metadata.
62-
// // * The extracted manifest is returned as a JSON string.
63-
// // *
64-
// // * @param file the uploaded image file (required).
65-
// // * @return a {@link ResponseEntity} containing the extracted C2PA manifest as a JSON string.
66-
// // * @throws ResponseStatusException
67-
// // * if the extraction fails due to an I/O error or invalid input.
68-
// // */
69-
// // @PostMapping(value = "/extract", consumes = "multipart/form-data")
70-
// // public ResponseEntity<String> extract(@RequestParam("file") MultipartFile file) {
71-
// // try {
72-
// // // Extract the C2PA manifest
73-
// // //InputStream in = file.getInputStream();
74-
// // //String manifestJson = analyzeService.fetchC2pa(in);
75-
// //
76-
// // // String manifestJson = analyzeService.fetchC2pa(new java.io.File("tempfile"));
77-
// //
78-
// // // Return the JSON response
79-
// // return ResponseEntity.ok(manifestJson);
80-
// // } catch (IOException e) {
81-
// // throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
82-
// // "Failed to extract C2PA manifest", e);
83-
// // }
84-
// // }
85-
//
86-
// /**
87-
// * Retrieves metadata for a specific image analysis job. (TODO: IMPLEMENT/DELETE)
88-
// * This endpoint fetches parsed EXIF and other metadata associated with the given job ID.
89-
// * The metadata is returned as a {@link Dtos.MetadataResponse} object.
90-
// *
91-
// * @param id the unique identifier of the image analysis job (required).
92-
// * @return a {@link ResponseEntity} containing the metadata associated with the job ID.
93-
// */
94-
// @GetMapping("/metadata/{id}")
95-
// public ResponseEntity<Dtos.MetadataResponse> metadata(@PathVariable String id) {
96-
// // TODO: fetch parsed EXIF/metadata from DB
97-
// return ResponseEntity.ok(new Dtos.MetadataResponse(id, Map.of()));
98-
// }
99-
//
100-
// /**
101-
// * Retrieves the confidence score and status for a specific image analysis job.
102-
// * This endpoint fetches the confidence score and current status associated with the given job ID.
103-
// * The confidence score represents the likelihood of the analysis being correct, and the status
104-
// * indicates the current state of the analysis (e.g., PENDING, COMPLETED).
105-
// *
106-
// * @param id the unique identifier of the image analysis job (required).
107-
// * @return a {@link ResponseEntity} containing the confidence score and status for the job ID.
108-
// */
109-
// @GetMapping("/confidence/{id}")
110-
// public ResponseEntity<Dtos.ConfidenceResponse> confidence(@PathVariable String id) {
111-
// // TODO: fetch score + status from DB
112-
// return ResponseEntity.ok(new Dtos.ConfidenceResponse(id, 0.42, "PENDING"));
113-
// }
114-
//
115-
// /**
116-
// * Compares two images or analysis results to compute their similarity.
117-
// * This endpoint accepts either image files or analysis job IDs to compare the similarity between
118-
// * two images. It supports both file-based comparison and ID-based comparison modes.
119-
// * The similarity score is returned as a percentage value.
120-
// *
121-
// * @param byIds an optional {@link Dtos.CompareRequest} containing job IDs for comparison.
122-
// * @param imageA an optional {@link MultipartFile} representing the first image file to compare.
123-
// * @param imageB an optional {@link MultipartFile} representing the second image file to compare.
124-
// * @return a {@link ResponseEntity} containing the similarity score and details of the comparison.
125-
// */
126-
// @PostMapping(value = "/compare", consumes = {"application/json",
127-
// "multipart/form-data"})
128-
// public ResponseEntity<Dtos.CompareResponse> compare(
129-
// @RequestBody(required = false) Dtos.CompareRequest byIds,
130-
// @RequestPart(value = "imageA", required = false) MultipartFile imageA,
131-
// @RequestPart(value = "imageB", required = false) MultipartFile imageB) {
132-
// // TODO: support id mode and file mode; compute similarity
133-
// return ResponseEntity.ok(new Dtos.CompareResponse("a", "b", 0.13));
134-
// }
135-
//}
1+
package dev.coms4156.project.metadetect.controller;
2+
3+
import dev.coms4156.project.metadetect.dto.Dtos;
4+
import dev.coms4156.project.metadetect.service.AnalyzeService;
5+
import java.util.UUID;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PathVariable;
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RequestParam;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
16+
/**
17+
* REST controller for image analysis operations.
18+
* Endpoints:
19+
* - POST /api/analyze/{imageId} -> start an analysis (202 Accepted)
20+
* - GET /api/analyze/{analysisId} -> status/score (polling)
21+
* - GET /api/analyze/{analysisId}/manifest -> manifest JSON
22+
* - GET /api/analyze/compare -> stubbed compare (left & right image IDs)
23+
* Notes:
24+
* - Ownership and RLS checks are enforced in AnalyzeService/ImageService.
25+
* - Exceptions (Forbidden/NotFound/etc.)
26+
* are expected to be mapped by global @RestControllerAdvice.
27+
*/
28+
@RestController
29+
@RequestMapping("/api/analyze")
30+
public class AnalyzeController {
31+
32+
private final AnalyzeService analyzeService;
33+
34+
public AnalyzeController(AnalyzeService analyzeService) {
35+
this.analyzeService = analyzeService;
36+
}
37+
38+
/**
39+
* Starts analysis for an existing image that is already uploaded to Supabase Storage.
40+
* Returns 202 with a body containing the new analysisId.
41+
*/
42+
@PostMapping("/{imageId}")
43+
public ResponseEntity<Dtos.AnalyzeStartResponse> submit(@PathVariable UUID imageId) {
44+
Dtos.AnalyzeStartResponse resp = analyzeService.submitAnalysis(imageId);
45+
// As per ticket: 202 Accepted with { analysisId }
46+
return ResponseEntity.status(HttpStatus.ACCEPTED).body(resp);
47+
}
48+
49+
/**
50+
* Returns current status (PENDING/COMPLETED/FAILED) and an optional score (stubbed).
51+
* Suitable for client-side polling.
52+
*/
53+
@GetMapping("/{analysisId}")
54+
public ResponseEntity<Dtos.AnalyzeConfidenceResponse> getStatus(@PathVariable UUID analysisId) {
55+
Dtos.AnalyzeConfidenceResponse resp = analyzeService.getConfidence(analysisId);
56+
return ResponseEntity.ok(resp);
57+
}
58+
59+
/**
60+
* Returns the stored C2PA manifest JSON for a completed analysis.
61+
*/
62+
@GetMapping("/{analysisId}/manifest")
63+
public ResponseEntity<Dtos.AnalysisManifestResponse> getManifest(@PathVariable UUID analysisId) {
64+
Dtos.AnalysisManifestResponse resp = analyzeService.getMetadata(analysisId);
65+
return ResponseEntity.ok(resp);
66+
}
67+
68+
/**
69+
* Stubbed comparison endpoint (Iteration 1).
70+
* Ownership of both images is validated by the service layer.
71+
* Example: /api/analyze/compare?left={imageId}&right={imageId}
72+
*/
73+
@GetMapping("/compare")
74+
public ResponseEntity<Dtos.AnalyzeCompareResponse> compare(
75+
@RequestParam("left") UUID leftImageId,
76+
@RequestParam("right") UUID rightImageId) {
77+
78+
Dtos.AnalyzeCompareResponse resp = analyzeService.compare(leftImageId, rightImageId);
79+
return ResponseEntity.ok(resp);
80+
}
81+
}

src/main/java/dev/coms4156/project/metadetect/model/AnalysisReport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public String toString() {
164164
*/
165165
public enum ReportStatus {
166166
PENDING,
167-
COMPLETED,
167+
DONE,
168168
FAILED
169169
}
170170
}

0 commit comments

Comments
 (0)