Add final controller endpoints.
This commit is contained in:
parent
ffe1d9bd40
commit
55eb95e08a
|
@ -1,6 +1,8 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.api.controller;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.SubmissionResponse;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.VideoProcessingCompletePayload;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.UploadsClient;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
@ -26,4 +28,11 @@ public class SubmissionController {
|
|||
submissionService.deleteSubmission(submissionId, user);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/video-processing-complete")
|
||||
public ResponseEntity<Void> handleVideoProcessingComplete(@RequestBody VideoProcessingCompletePayload taskStatus) {
|
||||
// TODO: Validate that the request came ONLY from the CDN service.
|
||||
submissionService.handleVideoProcessingComplete(taskStatus);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,12 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface SubmissionRepository extends JpaRepository<Submission, String>, JpaSpecificationExecutor<Submission> {
|
||||
@Modifying
|
||||
void deleteAllByUser(User user);
|
||||
|
||||
List<Submission> findAllByVideoProcessingTaskId(long taskId);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||
|
||||
public record VideoProcessingCompletePayload(
|
||||
long taskId,
|
||||
String status,
|
||||
String videoFileId,
|
||||
String thumbnailFileId
|
||||
) {}
|
|
@ -16,6 +16,7 @@ public class CdnClient {
|
|||
private final ObjectMapper objectMapper;
|
||||
|
||||
public final UploadsClient uploads;
|
||||
public final FilesClient files;
|
||||
|
||||
public CdnClient(String baseUrl) {
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
|
@ -25,6 +26,7 @@ public class CdnClient {
|
|||
this.baseUrl = baseUrl;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.uploads = new UploadsClient(this);
|
||||
this.files = new FilesClient(this);
|
||||
}
|
||||
|
||||
public <T> T get(String urlPath, Class<T> responseType) throws IOException, InterruptedException {
|
||||
|
@ -60,4 +62,13 @@ public class CdnClient {
|
|||
throw new IOException("Request failed with code " + response.statusCode());
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String urlPath) throws IOException, InterruptedException {
|
||||
HttpRequest req = HttpRequest.newBuilder(URI.create(baseUrl + urlPath))
|
||||
.DELETE().build();
|
||||
HttpResponse<Void> response = httpClient.send(req, HttpResponse.BodyHandlers.discarding());
|
||||
if (response.statusCode() >= 400) {
|
||||
throw new IOException("Request failed with code " + response.statusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.api.service.cdn_client;
|
||||
|
||||
public record FilesClient(CdnClient client) {
|
||||
public record FileMetadataResponse(
|
||||
String filename,
|
||||
String mimeType,
|
||||
long size,
|
||||
String createdAt
|
||||
) {}
|
||||
|
||||
public FileMetadataResponse getFileMetadata(String id) throws Exception {
|
||||
return client.get("/files/" + id + "/metadata", FileMetadataResponse.class);
|
||||
}
|
||||
|
||||
public void deleteFile(String id) throws Exception {
|
||||
client.delete("/files/" + id);
|
||||
}
|
||||
}
|
|
@ -10,14 +10,6 @@ public record UploadsClient(CdnClient client) {
|
|||
String thumbnailFileId
|
||||
) {}
|
||||
|
||||
public record FileMetadataResponse(
|
||||
String filename,
|
||||
String mimeType,
|
||||
long size,
|
||||
String uploadedAt,
|
||||
boolean availableForDownload
|
||||
) {}
|
||||
|
||||
public long uploadVideo(Path filePath, String contentType) throws Exception {
|
||||
return client.postFile("/uploads/video", filePath, contentType, FileUploadResponse.class).taskId();
|
||||
}
|
||||
|
@ -26,10 +18,6 @@ public record UploadsClient(CdnClient client) {
|
|||
return client.get("/uploads/video/" + id + "/status", VideoProcessingTaskStatusResponse.class);
|
||||
}
|
||||
|
||||
public FileMetadataResponse getFileMetadata(String id) throws Exception {
|
||||
return client.get("/files/" + id + "/metadata", FileMetadataResponse.class);
|
||||
}
|
||||
|
||||
public void startTask(long taskId) throws Exception {
|
||||
client.post("/uploads/video/" + taskId + "/start");
|
||||
}
|
||||
|
|
|
@ -141,7 +141,32 @@ public class ExerciseSubmissionService {
|
|||
if (!submission.getUser().getId().equals(user.getId())) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Cannot delete other user's submission.");
|
||||
}
|
||||
// TODO: Find a secure way to delete the associated video.
|
||||
try {
|
||||
|
||||
if (submission.getVideoFileId() != null) {
|
||||
cdnClient.files.deleteFile(submission.getVideoFileId());
|
||||
}
|
||||
if (submission.getThumbnailFileId() != null) {
|
||||
cdnClient.files.deleteFile(submission.getThumbnailFileId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Couldn't delete CDN content for submission " + submissionId, e);
|
||||
}
|
||||
submissionRepository.delete(submission);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void handleVideoProcessingComplete(VideoProcessingCompletePayload payload) {
|
||||
for (var submission : submissionRepository.findAllByVideoProcessingTaskId(payload.taskId())) {
|
||||
if (payload.status().equalsIgnoreCase("COMPLETE")) {
|
||||
submission.setVideoFileId(payload.videoFileId());
|
||||
submission.setThumbnailFileId(payload.thumbnailFileId());
|
||||
submissionRepository.save(submission);
|
||||
// TODO: Send notification of successful processing to the user!
|
||||
} else if (payload.status().equalsIgnoreCase("FAILED")) {
|
||||
submissionRepository.delete(submission);
|
||||
// TODO: Send notification of failed video processing to the user!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.gymboardcdn.files;
|
|||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
@ -35,4 +36,14 @@ public class FileController {
|
|||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Couldn't read file metadata.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/files/{id}")
|
||||
public void deleteFile(@PathVariable String id) {
|
||||
// TODO: Secure this so only API can access it!
|
||||
try {
|
||||
fileStorageService.delete(id);
|
||||
} catch (IOException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to delete file.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue