Fixed the exercise submission front-end again.
This commit is contained in:
parent
ae8595db07
commit
2881dc5376
|
@ -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)
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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`
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }} {{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
|
{{ submission.rawWeight }} {{ 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
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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([]);
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue