Added user submission improvements.

This commit is contained in:
Andrew Lalis 2023-02-16 14:48:01 +01:00
parent 0663296052
commit 6b7b38595e
11 changed files with 164 additions and 52 deletions

View File

@ -0,0 +1,23 @@
package nl.andrewlalis.gymboard_api.domains.api.controller;
import nl.andrewlalis.gymboard_api.domains.api.dto.SubmissionResponse;
import nl.andrewlalis.gymboard_api.domains.api.service.submission.UserSubmissionService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserSubmissionsController {
private final UserSubmissionService submissionService;
public UserSubmissionsController(UserSubmissionService submissionService) {
this.submissionService = submissionService;
}
@GetMapping(path = "/users/{userId}/recent-submissions")
public List<SubmissionResponse> getRecentSubmissions(@PathVariable String userId) {
return submissionService.getRecentSubmissions(userId);
}
}

View File

@ -15,7 +15,8 @@ public record SubmissionResponse(
double rawWeight,
String weightUnit,
double metricWeight,
int reps
int reps,
boolean verified
) {
public SubmissionResponse(Submission submission) {
this(
@ -29,7 +30,8 @@ public record SubmissionResponse(
submission.getRawWeight().doubleValue(),
submission.getWeightUnit().name(),
submission.getMetricWeight().doubleValue(),
submission.getReps()
submission.getReps(),
submission.isVerified()
);
}
}

View File

@ -19,4 +19,9 @@ public enum WeightUnit {
BigDecimal metric = new BigDecimal("0.45359237");
return metric.multiply(pounds);
}
public static BigDecimal toKilograms(BigDecimal weight, WeightUnit unit) {
if (unit == POUNDS) return toKilograms(weight);
return weight;
}
}

View File

@ -53,9 +53,23 @@ public class Submission {
@Column(nullable = false)
private int reps;
@Column(nullable = false)
private boolean verified;
public Submission() {}
public Submission(String id, Gym gym, Exercise exercise, User user, LocalDateTime performedAt, String videoFileId, BigDecimal rawWeight, WeightUnit unit, BigDecimal metricWeight, int reps) {
public Submission(
String id,
Gym gym,
Exercise exercise,
User user,
LocalDateTime performedAt,
String videoFileId,
BigDecimal rawWeight,
WeightUnit unit,
BigDecimal metricWeight,
int reps
) {
this.id = id;
this.gym = gym;
this.exercise = exercise;
@ -66,6 +80,7 @@ public class Submission {
this.weightUnit = unit;
this.metricWeight = metricWeight;
this.reps = reps;
this.verified = false;
}
public String getId() {
@ -111,4 +126,12 @@ public class Submission {
public int getReps() {
return reps;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}

View File

@ -41,14 +41,16 @@ public class GymService {
Gym gym = gymRepository.findByCompoundId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return submissionRepository.findAll((root, query, criteriaBuilder) -> {
query.orderBy(criteriaBuilder.desc(root.get("createdAt")));
query.orderBy(
criteriaBuilder.desc(root.get("performedAt")),
criteriaBuilder.desc(root.get("createdAt"))
);
query.distinct(true);
// TODO: Filter to only verified submissions.
return PredicateBuilder.and(criteriaBuilder)
.with(criteriaBuilder.equal(root.get("gym"), gym))
.with(criteriaBuilder.isTrue(root.get("verified")))
.build();
}, PageRequest.of(0, 10))
}, PageRequest.of(0, 5))
.map(SubmissionResponse::new)
.toList();
}

View File

@ -55,9 +55,10 @@ public class LeaderboardService {
query.distinct(true);
query.orderBy(criteriaBuilder.desc(root.get("metricWeight")));
PredicateBuilder pb = PredicateBuilder.and(criteriaBuilder);
PredicateBuilder pb = PredicateBuilder.and(criteriaBuilder)
.with(criteriaBuilder.isTrue(root.get("verified")));
cutoffTime.ifPresent(time -> pb.with(criteriaBuilder.greaterThan(root.get("createdAt"), time)));
cutoffTime.ifPresent(time -> pb.with(criteriaBuilder.greaterThan(root.get("performedAt"), time)));
optionalExercise.ifPresent(exercise -> pb.with(criteriaBuilder.equal(root.get("exercise"), exercise)));
if (!gyms.isEmpty()) {
PredicateBuilder or = PredicateBuilder.or(criteriaBuilder);

View File

@ -0,0 +1,44 @@
package nl.andrewlalis.gymboard_api.domains.api.service.submission;
import nl.andrewlalis.gymboard_api.domains.api.dao.submission.SubmissionRepository;
import nl.andrewlalis.gymboard_api.domains.api.dto.SubmissionResponse;
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import nl.andrewlalis.gymboard_api.util.PredicateBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
public class UserSubmissionService {
private final UserRepository userRepository;
private final SubmissionRepository submissionRepository;
public UserSubmissionService(UserRepository userRepository, SubmissionRepository submissionRepository) {
this.userRepository = userRepository;
this.submissionRepository = submissionRepository;
}
@Transactional(readOnly = true)
public List<SubmissionResponse> getRecentSubmissions(String userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (user.getPreferences().isAccountPrivate()) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
return submissionRepository.findAll((root, query, criteriaBuilder) -> {
query.orderBy(
criteriaBuilder.desc(root.get("performedAt")),
criteriaBuilder.desc(root.get("createdAt"))
);
PredicateBuilder pb = PredicateBuilder.and(criteriaBuilder)
.with(criteriaBuilder.equal(root.get("user"), user));
return pb.build();
}, PageRequest.of(0, 5)).map(SubmissionResponse::new).toList();
}
}

View File

@ -6,14 +6,16 @@ public record UserResponse(
String id,
boolean activated,
String email,
String name
String name,
boolean accountPrivate
) {
public UserResponse(User user) {
this(
user.getId(),
user.isActivated(),
user.getEmail(),
user.getName()
user.getName(),
user.getPreferences().isAccountPrivate()
);
}
}

View File

@ -63,8 +63,9 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
final LocalDateTime latestSubmission = LocalDateTime.now();
Random random = new Random(1);
List<Submission> submissions = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
generateRandomSubmission(
submissions.add(generateRandomSubmission(
gyms,
users,
exercises,
@ -72,11 +73,12 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
earliestSubmission,
latestSubmission,
random
);
));
}
submissionRepository.saveAll(submissions);
}
private void generateRandomSubmission(
private Submission generateRandomSubmission(
List<Gym> gyms,
List<User> users,
List<Exercise> exercises,
@ -94,18 +96,20 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
}
submissionRepository.save(new Submission(
ulid.nextULID(),
randomChoice(gyms, random),
randomChoice(exercises, random),
randomChoice(users, random),
time,
randomChoice(videoIds, random),
rawWeight,
weightUnit,
metricWeight,
random.nextInt(13)
));
var submission = new Submission(
ulid.nextULID(),
randomChoice(gyms, random),
randomChoice(exercises, random),
randomChoice(users, random),
time,
randomChoice(videoIds, random),
rawWeight,
weightUnit,
metricWeight,
random.nextInt(13) + 1
);
submission.setVerified(true);
return submission;
}
@Override

View File

@ -10,14 +10,13 @@
</q-item-label>
</q-item-section>
<q-item-section side top>
{{ submission.createdAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
{{ submission.performedAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission';
import { getFileUrl } from 'src/api/cdn';
import { DateTime } from 'luxon';
interface Props {

View File

@ -1,27 +1,27 @@
<template>
<q-page>
<StandardCenteredPage v-if="submission">
<h3>
{{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
{{ submission.exercise.displayName }}
</h3>
<p>{{ submission.reps }} reps</p>
<p>by <router-link :to="'/users/' + submission.user.id">{{ submission.user.name }}</router-link></p>
<p>At <router-link :to="getGymRoute(submission.gym)">{{ submission.gym.displayName }}</router-link></p>
<p>
{{ submission.createdAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
</p>
<video
:src="getFileUrl(submission.videoFileId)"
width="600"
loop
controls
autopictureinpicture
preload="metadata"
autoplay
/>
</StandardCenteredPage>
</q-page>
<q-page>
<StandardCenteredPage v-if="submission">
<video
class="submission-video"
:src="getFileUrl(submission.videoFileId)"
loop
controls
autopictureinpicture
preload="metadata"
autoplay
/>
<h3>
{{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
{{ submission.exercise.displayName }}
</h3>
<p>{{ submission.reps }} reps</p>
<p>by <router-link :to="'/users/' + submission.user.id">{{ submission.user.name }}</router-link></p>
<p>At <router-link :to="getGymRoute(submission.gym)">{{ submission.gym.displayName }}</router-link></p>
<p>
{{ submission.performedAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
</p>
</StandardCenteredPage>
</q-page>
</template>
<script setup lang="ts">
@ -49,3 +49,10 @@ onMounted(async () => {
}
});
</script>
<style scoped>
.submission-video {
width: 100%;
max-height: 100%;
margin-top: 20px;
}
</style>