diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 274cb34dd..b65de4e37 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -64,7 +64,6 @@ const dltConnector = { const community = { COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung', COMMUNITY_URL, - COMMUNITY_REGISTER_URL: COMMUNITY_URL + (process.env.COMMUNITY_REGISTER_PATH ?? '/register'), COMMUNITY_REDEEM_URL: COMMUNITY_URL + (process.env.COMMUNITY_REDEEM_PATH ?? '/redeem/{code}'), COMMUNITY_REDEEM_CONTRIBUTION_URL: COMMUNITY_URL + (process.env.COMMUNITY_REDEEM_CONTRIBUTION_PATH ?? '/redeem/CL-{code}'), diff --git a/config/common.schema.js b/config/common.schema.js new file mode 100644 index 000000000..a3debf455 --- /dev/null +++ b/config/common.schema.js @@ -0,0 +1,70 @@ +const Joi = require('joi') + +module.exports = Joi.object({ + browser_protocol: Joi.string() + .valid('http', 'https') + .description( + 'Protocol for all URLs in the browser, must be either http or https to prevent mixed content issues.', + ) + .default('http'), + + community_url: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .when('browser_protocol', { + is: 'https', + then: Joi.string().uri({ scheme: 'https' }), + otherwise: Joi.string().uri({ scheme: 'http' }), + }) + .description('The base URL of the community') + .default('http://0.0.0.0'), + + external_backend_url: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .when('browser_protocol', { + is: 'https', + then: Joi.string().uri({ scheme: 'https' }), + otherwise: Joi.string().uri({ scheme: 'http' }), + }) + .description( + ` + The external URL of the backend service, + accessible from outside the server (e.g., via Nginx or the server's public URL), + must use the same protocol as browser_protocol. + `, + ) + .default('http://0.0.0.0/graphql'), + + community_name: Joi.string() + .min(3) + .max(100) + .description('The name of the community') + .default('Gradido Entwicklung'), + + community_description: Joi.string() + .min(10) + .max(300) + .description('A short description of the community') + .default('Die lokale Entwicklungsumgebung von Gradido.'), + + community_support_email: Joi.string() + .email() + .description('The support email address for the community will be used in frontend and E-Mails') + .default('support@supportmail.com'), + + community_location: Joi.string() + .pattern(/^[-+]?[0-9]{1,2}(\.[0-9]+)?,\s?[-+]?[0-9]{1,3}(\.[0-9]+)?$/) + .description('Geographical location of the community in "latitude, longitude" format') + .default('49.280377, 9.690151'), + + gdt_active: Joi.boolean() + .description('Flag to indicate if gdt (Gradido Transform) service is used.') + .default(false), + + gms_active: Joi.boolean() + .description('Flag to indicate if the GMS (Geographic Member Search) service is used.') + .default(false), + + humhub_active: Joi.boolean() + .description('Flag to indicate if the HumHub based Community Server is used.') + .default(false), +}) \ No newline at end of file diff --git a/config/index.js b/config/index.js new file mode 100644 index 000000000..b6c63ed03 --- /dev/null +++ b/config/index.js @@ -0,0 +1,78 @@ +const joi = require('joi') +const pkg = require('../package') + +// Function to map and transform environment variables to match the schema +function mapEnvVariablesToSchema(schema) { + const mappedEnvVariables = {} + + // Iterate over all schema keys + for (const [key, value] of Object.entries(schema.describe().keys)) { + const lowerKey = key.toLowerCase() // Convert key to lowercase to match environment variable keys + + // Check if the environment variable exists and map it + const envValue = process.env[lowerKey] + if (envValue) { + // If the value is 'true' or 'false', convert it to a boolean + if (envValue === 'true' || envValue === 'false') { + mappedEnvVariables[lowerKey] = envValue === 'true' + } + // If the value is a number (check if it's a valid number), convert it + else if (!isNaN(envValue)) { + mappedEnvVariables[lowerKey] = Number(envValue) // Convert string to number + } + // For all other cases, keep the value as it is + else { + mappedEnvVariables[lowerKey] = envValue + } + } else { + // If no environment variable exists, use the default from the schema (if defined) + if (value.flags) { + let defaultValue = null + if (typeof value.flags.default === 'function') { + defaultValue = value.flags.default(schema) + } else { + defaultValue = value.flags.default + } + mappedEnvVariables[lowerKey] = defaultValue !== undefined ? defaultValue : null + // console.log({ key, value: mappedEnvVariables[lowerKey] }) + } + } + } + + return mappedEnvVariables +} + +const constants = { + DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 +} + +const version = { + APP_VERSION: pkg.version, + BUILD_COMMIT: process.env.BUILD_COMMIT ?? null, + // self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code + BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7), +} + +const environment = { + NODE_ENV: process.env.NODE_ENV, + DEBUG: process.env.NODE_ENV !== 'production' ?? false, + PRODUCTION: process.env.NODE_ENV === 'production' ?? false, + DEFAULT_PUBLISHER_ID: process.env.DEFAULT_PUBLISHER_ID ?? 2896, +} + +export function validateAndExport(schema) { + const envMappedToSchema = mapEnvVariablesToSchema(schema) + // validate env against schema + joi.attempt(envMappedToSchema, schema) + const finalEnvVariables = {} + for (const [key, value] of Object.entries(envMappedToSchema)) { + const upperKey = key.toUpperCase() // Convert key back to uppercase + finalEnvVariables[upperKey] = value + } + return { + ...constants, + ...version, + ...environment, + ...finalEnvVariables + } +} diff --git a/frontend/package.json b/frontend/package.json index ac205e77a..4cd7c9618 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -87,7 +87,8 @@ "eslint-plugin-promise": "^5.1.1", "eslint-plugin-vitest": "^0.5.4", "eslint-plugin-vue": "8.7.1", - "jsdom": "^25.0.0", + "joi": "^17.13.3", + "jsdom": "^25.0.0", "mock-apollo-client": "^1.2.1", "postcss": "^8.4.8", "postcss-html": "^1.3.0", diff --git a/frontend/src/config/config.schema.js b/frontend/src/config/config.schema.js new file mode 100644 index 000000000..96c245949 --- /dev/null +++ b/frontend/src/config/config.schema.js @@ -0,0 +1,79 @@ +const commonSchema = require('../../../config/common.schema') +const Joi = require('joi') + +module.exports = commonSchema.keys({ + // export default Joi.object({ + frontend_vite_host: Joi.alternatives() + .try( + Joi.string().valid('localhost').messages({ 'any.invalid': 'Must be localhost' }), + Joi.string() + .ip({ version: ['ipv4'] }) + .messages({ 'string.ip': 'Must be a valid IPv4 address' }), + Joi.string().domain().messages({ 'string.domain': 'Must be a valid domain' }), + ) + .description( + ` + Host (domain, IPv4, or localhost) for the frontend when running Vite as a standalone Node.js instance; + internally, nginx forwards requests to this address. + `, + ) + .default('0.0.0.0'), + + frontend_vite_port: Joi.number() + .integer() + .min(1024) + .max(49151) + .description('Port for hosting Frontend with Vite as a Node.js instance') + .default(3000), + + admin_auth_url: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .when('browser_protocol', { + is: 'https', + then: Joi.string().uri({ scheme: 'https' }), + otherwise: Joi.string().uri({ scheme: 'http' }), + }) + .description('Extern Url for admin-frontend') + .default('http://0.0.0.0/admin/authenticate?token='), + + meta_url: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .description('The base URL for the meta tags.') + .default('http://localhost'), + + meta_title_de: Joi.string() + .description('Meta title in German.') + .default('Gradido – Dein Dankbarkeitskonto'), + + meta_title_en: Joi.string() + .description('Meta title in English.') + .default('Gradido - Your gratitude account'), + + meta_description_de: Joi.string() + .description('Meta description in German.') + .default( + 'Dankbarkeit ist die Währung der neuen Zeit. Immer mehr Menschen entfalten ihr Potenzial und gestalten eine gute Zukunft für alle.', + ), + + meta_description_en: Joi.string() + .description('Meta description in English.') + .default( + 'Gratitude is the currency of the new age. More and more people are unleashing their potential and shaping a good future for all.', + ), + + meta_keywords_de: Joi.string() + .description('Meta keywords in German.') + .default( + 'Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem', + ), + + meta_keywords_en: Joi.string() + .description('Meta keywords in English.') + .default( + 'Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System', + ), + + meta_author: Joi.string() + .description('The author for the meta tags.') + .default('Bernd Hückstädt - Gradido-Akademie'), +}) diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index c20e8e8cf..1049c2e4d 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -1,118 +1,13 @@ +/* eslint-disable camelcase */ // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env). // The whole contents is exposed to the client // Load Package Details for some default values -const pkg = require('../../package') - -const constants = { - DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 - CONFIG_VERSION: { - DEFAULT: 'DEFAULT', - EXPECTED: 'v7.2024-08-06', - CURRENT: '', - }, -} - -const version = { - FRONTEND_MODULE_PROTOCOL: process.env.FRONTEND_MODULE_PROTOCOL ?? 'http', - FRONTEND_MODULE_HOST: process.env.FRONTEND_MODULE_HOST ?? '0.0.0.0', - FRONTEND_MODULE_PORT: process.env.FRONTEND_MODULE_PORT ?? '3000', - APP_VERSION: pkg.version, - BUILD_COMMIT: process.env.BUILD_COMMIT ?? null, - // self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code - BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7), -} - -let FRONTEND_MODULE_URL -// in case of hosting the frontend module with a nodejs-instance -if (process.env.FRONTEND_HOSTING === 'nodejs') { - FRONTEND_MODULE_URL = - version.FRONTEND_MODULE_PROTOCOL + - '://' + - version.FRONTEND_MODULE_HOST + - ':' + - version.FRONTEND_MODULE_PORT -} else { - // in case of hosting the frontend module with a nginx - FRONTEND_MODULE_URL = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST -} - -// const FRONTEND_MODULE_URI = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST // + -// ':' + -// version.FRONTEND_MODULE_PORT - -const features = { - GMS_ACTIVE: process.env.GMS_ACTIVE ?? false, - HUMHUB_ACTIVE: process.env.HUMHUB_ACTIVE ?? false, -} - -const environment = { - NODE_ENV: process.env.NODE_ENV, - DEBUG: process.env.NODE_ENV !== 'production' ?? false, - PRODUCTION: process.env.NODE_ENV === 'production' ?? false, - DEFAULT_PUBLISHER_ID: process.env.DEFAULT_PUBLISHER_ID ?? 2896, -} - -// const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost' -// const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http' -const COMMUNITY_URL = process.env.COMMUNITY_URL ?? FRONTEND_MODULE_URL - -const endpoints = { - GRAPHQL_URI: process.env.GRAPHQL_URI ?? COMMUNITY_URL + (process.env.GRAPHQL_PATH ?? '/graphql'), - ADMIN_AUTH_URL: - process.env.ADMIN_AUTH_URL ?? - COMMUNITY_URL + (process.env.ADMIN_AUTH_PATH ?? '/admin/authenticate?token={token}'), -} - -const community = { - COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung', - COMMUNITY_URL: COMMUNITY_URL, - COMMUNITY_REGISTER_URL: COMMUNITY_URL + (process.env.COMMUNITY_REGISTER_PATH ?? '/register'), - COMMUNITY_DESCRIPTION: - process.env.COMMUNITY_DESCRIPTION ?? 'Die lokale Entwicklungsumgebung von Gradido.', - COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL ?? 'support@supportmail.com', - COMMUNITY_LOCATION: process.env.COMMUNITY_LOCATION ?? '49.280377, 9.690151', -} - -const meta = { - META_URL: process.env.META_URL ?? 'http://localhost', - META_TITLE_DE: process.env.META_TITLE_DE ?? 'Gradido – Dein Dankbarkeitskonto', - META_TITLE_EN: process.env.META_TITLE_EN ?? 'Gradido - Your gratitude account', - META_DESCRIPTION_DE: - process.env.META_DESCRIPTION_DE ?? - 'Dankbarkeit ist die Währung der neuen Zeit. Immer mehr Menschen entfalten ihr Potenzial und gestalten eine gute Zukunft für alle.', - META_DESCRIPTION_EN: - process.env.META_DESCRIPTION_EN ?? - 'Gratitude is the currency of the new age. More and more people are unleashing their potential and shaping a good future for all.', - META_KEYWORDS_DE: - process.env.META_KEYWORDS_DE ?? - 'Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem', - META_KEYWORDS_EN: - process.env.META_KEYWORDS_EN ?? - 'Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System', - META_AUTHOR: process.env.META_AUTHOR ?? 'Bernd Hückstädt - Gradido-Akademie', -} - -// Check config version -constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION ?? constants.CONFIG_VERSION.DEFAULT -if ( - ![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes( - constants.CONFIG_VERSION.CURRENT, - ) -) { - throw new Error( - `Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`, - ) -} +import schema from './config.schema' +import { validateAndExport } from '../../../config' const CONFIG = { - ...constants, - ...version, - ...features, - ...environment, - ...endpoints, - ...community, - ...meta, + ...validateAndExport(schema), } module.exports = CONFIG diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index 25151cf39..95bd858f9 100755 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -292,7 +292,7 @@ const getCommunityStatistics = async () => { } const admin = () => { - window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('{token}', store.state.token)) + window.location.assign(CONFIG.ADMIN_AUTH_URL + store.state.token) store.dispatch('logout') // logout without redirect } diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 0ead04ccd..2516501c9 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -15,6 +15,8 @@ dotenv.config() // load env vars from .env const CONFIG = require('./src/config') +console.log(CONFIG) + // https://vitejs.dev/config/ export default defineConfig({ server: { @@ -76,7 +78,7 @@ export default defineConfig({ URL_PROTOCOL: null, COMMUNITY_URL: null, GRAPHQL_PATH: null, - GRAPHQL_URI: CONFIG.GRAPHQL_URI, // null, + EXTERNAL_BACKEND_URL: CONFIG.EXTERNAL_BACKEND_URL, // null, ADMIN_AUTH_PATH: CONFIG.ADMIN_AUTH_PATH ?? null, // it is the only env without exported default ADMIN_AUTH_URL: CONFIG.ADMIN_AUTH_URL, // null, COMMUNITY_NAME: null, @@ -107,5 +109,8 @@ export default defineConfig({ build: { outDir: path.resolve(__dirname, './build'), chunkSizeWarningLimit: 1600, + rollupOptions: { + external: ['joi'], + }, }, }) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 25e209e52..f2435177f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1073,6 +1073,18 @@ resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -1433,6 +1445,23 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sindresorhus/merge-streams@^2.1.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958" @@ -4638,6 +4667,17 @@ jiti@^2.3.0, jiti@^2.4.0, jiti@^2.4.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== +joi@^17.13.3: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-beautify@^1.14.9: version "1.15.1" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64"