Add more submission logic.
This commit is contained in:
parent
1ade7ffe66
commit
52be976286
|
@ -13,16 +13,20 @@ import nl.andrewlalis.gymboard_api.domains.submission.model.Submission;
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.submission.model.SubmissionProperties;
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service which handles the rather mundane tasks associated with exercise
|
* Service which handles the rather mundane tasks associated with exercise
|
||||||
|
@ -89,17 +93,23 @@ public class ExerciseSubmissionService {
|
||||||
if (weightUnit == WeightUnit.POUNDS) {
|
if (weightUnit == WeightUnit.POUNDS) {
|
||||||
metricWeight = WeightUnit.toKilograms(rawWeight);
|
metricWeight = WeightUnit.toKilograms(rawWeight);
|
||||||
}
|
}
|
||||||
Submission submission = submissionRepository.saveAndFlush(new Submission(
|
SubmissionProperties properties = new SubmissionProperties(
|
||||||
ulid.nextULID(), gym, exercise, user,
|
exercise,
|
||||||
performedAt,
|
performedAt,
|
||||||
payload.taskId(),
|
rawWeight,
|
||||||
rawWeight, weightUnit, metricWeight, payload.reps()
|
weightUnit,
|
||||||
));
|
payload.reps()
|
||||||
|
);
|
||||||
|
|
||||||
|
Submission submission = new Submission(ulid.nextULID(), gym, user, payload.taskId(), properties);
|
||||||
try {
|
try {
|
||||||
cdnClient.uploads.startTask(submission.getVideoProcessingTaskId());
|
cdnClient.uploads.startTask(submission.getVideoProcessingTaskId());
|
||||||
|
submission.setProcessing(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to start video processing task for submission " + submission.getId(), e);
|
log.error("Failed to start video processing task for submission.", e);
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to start video processing.");
|
||||||
}
|
}
|
||||||
|
submission = submissionRepository.save(submission);
|
||||||
return new SubmissionResponse(submission);
|
return new SubmissionResponse(submission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +135,7 @@ public class ExerciseSubmissionService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var status = cdnClient.uploads.getVideoProcessingTaskStatus(data.taskId());
|
var status = cdnClient.uploads.getVideoProcessingTaskStatus(data.taskId());
|
||||||
if (!status.status().equalsIgnoreCase("NOT_STARTED")) {
|
if (status == null || !status.status().equalsIgnoreCase("NOT_STARTED")) {
|
||||||
response.addMessage("Invalid video processing task.");
|
response.addMessage("Invalid video processing task.");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -156,6 +166,14 @@ public class ExerciseSubmissionService {
|
||||||
submissionRepository.delete(submission);
|
submissionRepository.delete(submission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked when the CDN calls this API's endpoint to notify
|
||||||
|
* us that a video processing task has completed. If the task completed
|
||||||
|
* successfully, we can set any related submissions' video and thumbnail
|
||||||
|
* file ids and remove its "processing" flag. Otherwise, we should delete
|
||||||
|
* the failed submission.
|
||||||
|
* @param payload The information about the task.
|
||||||
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void handleVideoProcessingComplete(VideoProcessingCompletePayload payload) {
|
public void handleVideoProcessingComplete(VideoProcessingCompletePayload payload) {
|
||||||
var submissionsToUpdate = submissionRepository.findUnprocessedByTaskId(payload.taskId());
|
var submissionsToUpdate = submissionRepository.findUnprocessedByTaskId(payload.taskId());
|
||||||
|
@ -164,6 +182,7 @@ public class ExerciseSubmissionService {
|
||||||
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());
|
||||||
|
submission.setProcessing(false);
|
||||||
submissionRepository.save(submission);
|
submissionRepository.save(submission);
|
||||||
// TODO: Send notification of successful processing to the user!
|
// TODO: Send notification of successful processing to the user!
|
||||||
} else if (payload.status().equalsIgnoreCase("FAILED")) {
|
} else if (payload.status().equalsIgnoreCase("FAILED")) {
|
||||||
|
@ -172,4 +191,94 @@ public class ExerciseSubmissionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A scheduled task that checks and resolves issues with any submission that
|
||||||
|
* stays in the "processing" state for too long.
|
||||||
|
* TODO: Find some way to clean up this mess of logic!
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.MINUTES)
|
||||||
|
public void checkProcessingSubmissions() {
|
||||||
|
var processingSubmissions = submissionRepository.findAllByProcessingTrue();
|
||||||
|
LocalDateTime actionCutoff = LocalDateTime.now().minus(Duration.ofMinutes(10));
|
||||||
|
LocalDateTime deleteCutoff = LocalDateTime.now().minus(Duration.ofMinutes(30));
|
||||||
|
for (var submission : processingSubmissions) {
|
||||||
|
if (submission.getCreatedAt().isBefore(actionCutoff)) {
|
||||||
|
// Sanity check to remove any inconsistent submission that doesn't have a task id for whatever reason.
|
||||||
|
if (submission.getVideoProcessingTaskId() == null) {
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because it doesn't have a task id.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var status = cdnClient.uploads.getVideoProcessingTaskStatus(submission.getVideoProcessingTaskId());
|
||||||
|
if (status == null) {
|
||||||
|
// The task no longer exists on the CDN, so remove the submission.
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because its task no longer exists on the CDN.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
} else if (status.status().equalsIgnoreCase("FAILED")) {
|
||||||
|
// The task failed, so we should remove the submission.
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because its task failed.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
} else if (status.status().equalsIgnoreCase("COMPLETED")) {
|
||||||
|
// The submission should be marked as complete.
|
||||||
|
submission.setVideoFileId(status.videoFileId());
|
||||||
|
submission.setThumbnailFileId(status.thumbnailFileId());
|
||||||
|
submission.setProcessing(false);
|
||||||
|
submissionRepository.save(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
} else if (status.status().equalsIgnoreCase("NOT_STARTED")) {
|
||||||
|
// If for whatever reason the submission's video processing never started, start now.
|
||||||
|
try {
|
||||||
|
cdnClient.uploads.startTask(submission.getVideoProcessingTaskId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to start processing task " + submission.getVideoProcessingTaskId(), e);
|
||||||
|
if (submission.getCreatedAt().isBefore(deleteCutoff)) {
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because it is waiting or processing for too long.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The task is waiting or processing, so delete the submission if it's been in that state for an unreasonably long time.
|
||||||
|
if (submission.getCreatedAt().isBefore(deleteCutoff)) {
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because it is waiting or processing for too long.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Couldn't fetch status of long-processing submission " + submission.getId() + " for user " + submission.getUser().getEmail(), e);
|
||||||
|
// We can't reliably remove this submission yet, so we'll try again on the next pass.
|
||||||
|
if (submission.getCreatedAt().isBefore(deleteCutoff)) {
|
||||||
|
log.warn(
|
||||||
|
"Removing long-processing submission {} for user {} because it is waiting or processing for too long.",
|
||||||
|
submission.getId(), submission.getUser().getEmail()
|
||||||
|
);
|
||||||
|
submissionRepository.delete(submission);
|
||||||
|
// TODO: Send notification to user.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ public interface SubmissionRepository extends JpaRepository<Submission, String>,
|
||||||
|
|
||||||
@Query("SELECT s FROM Submission s " +
|
@Query("SELECT s FROM Submission s " +
|
||||||
"WHERE s.videoProcessingTaskId = :taskId AND " +
|
"WHERE s.videoProcessingTaskId = :taskId AND " +
|
||||||
"(s.videoFileId IS NULL OR s.thumbnailFileId IS NULL)")
|
"s.processing = TRUE")
|
||||||
List<Submission> findUnprocessedByTaskId(long taskId);
|
List<Submission> findUnprocessedByTaskId(long taskId);
|
||||||
|
|
||||||
|
List<Submission> findAllByProcessingTrue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,34 +10,39 @@ public record SubmissionResponse(
|
||||||
String id,
|
String id,
|
||||||
String createdAt,
|
String createdAt,
|
||||||
GymSimpleResponse gym,
|
GymSimpleResponse gym,
|
||||||
ExerciseResponse exercise,
|
|
||||||
UserResponse user,
|
UserResponse user,
|
||||||
String performedAt,
|
|
||||||
long videoProcessingTaskId,
|
long videoProcessingTaskId,
|
||||||
String videoFileId,
|
String videoFileId,
|
||||||
String thumbnailFileId,
|
String thumbnailFileId,
|
||||||
|
boolean processing,
|
||||||
|
boolean verified,
|
||||||
|
|
||||||
|
// From SubmissionProperties
|
||||||
|
ExerciseResponse exercise,
|
||||||
|
String performedAt,
|
||||||
double rawWeight,
|
double rawWeight,
|
||||||
String weightUnit,
|
String weightUnit,
|
||||||
double metricWeight,
|
double metricWeight,
|
||||||
int reps,
|
int reps
|
||||||
boolean verified
|
|
||||||
) {
|
) {
|
||||||
public SubmissionResponse(Submission submission) {
|
public SubmissionResponse(Submission submission) {
|
||||||
this(
|
this(
|
||||||
submission.getId(),
|
submission.getId(),
|
||||||
StandardDateFormatter.format(submission.getCreatedAt()),
|
StandardDateFormatter.format(submission.getCreatedAt()),
|
||||||
new GymSimpleResponse(submission.getGym()),
|
new GymSimpleResponse(submission.getGym()),
|
||||||
new ExerciseResponse(submission.getExercise()),
|
|
||||||
new UserResponse(submission.getUser()),
|
new UserResponse(submission.getUser()),
|
||||||
StandardDateFormatter.format(submission.getPerformedAt()),
|
|
||||||
submission.getVideoProcessingTaskId(),
|
submission.getVideoProcessingTaskId(),
|
||||||
submission.getVideoFileId(),
|
submission.getVideoFileId(),
|
||||||
submission.getThumbnailFileId(),
|
submission.getThumbnailFileId(),
|
||||||
submission.getRawWeight().doubleValue(),
|
submission.isProcessing(),
|
||||||
submission.getWeightUnit().name(),
|
submission.isVerified(),
|
||||||
submission.getMetricWeight().doubleValue(),
|
|
||||||
submission.getReps(),
|
new ExerciseResponse(submission.getProperties().getExercise()),
|
||||||
submission.isVerified()
|
StandardDateFormatter.format(submission.getCreatedAt()),
|
||||||
|
submission.getProperties().getRawWeight().doubleValue(),
|
||||||
|
submission.getProperties().getWeightUnit().name(),
|
||||||
|
submission.getProperties().getMetricWeight().doubleValue(),
|
||||||
|
submission.getProperties().getReps()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,33 @@
|
||||||
package nl.andrewlalis.gymboard_api.domains.submission.model;
|
package nl.andrewlalis.gymboard_api.domains.submission.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.Exercise;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Submission entity represents a user's posted video of a lift they did at
|
||||||
|
* a gym.
|
||||||
|
* <p>
|
||||||
|
* A submission is created in the front-end using the following flow:
|
||||||
|
* </p>
|
||||||
|
* <ol>
|
||||||
|
* <li>User uploads a raw video of their lift.</li>
|
||||||
|
* <li>User enters some basic information about the lift.</li>
|
||||||
|
* <li>User submits the lift.</li>
|
||||||
|
* <li>API validates the information.</li>
|
||||||
|
* <li>API creates a new Submission, and tells the CDN service to process
|
||||||
|
* the uploaded video.</li>
|
||||||
|
* <li>Once processing completes successfully, the CDN sends the final video
|
||||||
|
* and thumbnail file ids to the API and the Submission's "processing" flag
|
||||||
|
* is removed.</li>
|
||||||
|
* <li>If for whatever reason the CDN's video processing fails or never
|
||||||
|
* completes, the Submission is deleted and the user is notified of the
|
||||||
|
* issue.</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "submission")
|
@Table(name = "submission")
|
||||||
public class Submission {
|
public class Submission {
|
||||||
|
@ -23,22 +41,16 @@ public class Submission {
|
||||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
private Gym gym;
|
private Gym gym;
|
||||||
|
|
||||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
|
||||||
private Exercise exercise;
|
|
||||||
|
|
||||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
private User user;
|
private User user;
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private LocalDateTime performedAt;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the video processing task that a user gives to us when they
|
* The id of the video processing task that a user gives to us when they
|
||||||
* create the submission, so that when the task finishes processing, we can
|
* create the submission, so that when the task finishes processing, we can
|
||||||
* route its data to the right submission.
|
* route its data to the right submission.
|
||||||
*/
|
*/
|
||||||
@Column(nullable = false, updatable = false)
|
@Column
|
||||||
private long videoProcessingTaskId;
|
private Long videoProcessingTaskId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the video file that was submitted for this submission. It lives
|
* The id of the video file that was submitted for this submission. It lives
|
||||||
|
@ -55,18 +67,19 @@ public class Submission {
|
||||||
@Column(length = 26)
|
@Column(length = 26)
|
||||||
private String thumbnailFileId = null;
|
private String thumbnailFileId = null;
|
||||||
|
|
||||||
@Column(nullable = false, precision = 7, scale = 2)
|
/**
|
||||||
private BigDecimal rawWeight;
|
* The user-specified properties of the submission.
|
||||||
|
*/
|
||||||
|
@Embedded
|
||||||
|
private SubmissionProperties properties;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
/**
|
||||||
|
* A flag that indicates whether this submission is currently processing.
|
||||||
|
* A submission is processing until its associated processing task completes
|
||||||
|
* either successfully or unsuccessfully.
|
||||||
|
*/
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private WeightUnit weightUnit;
|
private boolean processing;
|
||||||
|
|
||||||
@Column(nullable = false, precision = 7, scale = 2)
|
|
||||||
private BigDecimal metricWeight;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private int reps;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
|
@ -76,25 +89,15 @@ public class Submission {
|
||||||
public Submission(
|
public Submission(
|
||||||
String id,
|
String id,
|
||||||
Gym gym,
|
Gym gym,
|
||||||
Exercise exercise,
|
|
||||||
User user,
|
User user,
|
||||||
LocalDateTime performedAt,
|
|
||||||
long videoProcessingTaskId,
|
long videoProcessingTaskId,
|
||||||
BigDecimal rawWeight,
|
SubmissionProperties properties
|
||||||
WeightUnit unit,
|
|
||||||
BigDecimal metricWeight,
|
|
||||||
int reps
|
|
||||||
) {
|
) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.gym = gym;
|
this.gym = gym;
|
||||||
this.exercise = exercise;
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.performedAt = performedAt;
|
|
||||||
this.videoProcessingTaskId = videoProcessingTaskId;
|
this.videoProcessingTaskId = videoProcessingTaskId;
|
||||||
this.rawWeight = rawWeight;
|
this.properties = properties;
|
||||||
this.weightUnit = unit;
|
|
||||||
this.metricWeight = metricWeight;
|
|
||||||
this.reps = reps;
|
|
||||||
this.verified = false;
|
this.verified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,11 +113,7 @@ public class Submission {
|
||||||
return gym;
|
return gym;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exercise getExercise() {
|
public Long getVideoProcessingTaskId() {
|
||||||
return exercise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVideoProcessingTaskId() {
|
|
||||||
return videoProcessingTaskId;
|
return videoProcessingTaskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,24 +137,16 @@ public class Submission {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalDateTime getPerformedAt() {
|
public SubmissionProperties getProperties() {
|
||||||
return performedAt;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getRawWeight() {
|
public boolean isProcessing() {
|
||||||
return rawWeight;
|
return processing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WeightUnit getWeightUnit() {
|
public void setProcessing(boolean processing) {
|
||||||
return weightUnit;
|
this.processing = processing;
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMetricWeight() {
|
|
||||||
return metricWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReps() {
|
|
||||||
return reps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVerified() {
|
public boolean isVerified() {
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
package nl.andrewlalis.gymboard_api.domains.submission.model;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.Exercise;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A submission draft is a temporary entity that exists while a user is
|
|
||||||
* preparing their submission. It includes all the data needed to make a
|
|
||||||
* submission, so when the user has finished editing, they can "submit" their
|
|
||||||
* draft and video processing will then begin, and once done, their submission
|
|
||||||
* will be published.
|
|
||||||
* <p>
|
|
||||||
* <strong>This is not yet implemented!</strong>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@Table(name = "submission_draft")
|
|
||||||
public class SubmissionDraft {
|
|
||||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@CreationTimestamp
|
|
||||||
private LocalDateTime createdAt;
|
|
||||||
|
|
||||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
|
||||||
private User user;
|
|
||||||
|
|
||||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
|
||||||
private Gym gym;
|
|
||||||
|
|
||||||
// All of the following properties are editable while this draft has not yet
|
|
||||||
// been submitted. They will be validated upon submission.
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
private Exercise exercise;
|
|
||||||
|
|
||||||
@Column
|
|
||||||
private LocalDateTime performedAt;
|
|
||||||
|
|
||||||
@Column(precision = 7, scale = 2)
|
|
||||||
private BigDecimal rawWeight;
|
|
||||||
|
|
||||||
@Column @Enumerated(EnumType.STRING)
|
|
||||||
private WeightUnit weightUnit;
|
|
||||||
|
|
||||||
@Column
|
|
||||||
private int reps;
|
|
||||||
|
|
||||||
@Column
|
|
||||||
private long videoProcessingTaskId;
|
|
||||||
|
|
||||||
public SubmissionDraft() {}
|
|
||||||
|
|
||||||
public SubmissionDraft(User user, Gym gym) {
|
|
||||||
this.user = user;
|
|
||||||
this.gym = gym;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Gym getGym() {
|
|
||||||
return gym;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Exercise getExercise() {
|
|
||||||
return exercise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDateTime getPerformedAt() {
|
|
||||||
return performedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getRawWeight() {
|
|
||||||
return rawWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WeightUnit getWeightUnit() {
|
|
||||||
return weightUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReps() {
|
|
||||||
return reps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVideoProcessingTaskId() {
|
|
||||||
return videoProcessingTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExercise(Exercise exercise) {
|
|
||||||
this.exercise = exercise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPerformedAt(LocalDateTime performedAt) {
|
|
||||||
this.performedAt = performedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRawWeight(BigDecimal rawWeight) {
|
|
||||||
this.rawWeight = rawWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWeightUnit(WeightUnit weightUnit) {
|
|
||||||
this.weightUnit = weightUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReps(int reps) {
|
|
||||||
this.reps = reps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVideoProcessingTaskId(long videoProcessingTaskId) {
|
|
||||||
this.videoProcessingTaskId = videoProcessingTaskId;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.submission.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.api.model.Exercise;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic user-specified properties about a Submission.
|
||||||
|
*/
|
||||||
|
@Embeddable
|
||||||
|
public class SubmissionProperties {
|
||||||
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
|
private Exercise exercise;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime performedAt;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 7, scale = 2)
|
||||||
|
private BigDecimal rawWeight;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private WeightUnit weightUnit;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 7, scale = 2)
|
||||||
|
private BigDecimal metricWeight;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private int reps;
|
||||||
|
|
||||||
|
public SubmissionProperties() {}
|
||||||
|
|
||||||
|
public SubmissionProperties(
|
||||||
|
Exercise exercise,
|
||||||
|
LocalDateTime performedAt,
|
||||||
|
BigDecimal rawWeight,
|
||||||
|
WeightUnit weightUnit,
|
||||||
|
int reps
|
||||||
|
) {
|
||||||
|
this.exercise = exercise;
|
||||||
|
this.performedAt = performedAt;
|
||||||
|
this.rawWeight = rawWeight;
|
||||||
|
this.weightUnit = weightUnit;
|
||||||
|
this.metricWeight = WeightUnit.toKilograms(rawWeight, weightUnit);
|
||||||
|
this.reps = reps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exercise getExercise() {
|
||||||
|
return exercise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getPerformedAt() {
|
||||||
|
return performedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getRawWeight() {
|
||||||
|
return rawWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeightUnit getWeightUnit() {
|
||||||
|
return weightUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMetricWeight() {
|
||||||
|
return metricWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReps() {
|
||||||
|
return reps;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
||||||
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.UploadsClient;
|
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.UploadsClient;
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.submission.model.SubmissionProperties;
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -50,6 +51,8 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generate() throws Exception {
|
public void generate() throws Exception {
|
||||||
|
// First we generate a small set of uploaded files that all the
|
||||||
|
// submissions can link to, instead of having them all upload new content.
|
||||||
var uploads = generateUploads();
|
var uploads = generateUploads();
|
||||||
|
|
||||||
// Now that uploads are complete, we can proceed with generating the submissions.
|
// Now that uploads are complete, we can proceed with generating the submissions.
|
||||||
|
@ -76,8 +79,6 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
submissions.add(submission);
|
submissions.add(submission);
|
||||||
}
|
}
|
||||||
submissionRepository.saveAll(submissions);
|
submissionRepository.saveAll(submissions);
|
||||||
|
|
||||||
// After adding all the submissions, we'll signal to CDN that it can start processing.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Submission generateRandomSubmission(
|
private Submission generateRandomSubmission(
|
||||||
|
@ -97,20 +98,23 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
weightUnit = WeightUnit.POUNDS;
|
weightUnit = WeightUnit.POUNDS;
|
||||||
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
|
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
|
||||||
}
|
}
|
||||||
|
SubmissionProperties properties = new SubmissionProperties(
|
||||||
|
randomChoice(exercises, random),
|
||||||
|
time,
|
||||||
|
rawWeight,
|
||||||
|
weightUnit,
|
||||||
|
random.nextInt(13) + 1
|
||||||
|
);
|
||||||
|
|
||||||
var submission = new Submission(
|
var submission = new Submission(
|
||||||
ulid.nextULID(),
|
ulid.nextULID(),
|
||||||
randomChoice(gyms, random),
|
randomChoice(gyms, random),
|
||||||
randomChoice(exercises, random),
|
|
||||||
randomChoice(users, random),
|
randomChoice(users, random),
|
||||||
time,
|
|
||||||
randomChoice(new ArrayList<>(uploads.keySet()), random),
|
randomChoice(new ArrayList<>(uploads.keySet()), random),
|
||||||
rawWeight,
|
properties
|
||||||
weightUnit,
|
|
||||||
metricWeight,
|
|
||||||
random.nextInt(13) + 1
|
|
||||||
);
|
);
|
||||||
submission.setVerified(true);
|
submission.setVerified(true);
|
||||||
|
submission.setProcessing(false);
|
||||||
var uploadData = uploads.get(submission.getVideoProcessingTaskId());
|
var uploadData = uploads.get(submission.getVideoProcessingTaskId());
|
||||||
submission.setVideoFileId(uploadData.getFirst());
|
submission.setVideoFileId(uploadData.getFirst());
|
||||||
submission.setThumbnailFileId(uploadData.getSecond());
|
submission.setThumbnailFileId(uploadData.getSecond());
|
||||||
|
|
Loading…
Reference in New Issue