Added stuff

This commit is contained in:
Andrew Lalis 2023-04-05 16:51:58 +02:00
parent 55eb95e08a
commit af3435834f
5 changed files with 52 additions and 21 deletions

View File

@ -62,7 +62,8 @@ public class SecurityConfig {
"/auth/token", "/auth/token",
"/auth/register", "/auth/register",
"/auth/activate", "/auth/activate",
"/auth/reset-password" "/auth/reset-password",
"/submissions/video-processing-complete"
).permitAll() ).permitAll()
// Everything else must be authenticated, just to be safe. // Everything else must be authenticated, just to be safe.
.anyRequest().authenticated(); .anyRequest().authenticated();

View File

@ -5,6 +5,7 @@ import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@ -14,5 +15,8 @@ public interface SubmissionRepository extends JpaRepository<Submission, String>,
@Modifying @Modifying
void deleteAllByUser(User user); void deleteAllByUser(User user);
List<Submission> findAllByVideoProcessingTaskId(long taskId); @Query("SELECT s FROM Submission s " +
"WHERE s.videoProcessingTaskId = :taskId AND " +
"(s.videoFileId IS NULL OR s.thumbnailFileId IS NULL)")
List<Submission> findUnprocessedByTaskId(long taskId);
} }

View File

@ -157,7 +157,9 @@ public class ExerciseSubmissionService {
@Transactional @Transactional
public void handleVideoProcessingComplete(VideoProcessingCompletePayload payload) { public void handleVideoProcessingComplete(VideoProcessingCompletePayload payload) {
for (var submission : submissionRepository.findAllByVideoProcessingTaskId(payload.taskId())) { var submissionsToUpdate = submissionRepository.findUnprocessedByTaskId(payload.taskId());
log.info("Received video processing complete message from CDN: {}, affecting {} submissions.", payload, submissionsToUpdate.size());
for (var submission : submissionsToUpdate) {
if (payload.status().equalsIgnoreCase("COMPLETE")) { if (payload.status().equalsIgnoreCase("COMPLETE")) {
submission.setVideoFileId(payload.videoFileId()); submission.setVideoFileId(payload.videoFileId());
submission.setThumbnailFileId(payload.thumbnailFileId()); submission.setThumbnailFileId(payload.thumbnailFileId());

View File

@ -116,10 +116,15 @@ public class TokenService {
public Jws<Claims> getToken(String token) { public Jws<Claims> getToken(String token) {
if (token == null) return null; if (token == null) return null;
var builder = Jwts.parserBuilder() try {
.setSigningKey(this.getPrivateKey()) var builder = Jwts.parserBuilder()
.requireIssuer(ISSUER); .setSigningKey(this.getPrivateKey())
return builder.build().parseClaimsJws(token); .requireIssuer(ISSUER);
return builder.build().parseClaimsJws(token);
} catch (Exception e) {
log.warn("Error parsing JWT.", e);
return null;
}
} }
private PrivateKey getPrivateKey() { private PrivateKey getPrivateKey() {

View File

@ -1,5 +1,6 @@
package nl.andrewlalis.gymboardcdn.uploads.service; package nl.andrewlalis.gymboardcdn.uploads.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import nl.andrewlalis.gymboardcdn.files.FileMetadata; import nl.andrewlalis.gymboardcdn.files.FileMetadata;
@ -10,6 +11,7 @@ import nl.andrewlalis.gymboardcdn.uploads.service.process.ThumbnailGenerator;
import nl.andrewlalis.gymboardcdn.uploads.service.process.VideoProcessor; import nl.andrewlalis.gymboardcdn.uploads.service.process.VideoProcessor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -20,6 +22,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -36,6 +39,9 @@ public class VideoProcessingService {
private final ThumbnailGenerator thumbnailGenerator; private final ThumbnailGenerator thumbnailGenerator;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@Value("${app.api-origin}")
private String apiOrigin;
public VideoProcessingService(Executor videoProcessingExecutor, public VideoProcessingService(Executor videoProcessingExecutor,
VideoProcessingTaskRepository taskRepo, VideoProcessingTaskRepository taskRepo,
FileStorageService fileStorageService, FileStorageService fileStorageService,
@ -52,6 +58,9 @@ public class VideoProcessingService {
private void updateTask(VideoProcessingTask task, VideoProcessingTask.Status status) { private void updateTask(VideoProcessingTask task, VideoProcessingTask.Status status) {
task.setStatus(status); task.setStatus(status);
taskRepo.saveAndFlush(task); taskRepo.saveAndFlush(task);
if (status == VideoProcessingTask.Status.COMPLETED || status == VideoProcessingTask.Status.FAILED) {
sendTaskCompleteToApi(task);
}
} }
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
@ -143,9 +152,6 @@ public class VideoProcessingService {
task.setThumbnailFileId(thumbnailFileId); task.setThumbnailFileId(thumbnailFileId);
updateTask(task, VideoProcessingTask.Status.COMPLETED); updateTask(task, VideoProcessingTask.Status.COMPLETED);
log.info("Finished processing task {}.", task.getId()); log.info("Finished processing task {}.", task.getId());
// Send HTTP POST to API, with video id and thumbnail id.
sendProcessedDataToApi(videoFileId, thumbnailFileId);
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to copy processed video to final storage location.", e); log.error("Failed to copy processed video to final storage location.", e);
updateTask(task, VideoProcessingTask.Status.FAILED); updateTask(task, VideoProcessingTask.Status.FAILED);
@ -162,24 +168,37 @@ public class VideoProcessingService {
} }
} }
private void sendProcessedDataToApi(String videoId, String thumbnailId) throws IOException { /**
* Sends an update message to the Gymboard API when a task finishes its
* processing.
* @param task The task to send.
*/
private void sendTaskCompleteToApi(VideoProcessingTask task) {
ObjectNode obj = objectMapper.createObjectNode(); ObjectNode obj = objectMapper.createObjectNode();
obj.put("videoFileId", videoId); obj.put("taskId", task.getId());
obj.put("thumbnailFileId", thumbnailId); obj.put("status", task.getStatus().name());
String json = objectMapper.writeValueAsString(obj); obj.put("videoFileId", task.getVideoFileId());
obj.put("thumbnailFileId", task.getThumbnailFileId());
String json;
try {
json = objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("JSON error while sending task data to API for task " + task.getId(), e);
return;
}
HttpClient httpClient = HttpClient.newBuilder().build(); HttpClient httpClient = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/submissions/123/processed-data")) HttpRequest request = HttpRequest.newBuilder(URI.create(apiOrigin + "/submissions/video-processing-complete"))
.header("Authorization", "Bearer bullshit") .header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(3))
.POST(HttpRequest.BodyPublishers.ofString(json)) .POST(HttpRequest.BodyPublishers.ofString(json))
.build(); .build();
try { try {
HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding());
if (response.statusCode() == 200) { if (response.statusCode() >= 400) {
// We can now delete the task. log.error("API returned not-ok response {}", response.statusCode());
} }
} catch (InterruptedException e) { } catch (Exception e) {
// TODO: Retry! log.error("Failed to send HTTP request to API.", e);
throw new RuntimeException(e);
} }
} }
} }