Added working gym api.

This commit is contained in:
Andrew Lalis 2023-01-21 14:55:10 +01:00
parent 4f7f17f6b0
commit 181aabaac8
9 changed files with 127 additions and 30 deletions

View File

@ -0,0 +1,26 @@
package nl.andrewlalis.gymboard_api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
public class WebConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// Don't do this in production, use a proper list of allowed origins
config.addAllowedOriginPattern("*");
config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH"));
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -0,0 +1,15 @@
package nl.andrewlalis.gymboard_api.controller.dto;
import nl.andrewlalis.gymboard_api.model.GeoPoint;
public record GeoPointResponse(
double latitude,
double longitude
) {
public GeoPointResponse(GeoPoint p) {
this(
p.getLatitude().doubleValue(),
p.getLongitude().doubleValue()
);
}
}

View File

@ -10,10 +10,10 @@ public record GymResponse (
String cityShortName,
String cityName,
String createdAt,
String shortName,
String displayName,
String websiteUrl,
double locationLatitude,
double locationLongitude,
GeoPointResponse location,
String streetAddress
) {
public GymResponse(Gym gym) {
@ -23,10 +23,10 @@ public record GymResponse (
gym.getCity().getShortName(),
gym.getCity().getName(),
gym.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
gym.getShortName(),
gym.getDisplayName(),
gym.getWebsiteUrl(),
gym.getLocation().getLatitude().doubleValue(),
gym.getLocation().getLongitude().doubleValue(),
new GeoPointResponse(gym.getLocation()),
gym.getStreetAddress()
);
}

View File

@ -1 +1,3 @@
spring.jpa.open-in-view=false
spring.servlet.multipart.enabled=false

View File

@ -11,8 +11,9 @@
const { configure } = require('quasar/wrappers');
const path = require('path');
const { withCtx } = require('vue');
module.exports = configure(function (/* ctx */) {
module.exports = configure(function (ctx) {
return {
eslint: {
// fix: true,
@ -69,7 +70,9 @@ module.exports = configure(function (/* ctx */) {
// publicPath: '/',
// analyze: true,
// env: {},
env: {
API: ctx.dev ? 'http://localhost:8080' : 'https://api.gymboard.com'
},
// rawDefine: {}
// ignorePublicFolder: true,
// minify: false,

View File

@ -0,0 +1,40 @@
import axios from "axios";
import process from "process";
const api = axios.create({
baseURL: 'http://localhost:8080'
});
export type Gym = {
countryCode: string,
countryName: string,
cityShortName: string,
cityName: string,
createdAt: Date,
shortName: string,
displayName: string,
websiteUrl: string | null,
location: {
latitude: number,
longitude: number
},
streetAddress: string
};
export async function getGym(countryCode: string, cityShortName: string, gymShortName: string): Promise<Gym> {
const response = await api.get(`/gyms/${countryCode}/${cityShortName}/${gymShortName}`);
const d = response.data;
const gym: Gym = {
countryCode: d.countryCode,
countryName: d.countryName,
cityShortName: d.cityShortName,
cityName: d.cityName,
createdAt: new Date(d.createdAt),
shortName: d.shortName,
displayName: d.displayName,
websiteUrl: d.websiteUrl,
location: d.location,
streetAddress: d.streetAddress
};
return gym;
}

View File

@ -7,24 +7,10 @@ declare module '@vue/runtime-core' {
}
}
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' });
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { api };

View File

@ -1,17 +1,42 @@
<template>
<q-page>
Hello Gym Page
<p>
Leaderboard total
</p>
<q-page v-if="gym">
<h3>{{ gym.displayName }}</h3>
<p>Recent top lifts go here.</p>
<q-btn
color="primary"
label="Submit Your Lift"
:to="route.fullPath + '/submit'"
/>
<p>All the rest of the gym leaderboards should show up here.</p>
</q-page>
<q-page v-if="notFound">
<h3>Gym not found! Oh no!!!</h3>
<router-link to="/">Back</router-link>
</q-page>
</template>
<script setup lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
const mountCount = ref(0);
import { onMounted, ref, Ref } from 'vue';
import { getGym, Gym } from 'src/api/gymboard-api';
import { useRoute } from 'vue-router';
onMounted(() => {
console.log("mounted");
const route = useRoute();
const gym: Ref<Gym | undefined> = ref<Gym>();
const notFound: Ref<boolean | undefined> = ref<boolean>();
// Once the component is mounted, load the gym that we're at.
onMounted(async () => {
try {
gym.value = await getGym(
route.params.countryCode as string,
route.params.cityShortName as string,
route.params.gymShortName as string
);
notFound.value = false;
} catch (error) {
console.error(error);
notFound.value = true;
}
});
</script>

View File

@ -13,7 +13,7 @@ const routes: RouteRecordRaw[] = [
component: IndexPage
},
{
path: 'g/:countryId/:cityId/:gymName',
path: 'g/:countryCode/:cityShortName/:gymShortName',
component: GymPage
}
],