Added user submission improvements.
This commit is contained in:
parent
0663296052
commit
6b7b38595e
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ public record SubmissionResponse(
|
||||||
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(
|
||||||
|
@ -29,7 +30,8 @@ public record SubmissionResponse(
|
||||||
submission.getRawWeight().doubleValue(),
|
submission.getRawWeight().doubleValue(),
|
||||||
submission.getWeightUnit().name(),
|
submission.getWeightUnit().name(),
|
||||||
submission.getMetricWeight().doubleValue(),
|
submission.getMetricWeight().doubleValue(),
|
||||||
submission.getReps()
|
submission.getReps(),
|
||||||
|
submission.isVerified()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,9 @@ public enum WeightUnit {
|
||||||
BigDecimal metric = new BigDecimal("0.45359237");
|
BigDecimal metric = new BigDecimal("0.45359237");
|
||||||
return metric.multiply(pounds);
|
return metric.multiply(pounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static BigDecimal toKilograms(BigDecimal weight, WeightUnit unit) {
|
||||||
|
if (unit == POUNDS) return toKilograms(weight);
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,23 @@ public class Submission {
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private int reps;
|
private int reps;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean verified;
|
||||||
|
|
||||||
public Submission() {}
|
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.id = id;
|
||||||
this.gym = gym;
|
this.gym = gym;
|
||||||
this.exercise = exercise;
|
this.exercise = exercise;
|
||||||
|
@ -66,6 +80,7 @@ public class Submission {
|
||||||
this.weightUnit = unit;
|
this.weightUnit = unit;
|
||||||
this.metricWeight = metricWeight;
|
this.metricWeight = metricWeight;
|
||||||
this.reps = reps;
|
this.reps = reps;
|
||||||
|
this.verified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -111,4 +126,12 @@ public class Submission {
|
||||||
public int getReps() {
|
public int getReps() {
|
||||||
return reps;
|
return reps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVerified() {
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerified(boolean verified) {
|
||||||
|
this.verified = verified;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,16 @@ public class GymService {
|
||||||
Gym gym = gymRepository.findByCompoundId(id)
|
Gym gym = gymRepository.findByCompoundId(id)
|
||||||
.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(criteriaBuilder.desc(root.get("createdAt")));
|
query.orderBy(
|
||||||
|
criteriaBuilder.desc(root.get("performedAt")),
|
||||||
|
criteriaBuilder.desc(root.get("createdAt"))
|
||||||
|
);
|
||||||
query.distinct(true);
|
query.distinct(true);
|
||||||
|
|
||||||
// TODO: Filter to only verified submissions.
|
|
||||||
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")))
|
||||||
.build();
|
.build();
|
||||||
}, PageRequest.of(0, 10))
|
}, PageRequest.of(0, 5))
|
||||||
.map(SubmissionResponse::new)
|
.map(SubmissionResponse::new)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,9 +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")));
|
||||||
|
|
||||||
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)));
|
optionalExercise.ifPresent(exercise -> pb.with(criteriaBuilder.equal(root.get("exercise"), exercise)));
|
||||||
if (!gyms.isEmpty()) {
|
if (!gyms.isEmpty()) {
|
||||||
PredicateBuilder or = PredicateBuilder.or(criteriaBuilder);
|
PredicateBuilder or = PredicateBuilder.or(criteriaBuilder);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,14 +6,16 @@ public record UserResponse(
|
||||||
String id,
|
String id,
|
||||||
boolean activated,
|
boolean activated,
|
||||||
String email,
|
String email,
|
||||||
String name
|
String name,
|
||||||
|
boolean accountPrivate
|
||||||
) {
|
) {
|
||||||
public UserResponse(User user) {
|
public UserResponse(User user) {
|
||||||
this(
|
this(
|
||||||
user.getId(),
|
user.getId(),
|
||||||
user.isActivated(),
|
user.isActivated(),
|
||||||
user.getEmail(),
|
user.getEmail(),
|
||||||
user.getName()
|
user.getName(),
|
||||||
|
user.getPreferences().isAccountPrivate()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,9 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
final LocalDateTime latestSubmission = LocalDateTime.now();
|
final LocalDateTime latestSubmission = LocalDateTime.now();
|
||||||
|
|
||||||
Random random = new Random(1);
|
Random random = new Random(1);
|
||||||
|
List<Submission> submissions = new ArrayList<>(count);
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
generateRandomSubmission(
|
submissions.add(generateRandomSubmission(
|
||||||
gyms,
|
gyms,
|
||||||
users,
|
users,
|
||||||
exercises,
|
exercises,
|
||||||
|
@ -72,11 +73,12 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
earliestSubmission,
|
earliestSubmission,
|
||||||
latestSubmission,
|
latestSubmission,
|
||||||
random
|
random
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
submissionRepository.saveAll(submissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateRandomSubmission(
|
private Submission generateRandomSubmission(
|
||||||
List<Gym> gyms,
|
List<Gym> gyms,
|
||||||
List<User> users,
|
List<User> users,
|
||||||
List<Exercise> exercises,
|
List<Exercise> exercises,
|
||||||
|
@ -94,18 +96,20 @@ public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||||
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
|
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
|
||||||
}
|
}
|
||||||
|
|
||||||
submissionRepository.save(new Submission(
|
var submission = new Submission(
|
||||||
ulid.nextULID(),
|
ulid.nextULID(),
|
||||||
randomChoice(gyms, random),
|
randomChoice(gyms, random),
|
||||||
randomChoice(exercises, random),
|
randomChoice(exercises, random),
|
||||||
randomChoice(users, random),
|
randomChoice(users, random),
|
||||||
time,
|
time,
|
||||||
randomChoice(videoIds, random),
|
randomChoice(videoIds, random),
|
||||||
rawWeight,
|
rawWeight,
|
||||||
weightUnit,
|
weightUnit,
|
||||||
metricWeight,
|
metricWeight,
|
||||||
random.nextInt(13)
|
random.nextInt(13) + 1
|
||||||
));
|
);
|
||||||
|
submission.setVerified(true);
|
||||||
|
return submission;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -10,14 +10,13 @@
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side top>
|
<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-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission';
|
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission';
|
||||||
import { getFileUrl } from 'src/api/cdn';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<q-page>
|
<q-page>
|
||||||
<StandardCenteredPage v-if="submission">
|
<StandardCenteredPage v-if="submission">
|
||||||
<h3>
|
<video
|
||||||
{{ submission.rawWeight }} {{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
|
class="submission-video"
|
||||||
{{ submission.exercise.displayName }}
|
:src="getFileUrl(submission.videoFileId)"
|
||||||
</h3>
|
loop
|
||||||
<p>{{ submission.reps }} reps</p>
|
controls
|
||||||
<p>by <router-link :to="'/users/' + submission.user.id">{{ submission.user.name }}</router-link></p>
|
autopictureinpicture
|
||||||
<p>At <router-link :to="getGymRoute(submission.gym)">{{ submission.gym.displayName }}</router-link></p>
|
preload="metadata"
|
||||||
<p>
|
autoplay
|
||||||
{{ submission.createdAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
|
/>
|
||||||
</p>
|
<h3>
|
||||||
<video
|
{{ submission.rawWeight }} {{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
|
||||||
:src="getFileUrl(submission.videoFileId)"
|
{{ submission.exercise.displayName }}
|
||||||
width="600"
|
</h3>
|
||||||
loop
|
<p>{{ submission.reps }} reps</p>
|
||||||
controls
|
<p>by <router-link :to="'/users/' + submission.user.id">{{ submission.user.name }}</router-link></p>
|
||||||
autopictureinpicture
|
<p>At <router-link :to="getGymRoute(submission.gym)">{{ submission.gym.displayName }}</router-link></p>
|
||||||
preload="metadata"
|
<p>
|
||||||
autoplay
|
{{ submission.performedAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
|
||||||
/>
|
</p>
|
||||||
</StandardCenteredPage>
|
</StandardCenteredPage>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -48,4 +48,11 @@ onMounted(async () => {
|
||||||
await router.push('/');
|
await router.push('/');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.submission-video {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue