Zfunkcneni aplikace, aktualizace zavislosti
This commit is contained in:
@@ -18,6 +18,7 @@ ENV API_URL "https://api.poletani.cz"
|
|||||||
ENV GOOGLE_MAPS_API_KEY "AIzaSyAfTU1VntF9rp2D9TR4CFXIMALF5GLgsio"
|
ENV GOOGLE_MAPS_API_KEY "AIzaSyAfTU1VntF9rp2D9TR4CFXIMALF5GLgsio"
|
||||||
|
|
||||||
RUN quasar build -m ssr
|
RUN quasar build -m ssr
|
||||||
|
RUN cp /app/dist/ssr/server/server-entry.mjs /app/dist/ssr/server/server-entry.js
|
||||||
|
|
||||||
FROM node:22-alpine AS production-stage
|
FROM node:22-alpine AS production-stage
|
||||||
|
|
||||||
|
|||||||
+29
-17
@@ -10,37 +10,49 @@
|
|||||||
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore"
|
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.12.7",
|
"@apollo/client": "^4.0.5",
|
||||||
"@quasar/extras": "^1.16.16",
|
"@quasar/extras": "^1.16.16",
|
||||||
"@sentry/browser": "^8.51.0",
|
"@sentry/browser": "^10.14.0",
|
||||||
"@sentry/integrations": "^7.114.0",
|
"@sentry/integrations": "^7.114.0",
|
||||||
"@sentry/vue": "^8.51.0",
|
"@sentry/vue": "^10.14.0",
|
||||||
"@vue/apollo-composable": "4.2.1",
|
"@vue/apollo-composable": "4.2.2",
|
||||||
"@vue/apollo-option": "^4.2.0",
|
"@vue/apollo-option": "^4.2.0",
|
||||||
"apollo-upload-client": "^18.0.1",
|
"apollo-upload-client": "^19.0.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"chart.js": "^4.4.7",
|
"chart.js": "^4.4.7",
|
||||||
"graphql": "16.10.0",
|
"graphql": "16.11.0",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"photoswipe": "^5.4.4",
|
"photoswipe": "^5.4.4",
|
||||||
"photoswipe-dynamic-caption-plugin": "^1.2.7",
|
"photoswipe-dynamic-caption-plugin": "^1.2.7",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^3.0.3",
|
||||||
"quasar": "2.17.7",
|
"quasar": "2.18.2",
|
||||||
"vue": "3.5.13",
|
"rxjs": "^7.8.2",
|
||||||
|
"vue": "3.5.21",
|
||||||
"vue-chartjs": "^5.3.2",
|
"vue-chartjs": "^5.3.2",
|
||||||
"vue-picture-cropper": "^0.7.0",
|
"vue-picture-cropper": "^0.7.0",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vue3-google-map": "^0.21.1"
|
"vue3-google-map": "^0.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "2.0.8",
|
"@quasar/app-vite": "2.4.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.36.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^10.5.0",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.4.2"
|
"prettier": "^3.6.2"
|
||||||
},
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"last 10 Chrome versions",
|
||||||
|
"last 10 Firefox versions",
|
||||||
|
"last 4 Edge versions",
|
||||||
|
"last 7 Safari versions",
|
||||||
|
"last 8 Android versions",
|
||||||
|
"last 8 ChromeAndroid versions",
|
||||||
|
"last 8 FirefoxAndroid versions",
|
||||||
|
"last 10 iOS versions",
|
||||||
|
"last 5 Opera versions"
|
||||||
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// https://github.com/michael-ciniawsky/postcss-load-config
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
// https://github.com/postcss/autoprefixer
|
|
||||||
require('autoprefixer')({
|
|
||||||
overrideBrowserslist: [
|
|
||||||
'last 4 Chrome versions',
|
|
||||||
'last 4 Firefox versions',
|
|
||||||
'last 4 Edge versions',
|
|
||||||
'last 4 Safari versions',
|
|
||||||
'last 4 Android versions',
|
|
||||||
'last 4 ChromeAndroid versions',
|
|
||||||
'last 4 FirefoxAndroid versions',
|
|
||||||
'last 4 iOS versions'
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// https://github.com/elchininet/postcss-rtlcss
|
|
||||||
// If you want to support RTL css, then
|
|
||||||
// 1. yarn/npm install postcss-rtlcss
|
|
||||||
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
|
||||||
// 3. uncomment the following line:
|
|
||||||
// require('postcss-rtlcss')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ssrMiddleware } from 'quasar/wrappers'
|
import { defineSsrMiddleware } from '#q-app/wrappers'
|
||||||
|
|
||||||
// This middleware should execute as last one
|
// This middleware should execute as last one
|
||||||
// since it captures everything and tries to
|
// since it captures everything and tries to
|
||||||
// render the page with Vue
|
// render the page with Vue
|
||||||
|
|
||||||
export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
export default defineSsrMiddleware(({ app, resolve, render, serve }) => {
|
||||||
// we capture any other Express route and hand it
|
// we capture any other Express route and hand it
|
||||||
// over to Vue and Vue Router to render our page
|
// over to Vue and Vue Router to render our page
|
||||||
app.get(resolve.urlPath('*'), (req, res) => {
|
app.get(resolve.urlPath('*'), (req, res) => {
|
||||||
@@ -28,12 +28,9 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
|||||||
} else if (err.code === 404) {
|
} else if (err.code === 404) {
|
||||||
// hmm, Vue Router could not find the requested route
|
// hmm, Vue Router could not find the requested route
|
||||||
|
|
||||||
console.log("404 v renderu")
|
|
||||||
|
|
||||||
// Should reach here only if no "catch-all" route
|
// Should reach here only if no "catch-all" route
|
||||||
// is defined in /src/routes
|
// is defined in /src/routes
|
||||||
// res.status(404).send('404 | Page Not Found')
|
res.status(404).send('404 | Page Not Found')
|
||||||
res.redirect(resolve.urlPath('404'))
|
|
||||||
} else if (process.env.DEV) {
|
} else if (process.env.DEV) {
|
||||||
// well, we treat any other code as error;
|
// well, we treat any other code as error;
|
||||||
// if we're in dev mode, then we can use Quasar CLI
|
// if we're in dev mode, then we can use Quasar CLI
|
||||||
@@ -42,7 +39,6 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
|||||||
|
|
||||||
// serve.error is available on dev only
|
// serve.error is available on dev only
|
||||||
serve.error({ err, req, res })
|
serve.error({ err, req, res })
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we're in production, so we should have another method
|
// we're in production, so we should have another method
|
||||||
// to display something to the client when we encounter an error
|
// to display something to the client when we encounter an error
|
||||||
@@ -51,7 +47,11 @@ export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
|||||||
|
|
||||||
// Render Error Page on production or
|
// Render Error Page on production or
|
||||||
// create a route (/src/routes) for an error page and redirect to it
|
// create a route (/src/routes) for an error page and redirect to it
|
||||||
res.redirect(resolve.urlPath('500'))
|
res.status(500).send('500 | Internal Server Error')
|
||||||
|
|
||||||
|
if (process.env.DEBUGGING) {
|
||||||
|
console.error(err.stack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+46
-25
@@ -12,21 +12,22 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import compression from 'compression'
|
import compression from 'compression'
|
||||||
import {
|
import {
|
||||||
ssrClose,
|
defineSsrCreate,
|
||||||
ssrCreate,
|
defineSsrInjectDevMiddleware,
|
||||||
ssrListen,
|
defineSsrListen,
|
||||||
ssrRenderPreloadTag,
|
defineSsrClose,
|
||||||
ssrServeStaticContent
|
defineSsrServeStaticContent,
|
||||||
} from 'quasar/wrappers'
|
defineSsrRenderPreloadTag
|
||||||
|
} from '#q-app/wrappers'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create your webserver and return its instance.
|
* Create your webserver and return its instance.
|
||||||
* If needed, prepare your webserver to receive
|
* If needed, prepare your webserver to receive
|
||||||
* connect-like middlewares.
|
* connect-like middlewares.
|
||||||
*
|
*
|
||||||
* Should NOT be async!
|
* Can be async: defineSsrCreate(async ({ ... }) => { ... })
|
||||||
*/
|
*/
|
||||||
export const create = ssrCreate((/* { ... } */) => {
|
export const create = defineSsrCreate((/* { ... } */) => {
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
// attackers can use this header to detect apps running Express
|
// attackers can use this header to detect apps running Express
|
||||||
@@ -42,6 +43,19 @@ export const create = ssrCreate((/* { ... } */) => {
|
|||||||
return app
|
return app
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by Quasar SSR dev server to inject middleware into the webserver.
|
||||||
|
* It uses it to handle Vite dev server, handle public paths, etc.
|
||||||
|
* The given middleware is compatible with `node:http`'s Server, Express, Connect, etc.
|
||||||
|
*
|
||||||
|
* Can be async: defineSsrInjectDevMiddleware(async ({ app }) => { ... })
|
||||||
|
*/
|
||||||
|
export const injectDevMiddleware = defineSsrInjectDevMiddleware(({ app }) => {
|
||||||
|
return (middleware) => {
|
||||||
|
app.use(middleware)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You need to make the server listen to the indicated port
|
* You need to make the server listen to the indicated port
|
||||||
* and return the listening instance or whatever you need to
|
* and return the listening instance or whatever you need to
|
||||||
@@ -52,10 +66,12 @@ export const create = ssrCreate((/* { ... } */) => {
|
|||||||
*
|
*
|
||||||
* For production, you can instead export your
|
* For production, you can instead export your
|
||||||
* handler for serverless use or whatever else fits your needs.
|
* handler for serverless use or whatever else fits your needs.
|
||||||
|
*
|
||||||
|
* Can be async: defineSsrListen(async ({ app, devHttpsApp, port }) => { ... })
|
||||||
*/
|
*/
|
||||||
export const listen = ssrListen(async ({ app, port, isReady }) => {
|
export const listen = defineSsrListen(({ app, devHttpsApp, port }) => {
|
||||||
await isReady()
|
const server = devHttpsApp || app
|
||||||
return app.listen(port, () => {
|
return server.listen(port, () => {
|
||||||
if (process.env.PROD) {
|
if (process.env.PROD) {
|
||||||
console.log('Server listening at port ' + port)
|
console.log('Server listening at port ' + port)
|
||||||
}
|
}
|
||||||
@@ -70,9 +86,9 @@ export const listen = ssrListen(async ({ app, port, isReady }) => {
|
|||||||
* Should you need the result of the "listen()" call above,
|
* Should you need the result of the "listen()" call above,
|
||||||
* you can use the "listenResult" param.
|
* you can use the "listenResult" param.
|
||||||
*
|
*
|
||||||
* Can be async.
|
* Can be async: defineSsrClose(async ({ listenResult }) => { ... })
|
||||||
*/
|
*/
|
||||||
export const close = ssrClose(({ listenResult }) => {
|
export const close = defineSsrClose(({ listenResult }) => {
|
||||||
return listenResult.close()
|
return listenResult.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,14 +97,19 @@ const maxAge = process.env.DEV
|
|||||||
: 1000 * 60 * 60 * 24 * 30
|
: 1000 * 60 * 60 * 24 * 30
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should return middleware that serves the indicated path
|
* Should return a function that will be used to configure the webserver
|
||||||
* with static content.
|
* to serve static content at "urlPath" from "pathToServe" folder/file.
|
||||||
|
*
|
||||||
|
* Notice resolve.urlPath(urlPath) and resolve.public(pathToServe) usages.
|
||||||
|
*
|
||||||
|
* Can be async: defineSsrServeStaticContent(async ({ app, resolve }) => {
|
||||||
|
* Can return an async function: return async ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
|
||||||
*/
|
*/
|
||||||
export const serveStaticContent = ssrServeStaticContent((path, opts) => {
|
export const serveStaticContent = defineSsrServeStaticContent(({ app, resolve }) => {
|
||||||
return express.static(path, {
|
return ({ urlPath = '/', pathToServe = '.', opts = {} }) => {
|
||||||
maxAge,
|
const serveFn = express.static(resolve.public(pathToServe), { maxAge, ...opts })
|
||||||
...opts
|
app.use(resolve.urlPath(urlPath), serveFn)
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const jsRE = /\.js$/
|
const jsRE = /\.js$/
|
||||||
@@ -103,13 +124,13 @@ const pngRE = /\.png$/
|
|||||||
* Should return a String with HTML output
|
* Should return a String with HTML output
|
||||||
* (if any) for preloading indicated file
|
* (if any) for preloading indicated file
|
||||||
*/
|
*/
|
||||||
export const renderPreloadTag = ssrRenderPreloadTag((file) => {
|
export const renderPreloadTag = defineSsrRenderPreloadTag((file/* , { ssrContext } */) => {
|
||||||
if (jsRE.test(file) === true) {
|
if (jsRE.test(file) === true) {
|
||||||
return `<link rel="modulepreload" href="${file}" crossorigin>`
|
return `<link rel="modulepreload" href="${file}" crossorigin>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cssRE.test(file) === true) {
|
if (cssRE.test(file) === true) {
|
||||||
return `<link rel="stylesheet" href="${file}">`
|
return `<link rel="stylesheet" href="${file}" crossorigin>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (woffRE.test(file) === true) {
|
if (woffRE.test(file) === true) {
|
||||||
@@ -121,15 +142,15 @@ export const renderPreloadTag = ssrRenderPreloadTag((file) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gifRE.test(file) === true) {
|
if (gifRE.test(file) === true) {
|
||||||
return `<link rel="preload" href="${file}" as="image" type="image/gif">`
|
return `<link rel="preload" href="${file}" as="image" type="image/gif" crossorigin>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jpgRE.test(file) === true) {
|
if (jpgRE.test(file) === true) {
|
||||||
return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
|
return `<link rel="preload" href="${file}" as="image" type="image/jpeg" crossorigin>`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pngRE.test(file) === true) {
|
if (pngRE.test(file) === true) {
|
||||||
return `<link rel="preload" href="${file}" as="image" type="image/png">`
|
return `<link rel="preload" href="${file}" as="image" type="image/png" crossorigin>`
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
Vendored
-10
@@ -1,10 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
|
||||||
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
|
||||||
import "quasar/dist/types/feature-flag";
|
|
||||||
|
|
||||||
declare module "quasar/dist/types/feature-flag" {
|
|
||||||
interface QuasarFeatureFlags {
|
|
||||||
ssr: true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-14
@@ -1,23 +1,21 @@
|
|||||||
import {ApolloClient, ApolloLink, fromPromise, InMemoryCache} from '@apollo/client/core'
|
import {ApolloClient, ApolloLink, InMemoryCache} from '@apollo/client/core'
|
||||||
import {onError} from "@apollo/client/link/error";
|
import {ErrorLink} from "@apollo/client/link/error";
|
||||||
import {useAppStore} from "stores/App";
|
import {useAppStore} from "stores/App";
|
||||||
import {Notify} from "quasar";
|
import {Notify} from "quasar";
|
||||||
import {errorObject, warningObject} from "src/composables/dialog";
|
import {errorObject, warningObject} from "src/composables/dialog";
|
||||||
import {useAuthStore} from "stores/Auth";
|
import {useAuthStore} from "stores/Auth";
|
||||||
// import {createUploadLink} from "apollo-upload-client";
|
import UploadHttpLink from "apollo-upload-client/UploadHttpLink.mjs";
|
||||||
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";
|
|
||||||
|
|
||||||
const handleRefreshToken = (store, router, forward, operation) => {
|
const handleRefreshToken = (store, router, forward, operation) => {
|
||||||
if (!operation.query?._refreshUnauthorizedUser) return;
|
if (!operation.query?._refreshUnauthorizedUser) return;
|
||||||
|
|
||||||
console.log("refreshing token, ", operation.query);
|
console.log("refreshing token, ", operation.query);
|
||||||
return fromPromise(useAuthStore(store).refreshAccessToken()
|
return useAuthStore(store).refreshAccessToken()
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
Notify.create(warningObject("Tvoje přihlášení vypršelo. Přihlaš se prosím znovu."));
|
Notify.create(warningObject("Tvoje přihlášení vypršelo. Přihlaš se prosím znovu."));
|
||||||
router.push({name: "login"})
|
router.push({name: "login"})
|
||||||
}))
|
})
|
||||||
.filter((value) => Boolean(value))
|
.then(value => value.flatMap((accessToken) => {
|
||||||
.flatMap((accessToken) => {
|
|
||||||
const oldHeaders = operation.getContext().headers;
|
const oldHeaders = operation.getContext().headers;
|
||||||
operation.setContext({
|
operation.setContext({
|
||||||
headers: {
|
headers: {
|
||||||
@@ -28,12 +26,12 @@ const handleRefreshToken = (store, router, forward, operation) => {
|
|||||||
|
|
||||||
// retry the request, returning the new observable
|
// retry the request, returning the new observable
|
||||||
return forward(operation);
|
return forward(operation);
|
||||||
});
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apolloWrapper = {};
|
export const apolloWrapper = {};
|
||||||
export default (({store, router}) => {
|
export default (({store, router}) => {
|
||||||
const uploadLink = createUploadLink({
|
const uploadLink = new UploadHttpLink({
|
||||||
uri: process.env.API_URL + '/graphql',
|
uri: process.env.API_URL + '/graphql',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
})
|
})
|
||||||
@@ -48,7 +46,7 @@ export default (({store, router}) => {
|
|||||||
return forward(operation);
|
return forward(operation);
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorLink = onError(({graphQLErrors, networkError, operation, forward}) => {
|
const errorLink = new ErrorLink(({graphQLErrors, networkError, operation, forward}) => {
|
||||||
if (networkError?.statusCode === 401) {
|
if (networkError?.statusCode === 401) {
|
||||||
return handleRefreshToken(store, router, forward, operation);
|
return handleRefreshToken(store, router, forward, operation);
|
||||||
}
|
}
|
||||||
@@ -64,7 +62,7 @@ export default (({store, router}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
const apolloClient = new ApolloClient({
|
||||||
link: ApolloLink.from([authLink, errorLink, uploadLink]),
|
link: ApolloLink.from([authLink, errorLink, uploadLink]),
|
||||||
@@ -85,8 +83,7 @@ export default (({store, router}) => {
|
|||||||
if (errStr.includes('Not found') || errStr.includes("No row was found")) {
|
if (errStr.includes('Not found') || errStr.includes("No row was found")) {
|
||||||
useAppStore(store).appStatus = 404;
|
useAppStore(store).appStatus = 404;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
} else if (!errStr.includes("401") && !errStr.includes("Not authorized")) {
|
||||||
else if (!errStr.includes("401") && !errStr.includes("Not authorized")) {
|
|
||||||
console.error("Error during query: ", err)
|
console.error("Error during query: ", err)
|
||||||
}
|
}
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {apolloWrapper} from "boot/apollo";
|
import {apolloWrapper} from "boot/apollo";
|
||||||
import {getOperationName} from "@apollo/client/utilities";
|
import {getOperationName} from "@apollo/client/utilities/internal";
|
||||||
|
|
||||||
export const setItems = (data, appendItems, itemsModel, totalItemsCount) => {
|
export const setItems = (data, appendItems, itemsModel, totalItemsCount) => {
|
||||||
const originalData = appendItems ? [...itemsModel.value] : [];
|
const originalData = appendItems ? [...itemsModel.value] : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user