Added working gym api.

This commit is contained in:
Andrew Lalis 2023-01-21 14:55:10 +01:00
parent fa359e63c1
commit 3d5fe43526
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 cityShortName,
String cityName, String cityName,
String createdAt, String createdAt,
String shortName,
String displayName, String displayName,
String websiteUrl, String websiteUrl,
double locationLatitude, GeoPointResponse location,
double locationLongitude,
String streetAddress String streetAddress
) { ) {
public GymResponse(Gym gym) { public GymResponse(Gym gym) {
@ -23,10 +23,10 @@ public record GymResponse (
gym.getCity().getShortName(), gym.getCity().getShortName(),
gym.getCity().getName(), gym.getCity().getName(),
gym.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), gym.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
gym.getShortName(),
gym.getDisplayName(), gym.getDisplayName(),
gym.getWebsiteUrl(), gym.getWebsiteUrl(),
gym.getLocation().getLatitude().doubleValue(), new GeoPointResponse(gym.getLocation()),
gym.getLocation().getLongitude().doubleValue(),
gym.getStreetAddress() gym.getStreetAddress()
); );
} }

View File

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

View File

@ -11,8 +11,9 @@
const { configure } = require('quasar/wrappers'); const { configure } = require('quasar/wrappers');
const path = require('path'); const path = require('path');
const { withCtx } = require('vue');
module.exports = configure(function (/* ctx */) { module.exports = configure(function (ctx) {
return { return {
eslint: { eslint: {
// fix: true, // fix: true,
@ -69,7 +70,9 @@ module.exports = configure(function (/* ctx */) {
// publicPath: '/', // publicPath: '/',
// analyze: true, // analyze: true,
// env: {}, env: {
API: ctx.dev ? 'http://localhost:8080' : 'https://api.gymboard.com'
},
// rawDefine: {} // rawDefine: {}
// ignorePublicFolder: true, // ignorePublicFolder: true,
// minify: false, // 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 }) => { export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api // for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios; app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) // ^ ^ ^ 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 // 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> <template>
<q-page> <q-page v-if="gym">
Hello Gym Page <h3>{{ gym.displayName }}</h3>
<p> <p>Recent top lifts go here.</p>
Leaderboard total <q-btn
</p> 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> </q-page>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, onMounted, ref } from 'vue'; import { onMounted, ref, Ref } from 'vue';
const mountCount = ref(0); import { getGym, Gym } from 'src/api/gymboard-api';
import { useRoute } from 'vue-router';
onMounted(() => { const route = useRoute();
console.log("mounted");
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> </script>

View File

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