Added improved submission model.

This commit is contained in:
Andrew Lalis 2023-02-07 14:50:24 +01:00
parent 184491b9ea
commit 080c44c467
14 changed files with 211 additions and 45 deletions

View File

@ -6,6 +6,8 @@ import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
import nl.andrewlalis.gymboard_api.domains.api.dto.GymResponse; import nl.andrewlalis.gymboard_api.domains.api.dto.GymResponse;
import nl.andrewlalis.gymboard_api.domains.api.service.GymService; import nl.andrewlalis.gymboard_api.domains.api.service.GymService;
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService; import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@ -37,8 +39,9 @@ public class GymController {
@PostMapping(path = "/submissions") @PostMapping(path = "/submissions")
public ExerciseSubmissionResponse createSubmission( public ExerciseSubmissionResponse createSubmission(
@PathVariable String compoundId, @PathVariable String compoundId,
@AuthenticationPrincipal User user,
@RequestBody ExerciseSubmissionPayload payload @RequestBody ExerciseSubmissionPayload payload
) { ) {
return submissionService.createSubmission(CompoundGymId.parse(compoundId), payload); return submissionService.createSubmission(CompoundGymId.parse(compoundId), user.getId(), payload);
} }
} }

View File

@ -1,7 +1,6 @@
package nl.andrewlalis.gymboard_api.domains.api.dto; package nl.andrewlalis.gymboard_api.domains.api.dto;
public record ExerciseSubmissionPayload( public record ExerciseSubmissionPayload(
String name,
String exerciseShortName, String exerciseShortName,
float weight, float weight,
String weightUnit, String weightUnit,

View File

@ -1,17 +1,17 @@
package nl.andrewlalis.gymboard_api.domains.api.dto; package nl.andrewlalis.gymboard_api.domains.api.dto;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission; import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserResponse;
import nl.andrewlalis.gymboard_api.util.StandardDateFormatter; import nl.andrewlalis.gymboard_api.util.StandardDateFormatter;
import java.time.format.DateTimeFormatter;
public record ExerciseSubmissionResponse( public record ExerciseSubmissionResponse(
String id, String id,
String createdAt, String createdAt,
GymSimpleResponse gym, GymSimpleResponse gym,
ExerciseResponse exercise, ExerciseResponse exercise,
UserResponse user,
String performedAt,
String videoFileId, String videoFileId,
String submitterName,
double rawWeight, double rawWeight,
String weightUnit, String weightUnit,
double metricWeight, double metricWeight,
@ -23,8 +23,9 @@ public record ExerciseSubmissionResponse(
StandardDateFormatter.format(submission.getCreatedAt()), StandardDateFormatter.format(submission.getCreatedAt()),
new GymSimpleResponse(submission.getGym()), new GymSimpleResponse(submission.getGym()),
new ExerciseResponse(submission.getExercise()), new ExerciseResponse(submission.getExercise()),
new UserResponse(submission.getUser()),
StandardDateFormatter.format(submission.getPerformedAt()),
submission.getVideoFileId(), submission.getVideoFileId(),
submission.getSubmitterName(),
submission.getRawWeight().doubleValue(), submission.getRawWeight().doubleValue(),
submission.getWeightUnit().name(), submission.getWeightUnit().name(),
submission.getMetricWeight().doubleValue(), submission.getMetricWeight().doubleValue(),

View File

@ -3,9 +3,11 @@ package nl.andrewlalis.gymboard_api.domains.api.model.exercise;
import jakarta.persistence.*; import jakarta.persistence.*;
import nl.andrewlalis.gymboard_api.domains.api.model.Gym; import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit; import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Entity @Entity
@ -24,6 +26,12 @@ public class ExerciseSubmission {
@ManyToOne(optional = false, fetch = FetchType.LAZY) @ManyToOne(optional = false, fetch = FetchType.LAZY)
private Exercise exercise; private Exercise exercise;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
private User user;
@Column(nullable = false)
private LocalDateTime performedAt;
/** /**
* The id of the video file that was submitted for this submission. It lives * The id of the video file that was submitted for this submission. It lives
* on the <em>gymboard-cdn</em> service as a stored file, which can be * on the <em>gymboard-cdn</em> service as a stored file, which can be
@ -32,9 +40,6 @@ public class ExerciseSubmission {
@Column(nullable = false, updatable = false, length = 26) @Column(nullable = false, updatable = false, length = 26)
private String videoFileId; private String videoFileId;
@Column(nullable = false, updatable = false, length = 63)
private String submitterName;
@Column(nullable = false, precision = 7, scale = 2) @Column(nullable = false, precision = 7, scale = 2)
private BigDecimal rawWeight; private BigDecimal rawWeight;
@ -50,12 +55,13 @@ public class ExerciseSubmission {
public ExerciseSubmission() {} public ExerciseSubmission() {}
public ExerciseSubmission(String id, Gym gym, Exercise exercise, String videoFileId, String submitterName, BigDecimal rawWeight, WeightUnit unit, BigDecimal metricWeight, int reps) { public ExerciseSubmission(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;
this.videoFileId = videoFileId; this.videoFileId = videoFileId;
this.submitterName = submitterName; this.user = user;
this.performedAt = performedAt;
this.rawWeight = rawWeight; this.rawWeight = rawWeight;
this.weightUnit = unit; this.weightUnit = unit;
this.metricWeight = metricWeight; this.metricWeight = metricWeight;
@ -82,8 +88,12 @@ public class ExerciseSubmission {
return videoFileId; return videoFileId;
} }
public String getSubmitterName() { public User getUser() {
return submitterName; return user;
}
public LocalDateTime getPerformedAt() {
return performedAt;
} }
public BigDecimal getRawWeight() { public BigDecimal getRawWeight() {

View File

@ -32,7 +32,13 @@ public class CdnClient {
.GET() .GET()
.build(); .build();
HttpResponse<String> response = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return objectMapper.readValue(response.body(), responseType); return objectMapper.readValue(response.body(), responseType);
} else if (response.statusCode() == 404) {
return null;
} else {
throw new IOException("Request failed with code " + response.statusCode());
}
} }
public <T> T postFile(String urlPath, Path filePath, String contentType, Class<T> responseType) throws IOException, InterruptedException { public <T> T postFile(String urlPath, Path filePath, String contentType, Class<T> responseType) throws IOException, InterruptedException {

View File

@ -6,6 +6,14 @@ public record UploadsClient(CdnClient client) {
public record FileUploadResponse(String id) {} public record FileUploadResponse(String id) {}
public record VideoProcessingTaskStatusResponse(String status) {} public record VideoProcessingTaskStatusResponse(String status) {}
public record FileMetadataResponse(
String filename,
String mimeType,
long size,
String uploadedAt,
boolean availableForDownload
) {}
public FileUploadResponse uploadVideo(Path filePath, String contentType) throws Exception { public FileUploadResponse uploadVideo(Path filePath, String contentType) throws Exception {
return client.postFile("/uploads/video", filePath, contentType, FileUploadResponse.class); return client.postFile("/uploads/video", filePath, contentType, FileUploadResponse.class);
} }
@ -13,4 +21,8 @@ public record UploadsClient(CdnClient client) {
public VideoProcessingTaskStatusResponse getVideoProcessingStatus(String id) throws Exception { public VideoProcessingTaskStatusResponse getVideoProcessingStatus(String id) throws Exception {
return client.get("/uploads/video/" + id + "/status", VideoProcessingTaskStatusResponse.class); return client.get("/uploads/video/" + id + "/status", VideoProcessingTaskStatusResponse.class);
} }
public FileMetadataResponse getFileMetadata(String id) throws Exception {
return client.get("/files/" + id + "/metadata", FileMetadataResponse.class);
}
} }

View File

@ -10,6 +10,8 @@ import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit; import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise; import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission; import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import nl.andrewlalis.gymboard_api.util.ULID; import nl.andrewlalis.gymboard_api.util.ULID;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -19,6 +21,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/** /**
* Service which handles the rather mundane tasks associated with exercise * Service which handles the rather mundane tasks associated with exercise
@ -29,15 +33,17 @@ public class ExerciseSubmissionService {
private static final Logger log = LoggerFactory.getLogger(ExerciseSubmissionService.class); private static final Logger log = LoggerFactory.getLogger(ExerciseSubmissionService.class);
private final GymRepository gymRepository; private final GymRepository gymRepository;
private final UserRepository userRepository;
private final ExerciseRepository exerciseRepository; private final ExerciseRepository exerciseRepository;
private final ExerciseSubmissionRepository exerciseSubmissionRepository; private final ExerciseSubmissionRepository exerciseSubmissionRepository;
private final ULID ulid; private final ULID ulid;
public ExerciseSubmissionService(GymRepository gymRepository, public ExerciseSubmissionService(GymRepository gymRepository,
ExerciseRepository exerciseRepository, UserRepository userRepository, ExerciseRepository exerciseRepository,
ExerciseSubmissionRepository exerciseSubmissionRepository, ExerciseSubmissionRepository exerciseSubmissionRepository,
ULID ulid) { ULID ulid) {
this.gymRepository = gymRepository; this.gymRepository = gymRepository;
this.userRepository = userRepository;
this.exerciseRepository = exerciseRepository; this.exerciseRepository = exerciseRepository;
this.exerciseSubmissionRepository = exerciseSubmissionRepository; this.exerciseSubmissionRepository = exerciseSubmissionRepository;
this.ulid = ulid; this.ulid = ulid;
@ -53,11 +59,14 @@ public class ExerciseSubmissionService {
/** /**
* Handles the creation of a new exercise submission. * Handles the creation of a new exercise submission.
* @param id The gym id. * @param id The gym id.
* @param userId The user's id.
* @param payload The submission data. * @param payload The submission data.
* @return The saved submission. * @return The saved submission.
*/ */
@Transactional @Transactional
public ExerciseSubmissionResponse createSubmission(CompoundGymId id, ExerciseSubmissionPayload payload) { public ExerciseSubmissionResponse createSubmission(CompoundGymId id, String userId, ExerciseSubmissionPayload payload) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN));
Gym gym = gymRepository.findByCompoundId(id) Gym gym = gymRepository.findByCompoundId(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
Exercise exercise = exerciseRepository.findById(payload.exerciseShortName()) Exercise exercise = exerciseRepository.findById(payload.exerciseShortName())
@ -76,8 +85,9 @@ public class ExerciseSubmissionService {
ulid.nextULID(), ulid.nextULID(),
gym, gym,
exercise, exercise,
user,
LocalDateTime.now(),
payload.videoFileId(), payload.videoFileId(),
payload.name(),
rawWeight, rawWeight,
weightUnit, weightUnit,
metricWeight, metricWeight,

View File

@ -1,61 +1,124 @@
package nl.andrewlalis.gymboard_api.util.sample_data; package nl.andrewlalis.gymboard_api.util.sample_data;
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository; import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId; import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseSubmissionRepository;
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload; import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit; import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient; import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService; import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
import nl.andrewlalis.gymboard_api.util.CsvUtil; import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import nl.andrewlalis.gymboard_api.util.ULID;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.time.Duration;
import java.util.Set; import java.time.LocalDateTime;
import java.util.*;
@Component @Component
@Profile("development") @Profile("development")
public class SampleSubmissionGenerator implements SampleDataGenerator { public class SampleSubmissionGenerator implements SampleDataGenerator {
private final GymRepository gymRepository;
private final UserRepository userRepository;
private final ExerciseRepository exerciseRepository; private final ExerciseRepository exerciseRepository;
private final ExerciseSubmissionService submissionService; private final ExerciseSubmissionService submissionService;
private final ExerciseSubmissionRepository submissionRepository;
private final ULID ulid;
@Value("${app.cdn-origin}") @Value("${app.cdn-origin}")
private String cdnOrigin; private String cdnOrigin;
public SampleSubmissionGenerator(ExerciseRepository exerciseRepository, ExerciseSubmissionService submissionService) { public SampleSubmissionGenerator(GymRepository gymRepository, UserRepository userRepository, ExerciseRepository exerciseRepository, ExerciseSubmissionService submissionService, ExerciseSubmissionRepository submissionRepository, ULID ulid) {
this.gymRepository = gymRepository;
this.userRepository = userRepository;
this.exerciseRepository = exerciseRepository; this.exerciseRepository = exerciseRepository;
this.submissionService = submissionService; this.submissionService = submissionService;
this.submissionRepository = submissionRepository;
this.ulid = ulid;
} }
@Override @Override
public void generate() throws Exception { public void generate() throws Exception {
final CdnClient cdnClient = new CdnClient(cdnOrigin); final CdnClient cdnClient = new CdnClient(cdnOrigin);
CsvUtil.load(Path.of("sample_data", "submissions.csv"), r -> {
var exercise = exerciseRepository.findById(r.get("exercise-short-name")).orElseThrow();
BigDecimal weight = new BigDecimal(r.get("raw-weight"));
WeightUnit unit = WeightUnit.parse(r.get("weight-unit"));
int reps = Integer.parseInt(r.get("reps"));
String name = r.get("submitter-name");
CompoundGymId gymId = CompoundGymId.parse(r.get("gym-id"));
String videoFilename = r.get("video-filename");
var video = cdnClient.uploads.uploadVideo(Path.of("sample_data", videoFilename), "video/mp4"); List<String> videoIds = new ArrayList<>();
submissionService.createSubmission(gymId, new ExerciseSubmissionPayload( var video1 = cdnClient.uploads.uploadVideo(Path.of("sample_data", "sample_video_curl.mp4"), "video/mp4");
name, var video2 = cdnClient.uploads.uploadVideo(Path.of("sample_data", "sample_video_ohp.mp4"), "video/mp4");
exercise.getShortName(), videoIds.add(video1.id());
weight.floatValue(), videoIds.add(video2.id());
unit.name(),
reps, List<Gym> gyms = gymRepository.findAll();
video.id() List<User> users = userRepository.findAll();
List<Exercise> exercises = exerciseRepository.findAll();
final int count = 10000;
final LocalDateTime earliestSubmission = LocalDateTime.now().minusYears(3);
final LocalDateTime latestSubmission = LocalDateTime.now();
Random random = new Random(1);
for (int i = 0; i < count; i++) {
generateRandomSubmission(
gyms,
users,
exercises,
videoIds,
earliestSubmission,
latestSubmission,
random
);
}
}
private void generateRandomSubmission(
List<Gym> gyms,
List<User> users,
List<Exercise> exercises,
List<String> videoIds,
LocalDateTime earliestSubmission,
LocalDateTime latestSubmission,
Random random
) {
LocalDateTime time = randomTime(earliestSubmission, latestSubmission, random);
BigDecimal metricWeight = new BigDecimal(random.nextInt(20, 250));
BigDecimal rawWeight = new BigDecimal(metricWeight.toString());
WeightUnit weightUnit = WeightUnit.KILOGRAMS;
if (random.nextDouble() > 0.5) {
weightUnit = WeightUnit.POUNDS;
rawWeight = metricWeight.multiply(new BigDecimal("2.2046226218"));
}
submissionRepository.save(new ExerciseSubmission(
ulid.nextULID(),
randomChoice(gyms, random),
randomChoice(exercises, random),
randomChoice(users, random),
time,
randomChoice(videoIds, random),
rawWeight,
weightUnit,
metricWeight,
random.nextInt(13)
)); ));
});
} }
@Override @Override
public Collection<Class<? extends SampleDataGenerator>> dependencies() { public Collection<Class<? extends SampleDataGenerator>> dependencies() {
return Set.of(SampleExerciseGenerator.class, SampleUserGenerator.class); return Set.of(SampleExerciseGenerator.class, SampleUserGenerator.class, SampleGymGenerator.class);
}
private <T> T randomChoice(List<T> items, Random rand) {
return items.get(rand.nextInt(items.size()));
}
private LocalDateTime randomTime(LocalDateTime start, LocalDateTime end, Random rand) {
Duration dur = Duration.between(start, end);
return start.plusSeconds(rand.nextLong(dur.toSeconds() + 1));
} }
} }

View File

@ -3,6 +3,7 @@ import { Exercise } from 'src/api/main/exercises';
import { api } from 'src/api/main/index'; import { api } from 'src/api/main/index';
import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing'; import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import {User} from "src/api/main/auth";
/** /**
* The data that's sent when creating a submission. * The data that's sent when creating a submission.
@ -33,8 +34,9 @@ export interface ExerciseSubmission {
createdAt: DateTime; createdAt: DateTime;
gym: SimpleGym; gym: SimpleGym;
exercise: Exercise; exercise: Exercise;
user: User;
performedAt: DateTime;
videoFileId: string; videoFileId: string;
submitterName: string;
rawWeight: number; rawWeight: number;
weightUnit: WeightUnit; weightUnit: WeightUnit;
metricWeight: number; metricWeight: number;
@ -43,7 +45,7 @@ export interface ExerciseSubmission {
export function parseSubmission(data: any): ExerciseSubmission { export function parseSubmission(data: any): ExerciseSubmission {
data.createdAt = DateTime.fromISO(data.createdAt); data.createdAt = DateTime.fromISO(data.createdAt);
console.log(data); data.performedAt = DateTime.fromISO(data.performedAt);
return data as ExerciseSubmission; return data as ExerciseSubmission;
} }

View File

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { GymSearchResult } from 'src/api/search/models'; import {GymSearchResult, UserSearchResult} from 'src/api/search/models';
const api = axios.create({ const api = axios.create({
baseURL: 'http://localhost:8081', baseURL: 'http://localhost:8081',
@ -15,3 +15,14 @@ export async function searchGyms(
const response = await api.get('/search/gyms?q=' + query); const response = await api.get('/search/gyms?q=' + query);
return response.data; return response.data;
} }
/**
* Searches for users using the given query, and eventually returns results.
* Note that only users whose accounts are not private will be included in
* search results.
* @param query The query to use.
*/
export async function searchUsers(query: string): Promise<Array<UserSearchResult>> {
const response = await api.get('/search/users?q=' + query);
return response.data;
}

View File

@ -8,6 +8,11 @@
icon="person" icon="person"
> >
<q-list> <q-list>
<q-item clickable v-close-popup :to="'/users/' + authStore.user?.id">
<q-item-section>
<q-item-label>{{ $t('accountMenuItem.myAccount') }}</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="api.auth.logout(authStore)"> <q-item clickable v-close-popup @click="api.auth.logout(authStore)">
<q-item-section> <q-item-section>
<q-item-label>{{ $t('accountMenuItem.logOut') }}</q-item-label> <q-item-label>{{ $t('accountMenuItem.logOut') }}</q-item-label>

View File

@ -41,6 +41,7 @@ export default {
}, },
accountMenuItem: { accountMenuItem: {
logIn: 'Login', logIn: 'Login',
myAccount: 'My Account',
logOut: 'Log out', logOut: 'Log out',
}, },
}; };

View File

@ -0,0 +1,41 @@
<template>
<q-page>
<StandardCenteredPage v-if="user">
<h3>{{ user?.name }}</h3>
<p>{{ user?.email }}</p>
<p v-if="isOwnUser">This is your account!</p>
</StandardCenteredPage>
</q-page>
</template>
<script setup lang="ts">
import StandardCenteredPage from 'components/StandardCenteredPage.vue';
import {onMounted, ref, Ref} from 'vue';
import {User} from 'src/api/main/auth';
import api from 'src/api/main';
import {useRoute} from 'vue-router';
import {useAuthStore} from 'stores/auth-store';
const route = useRoute();
const authStore = useAuthStore();
/**
* The user that this page displays information about.
*/
const user: Ref<User | undefined> = ref();
/**
* Flag that tells whether this user is the currently authenticated user.
*/
const isOwnUser = ref(false);
onMounted(async () => {
const userId = route.params.userId as string;
user.value = await api.auth.fetchUser(userId, authStore);
isOwnUser.value = user.value.id === authStore.user?.id;
});
</script>
<style scoped>
</style>

View File

@ -12,6 +12,7 @@ import RegisterPage from 'pages/auth/RegisterPage.vue';
import RegistrationSuccessPage from 'pages/auth/RegistrationSuccessPage.vue'; import RegistrationSuccessPage from 'pages/auth/RegistrationSuccessPage.vue';
import ActivationPage from 'pages/auth/ActivationPage.vue'; import ActivationPage from 'pages/auth/ActivationPage.vue';
import SubmissionPage from 'pages/SubmissionPage.vue'; import SubmissionPage from 'pages/SubmissionPage.vue';
import UserPage from 'pages/UserPage.vue';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
// Auth-related pages, which live outside the main layout. // Auth-related pages, which live outside the main layout.
@ -27,6 +28,7 @@ const routes: RouteRecordRaw[] = [
children: [ children: [
{ path: '', component: IndexPage }, { path: '', component: IndexPage },
{ path: 'testing', component: TestingPage }, { path: 'testing', component: TestingPage },
{ path: 'users/:userId', component: UserPage },
{ {
path: 'gyms/:countryCode/:cityShortName/:gymShortName', path: 'gyms/:countryCode/:cityShortName/:gymShortName',
component: GymPage, component: GymPage,