From 2881dc5376f268af7c6875f60baf33696fc9b947 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Fri, 7 Apr 2023 12:17:19 +0200 Subject: [PATCH] Fixed the exercise submission front-end again. --- .../domains/api/service/GymService.java | 3 +- .../api/service/LeaderboardService.java | 4 ++- .../submission/ExerciseSubmissionService.java | 34 ++++++++++++------ .../dao/UserAccountDataRequestRepository.java | 5 +++ .../service/UserAccountDeletionService.java | 16 ++++++++- .../dao/SubmissionReportRepository.java | 13 +++++++ .../dao/SubmissionVoteRepository.java | 13 +++++++ .../submission/dto/SubmissionPayload.java | 4 +-- gymboard-app/src/api/main/gyms.ts | 4 +-- gymboard-app/src/api/main/leaderboards.ts | 4 +-- gymboard-app/src/api/main/submission.ts | 21 ++++++----- gymboard-app/src/api/main/users.ts | 6 ++-- .../components/ExerciseSubmissionListItem.vue | 4 +-- gymboard-app/src/pages/SubmissionPage.vue | 36 +++++++++++++------ gymboard-app/src/pages/gym/GymHomePage.vue | 4 +-- .../src/pages/gym/GymLeaderboardsPage.vue | 4 +-- .../src/pages/user/UserSubmissionsPage.vue | 6 ++-- 17 files changed, 129 insertions(+), 52 deletions(-) create mode 100644 gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionReportRepository.java create mode 100644 gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionVoteRepository.java diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/GymService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/GymService.java index 62740af..e948cf5 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/GymService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/GymService.java @@ -42,13 +42,14 @@ public class GymService { .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); return submissionRepository.findAll((root, query, criteriaBuilder) -> { query.orderBy( - criteriaBuilder.desc(root.get("performedAt")), + criteriaBuilder.desc(root.get("properties").get("performedAt")), criteriaBuilder.desc(root.get("createdAt")) ); query.distinct(true); return PredicateBuilder.and(criteriaBuilder) .with(criteriaBuilder.equal(root.get("gym"), gym)) .with(criteriaBuilder.isTrue(root.get("verified"))) + .with(criteriaBuilder.isFalse(root.get("processing"))) .build(); }, PageRequest.of(0, 5)) .map(SubmissionResponse::new) diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/LeaderboardService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/LeaderboardService.java index 7b4293d..6286592 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/LeaderboardService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/LeaderboardService.java @@ -55,8 +55,10 @@ public class LeaderboardService { query.distinct(true); query.orderBy(criteriaBuilder.desc(root.get("metricWeight"))); + // Basic predicates that should always hold. PredicateBuilder pb = PredicateBuilder.and(criteriaBuilder) - .with(criteriaBuilder.isTrue(root.get("verified"))); + .with(criteriaBuilder.isTrue(root.get("verified"))) + .with(criteriaBuilder.isFalse(root.get("processing"))); cutoffTime.ifPresent(time -> pb.with(criteriaBuilder.greaterThan(root.get("performedAt"), time))); optionalExercise.ifPresent(exercise -> pb.with(criteriaBuilder.equal(root.get("exercise"), exercise))); diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/submission/ExerciseSubmissionService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/submission/ExerciseSubmissionService.java index e468b09..3591127 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/submission/ExerciseSubmissionService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/api/service/submission/ExerciseSubmissionService.java @@ -25,7 +25,9 @@ import org.springframework.web.server.ResponseStatusException; import java.math.BigDecimal; import java.time.Duration; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; import java.util.concurrent.TimeUnit; /** @@ -85,8 +87,10 @@ public class ExerciseSubmissionService { } // Create the submission. - LocalDateTime performedAt = payload.performedAt(); - if (performedAt == null) performedAt = LocalDateTime.now(); + LocalDateTime performedAt = LocalDateTime.now(); + if (payload.performedAt() != null) { + performedAt = LocalDate.parse(payload.performedAt()).atTime(performedAt.toLocalTime()); + } BigDecimal rawWeight = BigDecimal.valueOf(payload.weight()); WeightUnit weightUnit = WeightUnit.parse(payload.weightUnit()); BigDecimal metricWeight = BigDecimal.valueOf(payload.weight()); @@ -109,18 +113,26 @@ public class ExerciseSubmissionService { 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); + submission = submissionRepository.saveAndFlush(submission); return new SubmissionResponse(submission); } private ValidationResponse validateSubmissionData(Gym gym, User user, Exercise exercise, SubmissionPayload data) { ValidationResponse response = new ValidationResponse(); + LocalDateTime now = LocalDateTime.now(); LocalDateTime cutoff = LocalDateTime.now().minusDays(3); - if (data.performedAt() != null && data.performedAt().isAfter(LocalDateTime.now())) { - response.addMessage("Cannot submit an exercise from the future."); - } - if (data.performedAt() != null && data.performedAt().isBefore(cutoff)) { - response.addMessage("Cannot submit an exercise too far in the past."); + if (data.performedAt() != null) { + try { + LocalDateTime performedAt = LocalDate.parse(data.performedAt()).atTime(now.toLocalTime()); + if (performedAt.isAfter(now)) { + response.addMessage("Cannot submit an exercise from the future."); + } + if (performedAt.isBefore(cutoff)) { + response.addMessage("Cannot submit an exercise too far in the past."); + } + } catch (DateTimeParseException e) { + response.addMessage("Invalid performedAt format."); + } } if (data.reps() < 1 || data.reps() > 500) { response.addMessage("Invalid rep count."); @@ -179,7 +191,7 @@ public class ExerciseSubmissionService { 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("COMPLETED")) { submission.setVideoFileId(payload.videoFileId()); submission.setThumbnailFileId(payload.thumbnailFileId()); submission.setProcessing(false); @@ -197,10 +209,10 @@ public class ExerciseSubmissionService { * 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) + @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) public void checkProcessingSubmissions() { var processingSubmissions = submissionRepository.findAllByProcessingTrue(); - LocalDateTime actionCutoff = LocalDateTime.now().minus(Duration.ofMinutes(10)); + LocalDateTime actionCutoff = LocalDateTime.now().minus(Duration.ofMinutes(3)); LocalDateTime deleteCutoff = LocalDateTime.now().minus(Duration.ofMinutes(30)); for (var submission : processingSubmissions) { if (submission.getCreatedAt().isBefore(actionCutoff)) { diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserAccountDataRequestRepository.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserAccountDataRequestRepository.java index 7910390..ecea387 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserAccountDataRequestRepository.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserAccountDataRequestRepository.java @@ -1,10 +1,15 @@ package nl.andrewlalis.gymboard_api.domains.auth.dao; +import nl.andrewlalis.gymboard_api.domains.auth.model.User; import nl.andrewlalis.gymboard_api.domains.auth.model.UserAccountDataRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.stereotype.Repository; @Repository public interface UserAccountDataRequestRepository extends JpaRepository { boolean existsByUserIdAndFulfilledFalse(String userId); + + @Modifying + void deleteAllByUser(User user); } diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserAccountDeletionService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserAccountDeletionService.java index 1995843..c006628 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserAccountDeletionService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserAccountDeletionService.java @@ -1,8 +1,10 @@ package nl.andrewlalis.gymboard_api.domains.auth.service; +import nl.andrewlalis.gymboard_api.domains.submission.dao.SubmissionReportRepository; import nl.andrewlalis.gymboard_api.domains.submission.dao.SubmissionRepository; import nl.andrewlalis.gymboard_api.domains.auth.dao.*; import nl.andrewlalis.gymboard_api.domains.auth.model.User; +import nl.andrewlalis.gymboard_api.domains.submission.dao.SubmissionVoteRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -19,6 +21,9 @@ public class UserAccountDeletionService { private final EmailResetCodeRepository emailResetCodeRepository; private final PasswordResetCodeRepository passwordResetCodeRepository; private final SubmissionRepository submissionRepository; + private final SubmissionReportRepository submissionReportRepository; + private final SubmissionVoteRepository submissionVoteRepository; + private final UserAccountDataRequestRepository accountDataRequestRepository; public UserAccountDeletionService(UserRepository userRepository, UserReportRepository userReportRepository, @@ -26,7 +31,10 @@ public class UserAccountDeletionService { UserActivationCodeRepository userActivationCodeRepository, EmailResetCodeRepository emailResetCodeRepository, PasswordResetCodeRepository passwordResetCodeRepository, - SubmissionRepository submissionRepository) { + SubmissionRepository submissionRepository, + SubmissionReportRepository submissionReportRepository, + SubmissionVoteRepository submissionVoteRepository, + UserAccountDataRequestRepository accountDataRequestRepository) { this.userRepository = userRepository; this.userReportRepository = userReportRepository; this.userFollowingRepository = userFollowingRepository; @@ -34,6 +42,9 @@ public class UserAccountDeletionService { this.emailResetCodeRepository = emailResetCodeRepository; this.passwordResetCodeRepository = passwordResetCodeRepository; this.submissionRepository = submissionRepository; + this.submissionReportRepository = submissionReportRepository; + this.submissionVoteRepository = submissionVoteRepository; + this.accountDataRequestRepository = accountDataRequestRepository; } @Transactional @@ -46,6 +57,9 @@ public class UserAccountDeletionService { userReportRepository.deleteAllByUserOrReportedBy(user, user); userFollowingRepository.deleteAllByFollowedUserOrFollowingUser(user, user); submissionRepository.deleteAllByUser(user); + submissionReportRepository.deleteAllByUser(user); + submissionVoteRepository.deleteAllByUser(user); + accountDataRequestRepository.deleteAllByUser(user); userRepository.deleteById(user.getId()); } } diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionReportRepository.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionReportRepository.java new file mode 100644 index 0000000..c87244e --- /dev/null +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionReportRepository.java @@ -0,0 +1,13 @@ +package nl.andrewlalis.gymboard_api.domains.submission.dao; + +import nl.andrewlalis.gymboard_api.domains.auth.model.User; +import nl.andrewlalis.gymboard_api.domains.submission.model.SubmissionReport; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; + +@Repository +public interface SubmissionReportRepository extends JpaRepository { + @Modifying + void deleteAllByUser(User user); +} diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionVoteRepository.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionVoteRepository.java new file mode 100644 index 0000000..0f7812d --- /dev/null +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dao/SubmissionVoteRepository.java @@ -0,0 +1,13 @@ +package nl.andrewlalis.gymboard_api.domains.submission.dao; + +import nl.andrewlalis.gymboard_api.domains.auth.model.User; +import nl.andrewlalis.gymboard_api.domains.submission.model.SubmissionVote; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; + +@Repository +public interface SubmissionVoteRepository extends JpaRepository { + @Modifying + void deleteAllByUser(User user); +} diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dto/SubmissionPayload.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dto/SubmissionPayload.java index 0f3f681..0267c0d 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dto/SubmissionPayload.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/submission/dto/SubmissionPayload.java @@ -1,10 +1,8 @@ package nl.andrewlalis.gymboard_api.domains.submission.dto; -import java.time.LocalDateTime; - public record SubmissionPayload( String exerciseShortName, - LocalDateTime performedAt, + String performedAt, float weight, String weightUnit, int reps, diff --git a/gymboard-app/src/api/main/gyms.ts b/gymboard-app/src/api/main/gyms.ts index a58844f..7dcc899 100644 --- a/gymboard-app/src/api/main/gyms.ts +++ b/gymboard-app/src/api/main/gyms.ts @@ -1,5 +1,5 @@ import { GeoPoint } from 'src/api/main/models'; -import SubmissionsModule, { ExerciseSubmission, parseSubmission } from 'src/api/main/submission'; +import SubmissionsModule, { Submission, parseSubmission } from 'src/api/main/submission'; import { api } from 'src/api/main/index'; import { GymRoutable } from 'src/router/gym-routing'; @@ -51,7 +51,7 @@ class GymsModule { public async getRecentSubmissions( gym: GymRoutable - ): Promise> { + ): Promise> { const response = await api.get( `/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions` ); diff --git a/gymboard-app/src/api/main/leaderboards.ts b/gymboard-app/src/api/main/leaderboards.ts index a2997bd..1efb89f 100644 --- a/gymboard-app/src/api/main/leaderboards.ts +++ b/gymboard-app/src/api/main/leaderboards.ts @@ -1,4 +1,4 @@ -import { ExerciseSubmission, parseSubmission } from 'src/api/main/submission'; +import { Submission, parseSubmission } from 'src/api/main/submission'; import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing'; import { api } from 'src/api/main/index'; @@ -29,7 +29,7 @@ interface RequestParams { class LeaderboardsModule { public async getLeaderboard( params: LeaderboardParams - ): Promise> { + ): Promise> { const requestParams: RequestParams = {}; if (params.exerciseShortName) { requestParams.exercise = params.exerciseShortName; diff --git a/gymboard-app/src/api/main/submission.ts b/gymboard-app/src/api/main/submission.ts index e22ebd0..6034c87 100644 --- a/gymboard-app/src/api/main/submission.ts +++ b/gymboard-app/src/api/main/submission.ts @@ -14,7 +14,7 @@ export interface ExerciseSubmissionPayload { weight: number; weightUnit: string; reps: number; - videoFileId: string; + taskId: number; } export enum WeightUnit { @@ -29,30 +29,35 @@ export class WeightUnitUtil { } } -export interface ExerciseSubmission { +export interface Submission { id: string; createdAt: DateTime; gym: SimpleGym; - exercise: Exercise; user: User; + + videoFileId: string | null; + thumbnailFileId: string | null; + processing: boolean; + verified: boolean; + + exercise: Exercise; performedAt: DateTime; - videoFileId: string; rawWeight: number; weightUnit: WeightUnit; metricWeight: number; reps: number; } -export function parseSubmission(data: any): ExerciseSubmission { +export function parseSubmission(data: any): Submission { data.createdAt = DateTime.fromISO(data.createdAt); data.performedAt = DateTime.fromISO(data.performedAt); - return data as ExerciseSubmission; + return data as Submission; } class SubmissionsModule { public async getSubmission( submissionId: string - ): Promise { + ): Promise { const response = await api.get(`/submissions/${submissionId}`); return parseSubmission(response.data); } @@ -61,7 +66,7 @@ class SubmissionsModule { gym: GymRoutable, payload: ExerciseSubmissionPayload, authStore: AuthStoreType - ): Promise { + ): Promise { const gymId = getGymCompoundId(gym); const response = await api.post(`/gyms/${gymId}/submissions`, payload, authStore.axiosConfig); return parseSubmission(response.data); diff --git a/gymboard-app/src/api/main/users.ts b/gymboard-app/src/api/main/users.ts index b121a1f..f49fc74 100644 --- a/gymboard-app/src/api/main/users.ts +++ b/gymboard-app/src/api/main/users.ts @@ -1,15 +1,15 @@ import {api} from 'src/api/main'; import {AuthStoreType} from 'stores/auth-store'; -import {ExerciseSubmission, parseSubmission} from 'src/api/main/submission'; +import {Submission, parseSubmission} from 'src/api/main/submission'; import {defaultPaginationOptions, Page, PaginationOptions, toQueryParams} from 'src/api/main/models'; class UsersModule { - public async getRecentSubmissions(userId: string, authStore: AuthStoreType): Promise> { + public async getRecentSubmissions(userId: string, authStore: AuthStoreType): Promise> { const response = await api.get(`/users/${userId}/recent-submissions`, authStore.axiosConfig); return response.data.map(parseSubmission); } - public async getSubmissions(userId: string, authStore: AuthStoreType, paginationOptions: PaginationOptions = defaultPaginationOptions()): Promise> { + public async getSubmissions(userId: string, authStore: AuthStoreType, paginationOptions: PaginationOptions = defaultPaginationOptions()): Promise> { const config = structuredClone(authStore.axiosConfig); config.params = toQueryParams(paginationOptions); const response = await api.get(`/users/${userId}/submissions`, config); diff --git a/gymboard-app/src/components/ExerciseSubmissionListItem.vue b/gymboard-app/src/components/ExerciseSubmissionListItem.vue index 36bcb33..ceb4e6e 100644 --- a/gymboard-app/src/components/ExerciseSubmissionListItem.vue +++ b/gymboard-app/src/components/ExerciseSubmissionListItem.vue @@ -19,11 +19,11 @@