Added submission page and cleaned up time formatting.

This commit is contained in:
Andrew Lalis 2023-02-05 12:48:30 +01:00
parent 7c638d066e
commit 8c2a84755d
13 changed files with 114 additions and 46 deletions

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.gymboard_api.domains.api.dto;
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
import nl.andrewlalis.gymboard_api.util.StandardDateFormatter;
import java.time.format.DateTimeFormatter;
@ -19,7 +20,7 @@ public record ExerciseSubmissionResponse(
public ExerciseSubmissionResponse(ExerciseSubmission submission) {
this(
submission.getId(),
submission.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
StandardDateFormatter.format(submission.getCreatedAt()),
new GymSimpleResponse(submission.getGym()),
new ExerciseResponse(submission.getExercise()),
submission.getVideoFileId(),

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.gymboard_api.domains.api.dto;
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
import nl.andrewlalis.gymboard_api.util.StandardDateFormatter;
import java.time.format.DateTimeFormatter;
@ -22,7 +23,7 @@ public record GymResponse (
gym.getCity().getCountry().getName(),
gym.getCity().getShortName(),
gym.getCity().getName(),
gym.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
StandardDateFormatter.format(gym.getCreatedAt()),
gym.getShortName(),
gym.getDisplayName(),
gym.getWebsiteUrl(),

View File

@ -37,7 +37,7 @@ public class User {
)
private Set<Role> roles;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false)
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false, fetch = FetchType.LAZY)
private UserPersonalDetails personalDetails;
public User() {}
@ -49,6 +49,7 @@ public class User {
this.passwordHash = passwordHash;
this.name = name;
this.roles = new HashSet<>();
this.personalDetails = new UserPersonalDetails(this);
}
public String getId() {
@ -86,4 +87,8 @@ public class User {
public Set<Role> getRoles() {
return roles;
}
public UserPersonalDetails getPersonalDetails() {
return personalDetails;
}
}

View File

@ -0,0 +1,15 @@
package nl.andrewlalis.gymboard_api.util;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* The API-standard formatter for date-time objects that are sent as responses
* where we need to enforce a specific format.
*/
public class StandardDateFormatter {
public static String format(LocalDateTime utcTimestamp) {
return utcTimestamp.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}

View File

@ -11,6 +11,7 @@
"@quasar/cli": "^2.0.0",
"@quasar/extras": "^1.0.0",
"axios": "^0.21.1",
"luxon": "^3.2.1",
"moment": "^2.29.4",
"pinia": "^2.0.11",
"quasar": "^2.6.0",
@ -22,6 +23,7 @@
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@quasar/app-vite": "^1.0.0",
"@types/leaflet": "^1.9.0",
"@types/luxon": "^3.2.0",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
@ -598,6 +600,12 @@
"@types/geojson": "*"
}
},
"node_modules/@types/luxon": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.2.0.tgz",
"integrity": "sha512-lGmaGFoaXHuOLXFvuju2bfvZRqxAqkHPx9Y9IQdQABrinJJshJwfNCKV+u7rR3kJbiqfTF/NhOkcxxAFrObyaA==",
"dev": true
},
"node_modules/@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@ -4237,6 +4245,14 @@
"yallist": "^2.0.0"
}
},
"node_modules/luxon": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz",
"integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@ -6828,6 +6844,12 @@
"@types/geojson": "*"
}
},
"@types/luxon": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.2.0.tgz",
"integrity": "sha512-lGmaGFoaXHuOLXFvuju2bfvZRqxAqkHPx9Y9IQdQABrinJJshJwfNCKV+u7rR3kJbiqfTF/NhOkcxxAFrObyaA==",
"dev": true
},
"@types/mime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@ -9347,6 +9369,11 @@
"yallist": "^2.0.0"
}
},
"luxon": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz",
"integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg=="
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",

View File

@ -14,6 +14,7 @@
"@quasar/cli": "^2.0.0",
"@quasar/extras": "^1.0.0",
"axios": "^0.21.1",
"luxon": "^3.2.1",
"moment": "^2.29.4",
"pinia": "^2.0.11",
"quasar": "^2.6.0",
@ -25,6 +26,7 @@
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@quasar/app-vite": "^1.0.0",
"@types/leaflet": "^1.9.0",
"@types/luxon": "^3.2.0",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",

View File

@ -1,5 +1,5 @@
import { GeoPoint } from 'src/api/main/models';
import SubmissionsModule, { ExerciseSubmission } from 'src/api/main/submission';
import SubmissionsModule, { ExerciseSubmission, parseSubmission } from 'src/api/main/submission';
import { api } from 'src/api/main/index';
import { GymRoutable } from 'src/router/gym-routing';
@ -55,7 +55,8 @@ class GymsModule {
const response = await api.get(
`/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions`
);
return response.data;
const submissionObjects: Array<object> = response.data;
return submissionObjects.map(parseSubmission);
}
}

View File

@ -1,4 +1,4 @@
import { ExerciseSubmission } from 'src/api/main/submission';
import { ExerciseSubmission, parseSubmission } from 'src/api/main/submission';
import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing';
import { api } from 'src/api/main/index';
@ -46,7 +46,7 @@ class LeaderboardsModule {
if (params.size) requestParams.size = params.size;
const response = await api.get('/leaderboards', { params: requestParams });
return response.data.content;
return response.data.content.map(parseSubmission);
}
}

View File

@ -1,8 +1,8 @@
import { SimpleGym } from 'src/api/main/gyms';
import { Exercise } from 'src/api/main/exercises';
import { api, BASE_URL } from 'src/api/main/index';
import { api } from 'src/api/main/index';
import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing';
import { sleep } from 'src/utils';
import { DateTime } from 'luxon';
/**
* The data that's sent when creating a submission.
@ -30,7 +30,7 @@ export class WeightUnitUtil {
export interface ExerciseSubmission {
id: string;
createdAt: string;
createdAt: DateTime;
gym: SimpleGym;
exercise: Exercise;
videoFileId: string;
@ -41,12 +41,18 @@ export interface ExerciseSubmission {
reps: number;
}
export function parseSubmission(data: any): ExerciseSubmission {
data.createdAt = DateTime.fromISO(data.createdAt);
console.log(data);
return data as ExerciseSubmission;
}
class SubmissionsModule {
public async getSubmission(
submissionId: string
): Promise<ExerciseSubmission> {
const response = await api.get(`/submissions/${submissionId}`);
return response.data;
return parseSubmission(response.data);
}
public async createSubmission(
@ -55,7 +61,7 @@ class SubmissionsModule {
): Promise<ExerciseSubmission> {
const gymId = getGymCompoundId(gym);
const response = await api.post(`/gyms/${gymId}/submissions`, payload);
return response.data;
return parseSubmission(response.data);
}
}

View File

@ -1,36 +1,24 @@
<template>
<q-expansion-item
expand-separator
:label="
submission.rawWeight +
' ' +
submission.weightUnit +
' x' +
submission.reps +
' ' +
submission.exercise.displayName
"
:caption="submission.submitterName"
>
<q-card>
<q-card-section class="text-center">
<video
:src="getFileUrl(submission.videoFileId)"
width="600"
loop
controls
autopictureinpicture
preload="metadata"
/>
</q-card-section>
</q-card>
</q-expansion-item>
<q-item clickable :to="'/submissions/' + submission.id">
<q-item-section>
<q-item-label>
{{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
{{ submission.exercise.displayName }}
</q-item-label>
<q-item-label caption>
{{ submission.submitterName }}
</q-item-label>
</q-item-section>
<q-item-section side top>
{{ submission.createdAt.setLocale($i18n.locale).toLocaleString(DateTime.DATETIME_MED) }}
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { ExerciseSubmission } from 'src/api/main/submission';
import api from 'src/api/main';
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission';
import { getFileUrl } from 'src/api/cdn';
import { DateTime } from 'luxon';
interface Props {
submission: ExerciseSubmission;

View File

@ -1,16 +1,38 @@
<template>
<q-page>
<standard-centered-page v-if="submission">
<h3>Submission: {{ submission.id }}</h3>
</standard-centered-page>
<StandardCenteredPage v-if="submission">
<h3>
{{ submission.rawWeight }}&nbsp;{{ WeightUnitUtil.toAbbreviation(submission.weightUnit) }}
{{ submission.exercise.displayName }}
</h3>
<p>{{ submission.reps }} reps</p>
<p>by {{ submission.submitterName }}</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>
</template>
<script setup lang="ts">
import api from 'src/api/main';
import { ExerciseSubmission } from 'src/api/main/submission';
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
import { ExerciseSubmission, WeightUnitUtil } from 'src/api/main/submission';
import { onMounted, ref, Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { DateTime } from 'luxon';
import { getFileUrl } from 'src/api/cdn';
import { getGymRoute } from 'src/router/gym-routing';
const submission: Ref<ExerciseSubmission | undefined> = ref();

View File

@ -29,7 +29,7 @@
<div v-if="recentSubmissions.length > 0">
<h4 class="text-center">{{ $t('gymPage.homePage.recentLifts') }}</h4>
<q-list>
<q-list separator>
<ExerciseSubmissionListItem
v-for="sub in recentSubmissions"
:submission="sub"

View File

@ -17,7 +17,7 @@
emit-value
/>
</div>
<q-list>
<q-list separator>
<ExerciseSubmissionListItem
v-for="sub in submissions"
:submission="sub"