From f9d70a5d572532f9b23391e59d6a4b6c493c977f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Sun, 19 Nov 2023 00:44:01 +0100 Subject: [PATCH] Ne uplne otestovany prepis storu --- quasar.config.js | 1 + src/boot/apollo.js | 96 ++++++ src/boot/axios.js | 75 ++--- src/boot/chartJs.js | 4 +- src/components/FormSubmitButton.vue | 43 +++ src/components/FormWrapper.vue | 30 +- .../aircrafts/AircraftDetailCard.vue | 2 - src/components/banners/AccessDeniedBanner.vue | 2 - .../basicSkeletons/BasicListSkeleton.vue | 7 +- src/components/event/EventsList.vue | 4 +- src/components/flights/FlightBasicInfo.vue | 1 - .../flights/FlightDetailSkeleton.vue | 5 +- src/components/flights/FlightTrackMap.vue | 15 +- .../flights/form/FlightFormNotes.vue | 1 - src/components/poi/POIForm.vue | 2 +- src/components/profile/PilotProfileCard.vue | 8 +- src/composables/objects/aircraft.js | 25 -- src/composables/objects/copilot.js | 23 -- src/composables/objects/event.js | 37 --- src/composables/objects/flight.js | 36 --- src/composables/objects/organization.js | 42 --- src/composables/objects/poi.js | 22 -- src/composables/photos/batchEdit.js | 40 --- src/composables/photos/editor/crop.js | 73 ++--- src/composables/photos/editor/photo.js | 16 - src/composables/photos/simpleEdit.js | 22 -- src/graphql/apollo.js | 109 ------- src/graphql/mutations/flight.js | 7 +- src/graphql/mutations/organization.js | 26 +- src/layouts/ProfileLayout.vue | 12 +- src/layouts/PublicLayout.vue | 16 +- .../profile/aircraft/AircraftDetailPage.vue | 31 +- src/pages/profile/aircraft/AircraftsPage.vue | 12 +- .../profile/aircraft/EditAircraftPage.vue | 10 +- .../profile/aircraft/NewAircraftsPage.vue | 8 +- .../profile/copilot/CopilotDetailPage.vue | 11 +- src/pages/profile/copilot/CopilotsPage.vue | 13 +- src/pages/profile/copilot/EditCopilotPage.vue | 21 +- src/pages/profile/copilot/NewCopilotPage.vue | 9 +- src/pages/profile/event/EditEventPage.vue | 14 +- src/pages/profile/event/EventDetailPage.vue | 13 +- src/pages/profile/event/EventsPage.vue | 12 +- src/pages/profile/event/NewEventPage.vue | 6 +- src/pages/profile/flight/EditFlightPage.vue | 179 +++++++---- src/pages/profile/flight/FlightDetailPage.vue | 103 +++---- src/pages/profile/flight/FlightsPage.vue | 13 +- src/pages/profile/flight/NewFlightPage.vue | 40 ++- src/pages/profile/flight/PhotoEditorPage.vue | 291 +++++++++--------- .../organization/EditOrganizationPage.vue | 14 +- .../organization/NewOrganizationPage.vue | 6 +- .../organization/OrganizationDetailPage.vue | 21 +- .../organization/OrganizationsPage.vue | 16 +- .../profile/poi/EditPointOfInterestPage.vue | 14 +- .../profile/poi/NewPointOfInterestPage.vue | 7 +- .../profile/poi/PointOfInterestDetailPage.vue | 35 ++- .../profile/poi/PointsOfInterestPage.vue | 13 +- src/pages/public/IndexPage.vue | 8 +- src/pages/public/PilotProfilePage.vue | 10 +- .../public/pilotProfile/CopilotDetailPage.vue | 17 +- .../public/pilotProfile/EventDetailPage.vue | 22 +- .../public/pilotProfile/EventsListPage.vue | 12 +- .../public/pilotProfile/FlightDetailPage.vue | 32 +- .../public/pilotProfile/FlightsListPage.vue | 19 +- src/stores/Aircrafts.js | 96 +++--- src/stores/Airports.js | 26 +- src/stores/App.js | 41 +-- src/stores/Copilots.js | 73 +++-- src/stores/Events.js | 74 +++-- src/stores/Flights.js | 157 ++++++---- src/stores/Organizations.js | 156 +++++++--- src/stores/Photo.js | 118 +++---- src/stores/PointOfInterestType.js | 50 ++- src/stores/PointsOfInterest.js | 79 +++-- src/stores/User.js | 164 +++++----- src/stores/helpers.js | 25 ++ src/stores/index.js | 15 +- 76 files changed, 1445 insertions(+), 1463 deletions(-) create mode 100644 src/boot/apollo.js create mode 100644 src/components/FormSubmitButton.vue delete mode 100644 src/composables/objects/aircraft.js delete mode 100644 src/composables/objects/copilot.js delete mode 100644 src/composables/objects/event.js delete mode 100644 src/composables/objects/organization.js delete mode 100644 src/composables/objects/poi.js delete mode 100644 src/composables/photos/batchEdit.js delete mode 100644 src/composables/photos/editor/photo.js delete mode 100644 src/composables/photos/simpleEdit.js delete mode 100644 src/graphql/apollo.js diff --git a/quasar.config.js b/quasar.config.js index b657d33..9f6b414 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -30,6 +30,7 @@ module.exports = configure(function (ctx) { // https://v2.quasar.dev/quasar-cli/boot-files boot: [ "axios", + "apollo", "chartJs", ], diff --git a/src/boot/apollo.js b/src/boot/apollo.js new file mode 100644 index 0000000..add596f --- /dev/null +++ b/src/boot/apollo.js @@ -0,0 +1,96 @@ +// import {boot} from "quasar/wrappers"; +import {ApolloClient, ApolloLink, fromPromise, InMemoryCache} from '@apollo/client/core' +import {createUploadLink} from 'apollo-upload-client'; +import {onError} from "@apollo/client/link/error"; +import {useUserStore} from "stores/User"; +import {useAppStore} from "stores/App"; +import {Notify} from "quasar"; +import {errorObject, warningObject} from "src/composables/dialog"; + +const handleRefreshToken = (store, forward, operation) => { + return fromPromise(useUserStore(store).refreshAccessToken() + .catch((error) => { + Notify.create(warningObject("Tvoje přihlášení vypřešlo. Přihlaš se prosím znovu.")); + window.location = "/login"; + })) + .filter((value) => Boolean(value)) + .flatMap((accessToken) => { + const oldHeaders = operation.getContext().headers; + operation.setContext({ + headers: { + ...oldHeaders, authorization: `Bearer ${accessToken}`, + }, + }); + + // retry the request, returning the new observable + return forward(operation); + }); +} + + + +export const apolloWrapper = {}; +export default (({store}) => { + const uploadLink = createUploadLink({ + uri: process.env.API_URL + '/graphql', + }) + + const authLink = new ApolloLink((operation, forward) => { + const accessToken = useUserStore(store).getAccessToken(); + + if (accessToken) { + operation.setContext({ + headers: {"Authorization": `Bearer ${accessToken}`}, + }); + } + return forward(operation); + }); + + const errorLink = onError(({graphQLErrors, networkError, operation, forward}) => { + if (networkError?.statusCode === 401) { + return handleRefreshToken(store, forward, operation); + } + + if (graphQLErrors) { + for (const error of graphQLErrors) { + if (error.message === "Not found") { + useAppStore(store).appStatus = 404; + } else if (error.message.includes("401") || error.message.includes("Not authorized")) { + return handleRefreshToken(store, forward, operation); + } else { + Notify.create(errorObject("API error: " + error.message)); + } + } + } + }); + + + const apolloClient = new ApolloClient({ + link: ApolloLink.from([authLink, errorLink, uploadLink]), cache: new InMemoryCache(), connectToDevTools: true, + }); + // const apolloProvider = createApolloProvider({ + // defaultClient: apolloClient, + // }); + + + apolloWrapper.query = (query, variables = {}) => { + return apolloClient.query({ + query: query, fetchPolicy: 'no-cache', variables: variables + }) + .then((response) => response) + .catch(err => { + console.error("Error during query: ", err) + return err; + }) + }; + apolloWrapper.mutate = (mutation, variables) => { + return apolloClient + .mutate({ + mutation: mutation, fetchPolicy: 'no-cache', variables: variables, + }) + .then((response) => response) + .catch(err => { + console.error("Error during mutation: ", err); + }) + }; +}); diff --git a/src/boot/axios.js b/src/boot/axios.js index 07ffed3..f486c57 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -3,55 +3,44 @@ import axios from 'axios' import {useUserStore} from "stores/User"; const api = axios.create({ - baseURL: process.env.API_URL, - withCredentials: true, - xsrfHeaderName: "X-CSRF-TOKEN", - headers: { - Accept: "application/json", - "Content-Type": "application/json;charset=UTF-8", - }, + baseURL: process.env.API_URL, + withCredentials: true, + xsrfHeaderName: "X-CSRF-TOKEN", + headers: { + Accept: "application/json", + "Content-Type": "application/json;charset=UTF-8", + }, }) -api.interceptors.request.use((config) => { - const token = useUserStore().getAccessToken(); - if (token) { - config.headers['Authorization'] = `Bearer ${token}` - } +export default boot(({store}) => { + api.interceptors.request.use((config) => { + const token = useUserStore(store).getAccessToken(); + if (token) { + config.headers['Authorization'] = `Bearer ${token}` + } - if (['/refresh', '/login'].includes(config.url)) { - delete config.headers['Authorization']; - } + if (['/refresh', '/login'].includes(config.url)) { + delete config.headers['Authorization']; + } - return config; -}) - -// Response interceptor for API calls -api.interceptors.response.use((response) => { - return response -}, async function (error) { - const originalRequest = error.config; - if (error.response.status === 401 && !['/refresh', '/login'].includes(originalRequest.url)) { - const newToken = await useUserStore().refreshAccessToken().catch(err => { - console.error(err); - return Promise.reject("Logged out"); + return config; }) - api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken; - return api(originalRequest); - } - return Promise.reject(error); -}); - -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 + // Response interceptor for API calls + api.interceptors.response.use( + (response) => response, + async function (error) { + const originalRequest = error.config; + if (error.response.status === 401 && !['/refresh', '/login'].includes(originalRequest.url)) { + const newToken = await useUserStore(store).refreshAccessToken().catch(err => { + console.error(err); + return Promise.reject("Logged out"); + }) + api.defaults.headers.common['Authorization'] = 'Bearer ' + newToken; + return api(originalRequest); + } + return Promise.reject(error); + }); }) export {axios, api} diff --git a/src/boot/chartJs.js b/src/boot/chartJs.js index 41daa61..84a3431 100644 --- a/src/boot/chartJs.js +++ b/src/boot/chartJs.js @@ -2,5 +2,5 @@ import {boot} from "quasar/wrappers"; import {BarElement, CategoryScale, Chart as ChartJS, Filler, Legend, LinearScale, LineElement, PointElement, Title, Tooltip} from "chart.js"; export default boot(({app}) => { - ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, Filler, BarElement, CategoryScale, LinearScale) -}) + ChartJS.register(Title, Tooltip, Legend, PointElement, LineElement, Filler, BarElement, CategoryScale, LinearScale); +}); diff --git a/src/components/FormSubmitButton.vue b/src/components/FormSubmitButton.vue new file mode 100644 index 0000000..22ad3aa --- /dev/null +++ b/src/components/FormSubmitButton.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/FormWrapper.vue b/src/components/FormWrapper.vue index f2c34d8..ce8d8f4 100644 --- a/src/components/FormWrapper.vue +++ b/src/components/FormWrapper.vue @@ -2,6 +2,7 @@ import {isRequestPending} from "src/composables/form/props"; import {computed, useSlots} from "vue"; import {useQuasar} from "quasar"; +import FormSubmitButton from "components/FormSubmitButton.vue"; const props = defineProps({ modelValue: { @@ -23,38 +24,11 @@ const emits = defineEmits(["save"]); const saveForm = () => { emits("save", props.modelValue); } - -const $q = useQuasar(); -const fullwidthBtn = computed(() => $q.screen.width <= $q.screen.sizes.md); - - - diff --git a/src/components/aircrafts/AircraftDetailCard.vue b/src/components/aircrafts/AircraftDetailCard.vue index dd0b9bd..02e0d1f 100644 --- a/src/components/aircrafts/AircraftDetailCard.vue +++ b/src/components/aircrafts/AircraftDetailCard.vue @@ -1,5 +1,4 @@