Fixed the exercise submission front-end again.

This commit is contained in:
Andrew Lalis 2023-04-07 12:17:19 +02:00
parent ae8595db07
commit 2881dc5376
17 changed files with 129 additions and 52 deletions

View File

@ -42,13 +42,14 @@ public class GymService {
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return submissionRepository.findAll((root, query, criteriaBuilder) -> { return submissionRepository.findAll((root, query, criteriaBuilder) -> {
query.orderBy( query.orderBy(
criteriaBuilder.desc(root.get("performedAt")), criteriaBuilder.desc(root.get("properties").get("performedAt")),
criteriaBuilder.desc(root.get("createdAt")) criteriaBuilder.desc(root.get("createdAt"))
); );
query.distinct(true); query.distinct(true);
return PredicateBuilder.and(criteriaBuilder) return PredicateBuilder.and(criteriaBuilder)
.with(criteriaBuilder.equal(root.get("gym"), gym)) .with(criteriaBuilder.equal(root.get("gym"), gym))
.with(criteriaBuilder.isTrue(root.get("verified"))) .with(criteriaBuilder.isTrue(root.get("verified")))
.with(criteriaBuilder.isFalse(root.get("processing")))
.build(); .build();
}, PageRequest.of(0, 5)) }, PageRequest.of(0, 5))
.map(SubmissionResponse::new) .map(SubmissionResponse::new)

View File

@ -55,8 +55,10 @@ public class LeaderboardService {
query.distinct(true); query.distinct(true);
query.orderBy(criteriaBuilder.desc(root.get("metricWeight"))); query.orderBy(criteriaBuilder.desc(root.get("metricWeight")));
// Basic predicates that should always hold.
PredicateBuilder pb = PredicateBuilder.and(criteriaBuilder) 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))); cutoffTime.ifPresent(time -> pb.with(criteriaBuilder.greaterThan(root.get("performedAt"), time)));
optionalExercise.ifPresent(exercise -> pb.with(criteriaBuilder.equal(root.get("exercise"), exercise))); optionalExercise.ifPresent(exercise -> pb.with(criteriaBuilder.equal(root.get("exercise"), exercise)));

View File

@ -25,7 +25,9 @@ import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -85,8 +87,10 @@ public class ExerciseSubmissionService {
} }
// Create the submission. // Create the submission.
LocalDateTime performedAt = payload.performedAt(); LocalDateTime performedAt = LocalDateTime.now();
if (performedAt == null) performedAt = LocalDateTime.now(); if (payload.performedAt() != null) {
performedAt = LocalDate.parse(payload.performedAt()).atTime(performedAt.toLocalTime());
}
BigDecimal rawWeight = BigDecimal.valueOf(payload.weight()); BigDecimal rawWeight = BigDecimal.valueOf(payload.weight());
WeightUnit weightUnit = WeightUnit.parse(payload.weightUnit()); WeightUnit weightUnit = WeightUnit.parse(payload.weightUnit());
BigDecimal metricWeight = BigDecimal.valueOf(payload.weight()); BigDecimal metricWeight = BigDecimal.valueOf(payload.weight());
@ -109,19 +113,27 @@ public class ExerciseSubmissionService {
log.error("Failed to start video processing task for submission.", e); log.error("Failed to start video processing task for submission.", e);
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to start video processing."); throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to start video processing.");
} }
submission = submissionRepository.save(submission); submission = submissionRepository.saveAndFlush(submission);
return new SubmissionResponse(submission); return new SubmissionResponse(submission);
} }
private ValidationResponse validateSubmissionData(Gym gym, User user, Exercise exercise, SubmissionPayload data) { private ValidationResponse validateSubmissionData(Gym gym, User user, Exercise exercise, SubmissionPayload data) {
ValidationResponse response = new ValidationResponse(); ValidationResponse response = new ValidationResponse();
LocalDateTime now = LocalDateTime.now();
LocalDateTime cutoff = LocalDateTime.now().minusDays(3); LocalDateTime cutoff = LocalDateTime.now().minusDays(3);
if (data.performedAt() != null && data.performedAt().isAfter(LocalDateTime.now())) { 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."); response.addMessage("Cannot submit an exercise from the future.");
} }
if (data.performedAt() != null && data.performedAt().isBefore(cutoff)) { if (performedAt.isBefore(cutoff)) {
response.addMessage("Cannot submit an exercise too far in the past."); 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) { if (data.reps() < 1 || data.reps() > 500) {
response.addMessage("Invalid rep count."); response.addMessage("Invalid rep count.");
} }
@ -179,7 +191,7 @@ public class ExerciseSubmissionService {
var submissionsToUpdate = submissionRepository.findUnprocessedByTaskId(payload.taskId()); var submissionsToUpdate = submissionRepository.findUnprocessedByTaskId(payload.taskId());
log.info("Received video processing complete message from CDN: {}, affecting {} submissions.", payload, submissionsToUpdate.size()); log.info("Received video processing complete message from CDN: {}, affecting {} submissions.", payload, submissionsToUpdate.size());
for (var submission : submissionsToUpdate) { for (var submission : submissionsToUpdate) {
if (payload.status().equalsIgnoreCase("COMPLETE")) { if (payload.status().equalsIgnoreCase("COMPLETED")) {
submission.setVideoFileId(payload.videoFileId()); submission.setVideoFileId(payload.videoFileId());
submission.setThumbnailFileId(payload.thumbnailFileId()); submission.setThumbnailFileId(payload.thumbnailFileId());
submission.setProcessing(false); submission.setProcessing(false);
@ -197,10 +209,10 @@ public class ExerciseSubmissionService {
* stays in the "processing" state for too long. * stays in the "processing" state for too long.
* TODO: Find some way to clean up this mess of logic! * 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() { public void checkProcessingSubmissions() {
var processingSubmissions = submissionRepository.findAllByProcessingTrue(); 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)); LocalDateTime deleteCutoff = LocalDateTime.now().minus(Duration.ofMinutes(30));
for (var submission : processingSubmissions) { for (var submission : processingSubmissions) {
if (submission.getCreatedAt().isBefore(actionCutoff)) { if (submission.getCreatedAt().isBefore(actionCutoff)) {

View File

@ -1,10 +1,15 @@
package nl.andrewlalis.gymboard_api.domains.auth.dao; 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 nl.andrewlalis.gymboard_api.domains.auth.model.UserAccountDataRequest;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface UserAccountDataRequestRepository extends JpaRepository<UserAccountDataRequest, Long> { public interface UserAccountDataRequestRepository extends JpaRepository<UserAccountDataRequest, Long> {
boolean existsByUserIdAndFulfilledFalse(String userId); boolean existsByUserIdAndFulfilledFalse(String userId);
@Modifying
void deleteAllByUser(User user);
} }

View File

@ -1,8 +1,10 @@
package nl.andrewlalis.gymboard_api.domains.auth.service; 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.submission.dao.SubmissionRepository;
import nl.andrewlalis.gymboard_api.domains.auth.dao.*; import nl.andrewlalis.gymboard_api.domains.auth.dao.*;
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.dao.SubmissionVoteRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -19,6 +21,9 @@ public class UserAccountDeletionService {
private final EmailResetCodeRepository emailResetCodeRepository; private final EmailResetCodeRepository emailResetCodeRepository;
private final PasswordResetCodeRepository passwordResetCodeRepository; private final PasswordResetCodeRepository passwordResetCodeRepository;
private final SubmissionRepository submissionRepository; private final SubmissionRepository submissionRepository;
private final SubmissionReportRepository submissionReportRepository;
private final SubmissionVoteRepository submissionVoteRepository;
private final UserAccountDataRequestRepository accountDataRequestRepository;
public UserAccountDeletionService(UserRepository userRepository, public UserAccountDeletionService(UserRepository userRepository,
UserReportRepository userReportRepository, UserReportRepository userReportRepository,
@ -26,7 +31,10 @@ public class UserAccountDeletionService {
UserActivationCodeRepository userActivationCodeRepository, UserActivationCodeRepository userActivationCodeRepository,
EmailResetCodeRepository emailResetCodeRepository, EmailResetCodeRepository emailResetCodeRepository,
PasswordResetCodeRepository passwordResetCodeRepository, PasswordResetCodeRepository passwordResetCodeRepository,
SubmissionRepository submissionRepository) { SubmissionRepository submissionRepository,
SubmissionReportRepository submissionReportRepository,
SubmissionVoteRepository submissionVoteRepository,
UserAccountDataRequestRepository accountDataRequestRepository) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.userReportRepository = userReportRepository; this.userReportRepository = userReportRepository;
this.userFollowingRepository = userFollowingRepository; this.userFollowingRepository = userFollowingRepository;
@ -34,6 +42,9 @@ public class UserAccountDeletionService {
this.emailResetCodeRepository = emailResetCodeRepository; this.emailResetCodeRepository = emailResetCodeRepository;
this.passwordResetCodeRepository = passwordResetCodeRepository; this.passwordResetCodeRepository = passwordResetCodeRepository;
this.submissionRepository = submissionRepository; this.submissionRepository = submissionRepository;
this.submissionReportRepository = submissionReportRepository;
this.submissionVoteRepository = submissionVoteRepository;
this.accountDataRequestRepository = accountDataRequestRepository;
} }
@Transactional @Transactional
@ -46,6 +57,9 @@ public class UserAccountDeletionService {
userReportRepository.deleteAllByUserOrReportedBy(user, user); userReportRepository.deleteAllByUserOrReportedBy(user, user);
userFollowingRepository.deleteAllByFollowedUserOrFollowingUser(user, user); userFollowingRepository.deleteAllByFollowedUserOrFollowingUser(user, user);
submissionRepository.deleteAllByUser(user); submissionRepository.deleteAllByUser(user);
submissionReportRepository.deleteAllByUser(user);
submissionVoteRepository.deleteAllByUser(user);
accountDataRequestRepository.deleteAllByUser(user);
userRepository.deleteById(user.getId()); userRepository.deleteById(user.getId());
} }
} }

View File

@ -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<SubmissionReport, Long> {
@Modifying
void deleteAllByUser(User user);
}

View File

@ -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<SubmissionVote, Long> {
@Modifying
void deleteAllByUser(User user);
}

View File

@ -1,10 +1,8 @@
package nl.andrewlalis.gymboard_api.domains.submission.dto; package nl.andrewlalis.gymboard_api.domains.submission.dto;
import java.time.LocalDateTime;
public record SubmissionPayload( public record SubmissionPayload(
String exerciseShortName, String exerciseShortName,
LocalDateTime performedAt, String performedAt,
float weight, float weight,
String weightUnit, String weightUnit,
int reps, int reps,

View File

@ -1,5 +1,5 @@
import { GeoPoint } from 'src/api/main/models'; 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 { api } from 'src/api/main/index';
import { GymRoutable } from 'src/router/gym-routing'; import { GymRoutable } from 'src/router/gym-routing';
@ -51,7 +51,7 @@ class GymsModule {
public async getRecentSubmissions( public async getRecentSubmissions(
gym: GymRoutable gym: GymRoutable
): Promise<Array<ExerciseSubmission>> { ): Promise<Array<Submission>> {
const response = await api.get( const response = await api.get(
`/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions` `/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions`
); );

View File

@ -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 { getGymCompoundId, GymRoutable } from 'src/router/gym-routing';
import { api } from 'src/api/main/index'; import { api } from 'src/api/main/index';
@ -29,7 +29,7 @@ interface RequestParams {
class LeaderboardsModule { class LeaderboardsModule {
public async getLeaderboard( public async getLeaderboard(
params: LeaderboardParams params: LeaderboardParams
): Promise<Array<ExerciseSubmission>> { ): Promise<Array<Submission>> {
const requestParams: RequestParams = {}; const requestParams: RequestParams = {};
if (params.exerciseShortName) { if (params.exerciseShortName) {
requestParams.exercise = params.exerciseShortName; requestParams.exercise = params.exerciseShortName;

View File

@ -14,7 +14,7 @@ export interface ExerciseSubmissionPayload {
weight: number; weight: number;
weightUnit: string; weightUnit: string;
reps: number; reps: number;
videoFileId: string; taskId: number;
} }
export enum WeightUnit { export enum WeightUnit {
@ -29,30 +29,35 @@ export class WeightUnitUtil {
} }
} }
export interface ExerciseSubmission { export interface Submission {
id: string; id: string;
createdAt: DateTime; createdAt: DateTime;
gym: SimpleGym; gym: SimpleGym;
exercise: Exercise;
user: User; user: User;
videoFileId: string | null;
thumbnailFileId: string | null;
processing: boolean;
verified: boolean;
exercise: Exercise;
performedAt: DateTime; performedAt: DateTime;
videoFileId: string;
rawWeight: number; rawWeight: number;
weightUnit: WeightUnit; weightUnit: WeightUnit;
metricWeight: number; metricWeight: number;
reps: number; reps: number;
} }
export function parseSubmission(data: any): ExerciseSubmission { export function parseSubmission(data: any): Submission {
data.createdAt = DateTime.fromISO(data.createdAt); data.createdAt = DateTime.fromISO(data.createdAt);
data.performedAt = DateTime.fromISO(data.performedAt); data.performedAt = DateTime.fromISO(data.performedAt);
return data as ExerciseSubmission; return data as Submission;
} }
class SubmissionsModule { class SubmissionsModule {
public async getSubmission( public async getSubmission(
submissionId: string submissionId: string
): Promise<ExerciseSubmission> { ): Promise<Submission> {
const response = await api.get(`/submissions/${submissionId}`); const response = await api.get(`/submissions/${submissionId}`);
return parseSubmission(response.data); return parseSubmission(response.data);
} }
@ -61,7 +66,7 @@ class SubmissionsModule {
gym: GymRoutable, gym: GymRoutable,
payload: ExerciseSubmissionPayload, payload: ExerciseSubmissionPayload,
authStore: AuthStoreType authStore: AuthStoreType
): Promise<ExerciseSubmission> { ): Promise<Submission> {
const gymId = getGymCompoundId(gym); const gymId = getGymCompoundId(gym);
const response = await api.post(`/gyms/${gymId}/submissions`, payload, authStore.axiosConfig); const response = await api.post(`/gyms/${gymId}/submissions`, payload, authStore.axiosConfig);
return parseSubmission(response.data); return parseSubmission(response.data);

View File

@ -1,15 +1,15 @@
import {api} from 'src/api/main'; import {api} from 'src/api/main';
import {AuthStoreType} from 'stores/auth-store'; 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'; import {defaultPaginationOptions, Page, PaginationOptions, toQueryParams} from 'src/api/main/models';
class UsersModule { class UsersModule {
public async getRecentSubmissions(userId: string, authStore: AuthStoreType): Promise<Array<ExerciseSubmission>> { public async getRecentSubmissions(userId: string, authStore: AuthStoreType): Promise<Array<Submission>> {
const response = await api.get(`/users/${userId}/recent-submissions`, authStore.axiosConfig); const response = await api.get(`/users/${userId}/recent-submissions`, authStore.axiosConfig);
return response.data.map(parseSubmission); return response.data.map(parseSubmission);
} }
public async getSubmissions(userId: string, authStore: AuthStoreType, paginationOptions: PaginationOptions = defaultPaginationOptions()): Promise<Page<ExerciseSubmission>> { public async getSubmissions(userId: string, authStore: AuthStoreType, paginationOptions: PaginationOptions = defaultPaginationOptions()): Promise<Page<Submission>> {
const config = structuredClone(authStore.axiosConfig); const config = structuredClone(authStore.axiosConfig);
config.params = toQueryParams(paginationOptions); config.params = toQueryParams(paginationOptions);
const response = await api.get(`/users/${userId}/submissions`, config); const response = await api.get(`/users/${userId}/submissions`, config);

View File

@ -19,11 +19,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission'; import { Submission, WeightUnitUtil } from 'src/api/main/submission';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
interface Props { interface Props {
submission: ExerciseSubmission; submission: Submission;
showName?: boolean; showName?: boolean;
showGym?: boolean; showGym?: boolean;
} }

View File

@ -2,6 +2,7 @@
<q-page> <q-page>
<StandardCenteredPage v-if="submission"> <StandardCenteredPage v-if="submission">
<video <video
v-if="!submission.processing"
class="submission-video" class="submission-video"
:src="getFileUrl(submission.videoFileId)" :src="getFileUrl(submission.videoFileId)"
loop loop
@ -10,6 +11,9 @@
preload="metadata" preload="metadata"
autoplay autoplay
/> />
<div v-if="submission.processing">
<p>This submission is still processing.</p>
</div>
<h3> <h3>
{{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }} {{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
{{ submission.exercise.displayName }} {{ submission.exercise.displayName }}
@ -23,7 +27,7 @@
<!-- Deletion button is only visible if the user who submitted it is viewing it. --> <!-- Deletion button is only visible if the user who submitted it is viewing it. -->
<q-btn <q-btn
v-if="authStore.user && authStore.user.id === submission.user.id" v-if="authStore.user && authStore.user.id === submission.user.id && !submission.processing"
label="Delete" label="Delete"
@click="deleteSubmission" @click="deleteSubmission"
/> />
@ -34,18 +38,18 @@
<script setup lang="ts"> <script setup lang="ts">
import api from 'src/api/main'; import api from 'src/api/main';
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue'; import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission'; import { Submission, WeightUnitUtil } from 'src/api/main/submission';
import { onMounted, ref, Ref } from 'vue'; import { onMounted, ref, Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { getFileUrl } from 'src/api/cdn'; import { getFileUrl } from 'src/api/cdn';
import { getGymRoute } from 'src/router/gym-routing'; import { getGymRoute } from 'src/router/gym-routing';
import {useAuthStore} from 'stores/auth-store'; import {useAuthStore} from 'stores/auth-store';
import {confirm, showApiErrorToast} from 'src/utils'; import {confirm, showApiErrorToast, showInfoToast} from 'src/utils';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import {useQuasar} from 'quasar'; import {useQuasar} from 'quasar';
const submission: Ref<ExerciseSubmission | undefined> = ref(); const submission: Ref<Submission | undefined> = ref();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -54,14 +58,24 @@ const i18n = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
onMounted(async () => { onMounted(async () => {
await loadSubmission();
if (submission.value?.processing) {
showInfoToast('This submission is still processing.');
}
});
async function loadSubmission() {
const submissionId = route.params.submissionId as string; const submissionId = route.params.submissionId as string;
try { try {
submission.value = await api.gyms.submissions.getSubmission(submissionId); submission.value = await api.gyms.submissions.getSubmission(submissionId);
if (submission.value.processing) {
setTimeout(loadSubmission, 3000);
}
} catch (error) { } catch (error) {
console.error(error); showApiErrorToast(error);
await router.push('/'); await router.push('/');
} }
}); }
/** /**
* Shows a confirmation dialog asking the user if they really want to delete * Shows a confirmation dialog asking the user if they really want to delete

View File

@ -43,7 +43,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, ref, Ref } from 'vue'; import { nextTick, onMounted, ref, Ref } from 'vue';
import { ExerciseSubmission } from 'src/api/main/submission'; import { Submission } from 'src/api/main/submission';
import api from 'src/api/main'; import api from 'src/api/main';
import { getGymFromRoute } from 'src/router/gym-routing'; import { getGymFromRoute } from 'src/router/gym-routing';
import ExerciseSubmissionListItem from 'components/ExerciseSubmissionListItem.vue'; import ExerciseSubmissionListItem from 'components/ExerciseSubmissionListItem.vue';
@ -51,7 +51,7 @@ import { Gym } from 'src/api/main/gyms';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import { Map, Marker, TileLayer } from 'leaflet'; import { Map, Marker, TileLayer } from 'leaflet';
const recentSubmissions: Ref<Array<ExerciseSubmission>> = ref([]); const recentSubmissions: Ref<Array<Submission>> = ref([]);
const gym: Ref<Gym | undefined> = ref(); const gym: Ref<Gym | undefined> = ref();
const TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; const TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';

View File

@ -33,13 +33,13 @@ import api from 'src/api/main';
import { Exercise } from 'src/api/main/exercises'; import { Exercise } from 'src/api/main/exercises';
import { Gym } from 'src/api/main/gyms'; import { Gym } from 'src/api/main/gyms';
import { LeaderboardTimeframe } from 'src/api/main/leaderboards'; import { LeaderboardTimeframe } from 'src/api/main/leaderboards';
import { ExerciseSubmission } from 'src/api/main/submission'; import { Submission } from 'src/api/main/submission';
import ExerciseSubmissionListItem from 'src/components/ExerciseSubmissionListItem.vue'; import ExerciseSubmissionListItem from 'src/components/ExerciseSubmissionListItem.vue';
import { getGymFromRoute } from 'src/router/gym-routing'; import { getGymFromRoute } from 'src/router/gym-routing';
import { sleep } from 'src/utils'; import { sleep } from 'src/utils';
import { onMounted, ref, Ref, watch, computed } from 'vue'; import { onMounted, ref, Ref, watch, computed } from 'vue';
const submissions: Ref<Array<ExerciseSubmission>> = ref([]); const submissions: Ref<Array<Submission>> = ref([]);
const gym: Ref<Gym | undefined> = ref(); const gym: Ref<Gym | undefined> = ref();
const exercises: Ref<Array<Exercise>> = ref([]); const exercises: Ref<Array<Exercise>> = ref([]);

View File

@ -21,7 +21,7 @@ import ExerciseSubmissionListItem from 'components/ExerciseSubmissionListItem.vu
import {showApiErrorToast} from 'src/utils'; import {showApiErrorToast} from 'src/utils';
import {PaginationHelpers} from 'src/api/main/models'; import {PaginationHelpers} from 'src/api/main/models';
import InfinitePageLoader from 'src/api/infinite-page-loader'; import InfinitePageLoader from 'src/api/infinite-page-loader';
import {ExerciseSubmission} from 'src/api/main/submission'; import {Submission} from 'src/api/main/submission';
interface Props { interface Props {
userId: string; userId: string;
@ -30,7 +30,7 @@ const props = defineProps<Props>();
const authStore = useAuthStore(); const authStore = useAuthStore();
const submissions: Ref<ExerciseSubmission[]> = ref([]); const submissions: Ref<Submission[]> = ref([]);
const loader = new InfinitePageLoader(submissions, async paginationOptions => { const loader = new InfinitePageLoader(submissions, async paginationOptions => {
try { try {
return await api.users.getSubmissions(props.userId, authStore, paginationOptions); return await api.users.getSubmissions(props.userId, authStore, paginationOptions);
@ -41,7 +41,7 @@ const loader = new InfinitePageLoader(submissions, async paginationOptions => {
onMounted(async () => { onMounted(async () => {
loader.registerWindowScrollListener(); loader.registerWindowScrollListener();
await loader.setPagination(PaginationHelpers.sortedDescBy('performedAt')); await loader.setPagination(PaginationHelpers.sortedDescBy('properties.performedAt'));
}); });
</script> </script>