diff --git a/backend/api/sites.mjs b/backend/api/sites.mjs index 964b900dc..e7e4d210a 100644 --- a/backend/api/sites.mjs +++ b/backend/api/sites.mjs @@ -16,12 +16,32 @@ async function routes (app, options) { app.get('/:siteIdorHostname', { schema: { summary: 'Get site info', - tags: ['Sites'] + tags: ['Sites'], + params: { + type: 'object', + properties: { + siteId: { + type: 'string', + description: 'Either a site ID, hostname or "current" to use the request hostname.', + oneOf: [ + { format: 'uuid' }, + { enum: ['current'] }, + { pattern: '^[a-f0-9]+$' } + ] + } + }, + required: ['siteIdorHostname'] + }, } }, async (req, reply) => { - const site = (uuidValidate(req.params.siteId)) - ? await WIKI.models.sites.getSiteById({ id: req.params.siteId }) - : await WIKI.models.sites.getSiteByHostname({ hostname: req.params.siteId }) + let site + if (req.params.siteId === 'current' && req.hostname) { + site = await WIKI.models.sites.getSiteByHostname({ hostname: req.hostname }) + } else if (uuidValidate(req.params.siteId)) { + site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + } else { + site = await WIKI.models.sites.getSiteByHostname({ hostname: req.params.siteId }) + } return site ? { ...site.config, diff --git a/backend/controllers/site.mjs b/backend/controllers/site.mjs new file mode 100644 index 000000000..cbc160ac9 --- /dev/null +++ b/backend/controllers/site.mjs @@ -0,0 +1,55 @@ +import { validate as uuidValidate } from 'uuid' +import { replyWithFile } from '../helpers/common.mjs' +import path from 'node:path' + +/** + * _site Routes + */ +async function routes (app, options) { + const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets') + + app.get('/:siteId/:resource', async (req, reply) => { + let site + if (req.params.siteId === 'current' && req.hostname) { + site = await WIKI.models.sites.getSiteByHostname({ hostname: req.hostname }) + } else if (uuidValidate(req.params.siteId)) { + site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + } else { + site = await WIKI.models.sites.getSiteByHostname({ hostname: req.params.siteId }) + } + if (!site) { + return reply.notFound('Site not found') + } + switch (req.params.resource) { + case 'logo': { + if (site.config.assets.logo) { + // TODO: Fetch from db if not in disk cache + return replyWithFile(reply, path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`)) + } else { + return replyWithFile(reply, path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg')) + } + } + case 'favicon': { + if (site.config.assets.favicon) { + // TODO: Fetch from db if not in disk cache + return replyWithFile(reply, path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`)) + } else { + return replyWithFile(reply, path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg')) + } + } + case 'loginbg': { + if (site.config.assets.loginBg) { + // TODO: Fetch from db if not in disk cache + return replyWithFile(reply, path.join(siteAssetsPath, `loginbg-${site.id}.jpg`)) + } else { + return replyWithFile(reply, path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg')) + } + } + default: { + return reply.badRequest('Invalid Site Resource') + } + } + }) +} + +export default routes diff --git a/backend/helpers/common.mjs b/backend/helpers/common.mjs index 2c45ba501..3c6a78185 100644 --- a/backend/helpers/common.mjs +++ b/backend/helpers/common.mjs @@ -1,5 +1,7 @@ import { isNil, isPlainObject, set, startCase, transform } from 'lodash-es' import crypto from 'node:crypto' +import mime from 'mime' +import fs from 'node:fs' /* eslint-disable promise/param-names */ export function createDeferred () { @@ -110,3 +112,9 @@ export function getDictNameFromLocale (locale) { return WIKI.data.tsDictMappings[loc] ?? 'simple' } } + +export function replyWithFile (reply, filePath) { + const stream = fs.createReadStream(filePath) + reply.header('Content-Type', mime.getType(filePath)) + return reply.send(stream) +} diff --git a/backend/index.mjs b/backend/index.mjs index ac21f72f4..f9e0e9f02 100644 --- a/backend/index.mjs +++ b/backend/index.mjs @@ -393,6 +393,7 @@ async function initHTTPServer () { // }) app.register(import('./api/index.mjs'), { prefix: '/_api' }) + app.register(import('./controllers/site.mjs'), { prefix: '/_site' }) // ---------------------------------------- // Error handling diff --git a/backend/package-lock.json b/backend/package-lock.json index 61fe23ed7..5befe59a4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -29,6 +29,7 @@ "fastify-favicon": "5.0.0", "lodash-es": "4.17.23", "luxon": "3.7.2", + "mime": "4.1.0", "nanoid": "5.1.6", "node-cache": "5.1.2", "pem-jwk": "2.0.0", @@ -1319,6 +1320,18 @@ "mime": "^3" } }, + "node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@fastify/sensible": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@fastify/sensible/-/sensible-6.0.4.tgz", @@ -6045,15 +6058,18 @@ } }, "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], "license": "MIT", "bin": { - "mime": "cli.js" + "mime": "bin/cli.js" }, "engines": { - "node": ">=10.0.0" + "node": ">=16" } }, "node_modules/mime-db": { diff --git a/backend/package.json b/backend/package.json index 614425149..ff82dcbda 100644 --- a/backend/package.json +++ b/backend/package.json @@ -58,6 +58,7 @@ "fastify-favicon": "5.0.0", "lodash-es": "4.17.23", "luxon": "3.7.2", + "mime": "4.1.0", "nanoid": "5.1.6", "node-cache": "5.1.2", "pem-jwk": "2.0.0", diff --git a/frontend/index.html b/frontend/index.html index dcba3bf0b..45537b326 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 925bcdfd0..269b3b683 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -143,9 +143,8 @@ router.beforeEach(async (to, from) => { // -> Locale if (!commonStore.desiredLocale || !siteStore.locales.active.some(l => l.code === commonStore.desiredLocale)) { commonStore.setLocale(siteStore.locales.primary) - } else { - applyLocale(commonStore.desiredLocale) } + applyLocale(commonStore.desiredLocale) // -> User Profile if (userStore.authenticated && !userStore.profileLoaded) { diff --git a/frontend/src/components/AuthLoginPanel.vue b/frontend/src/components/AuthLoginPanel.vue index 9c2e750a8..adcc8bd18 100644 --- a/frontend/src/components/AuthLoginPanel.vue +++ b/frontend/src/components/AuthLoginPanel.vue @@ -983,7 +983,7 @@ async function finishSetupTFA () { // MOUNTED onMounted(async () => { - await fetchStrategies() + // await fetchStrategies() }) diff --git a/frontend/src/components/HeaderNav.vue b/frontend/src/components/HeaderNav.vue index 13d585f5b..22e9951fe 100644 --- a/frontend/src/components/HeaderNav.vue +++ b/frontend/src/components/HeaderNav.vue @@ -17,10 +17,10 @@ q-header.bg-header.text-white.site-header( size='34px' square ) - img(:src='`/_site/logo`') + img(:src='`/_site/current/logo`') img( v-else - :src='`/_site/logo`' + :src='`/_site/current/logo`' style='height: 34px' ) q-toolbar-title.text-h6(v-if='siteStore.logoText') {{siteStore.title}} diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 02fd158f0..88d36bdd7 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -2,12 +2,12 @@ .auth .auth-content .auth-logo - img(:src='`/_site/logo`' :alt='siteStore.title') + img(:src='`/_site/current/logo`' :alt='siteStore.title') h2.auth-site-title(v-if='siteStore.logoText') {{ siteStore.title }} p.text-grey-7 Login to continue auth-login-panel .auth-bg(aria-hidden="true") - img(:src='`/_site/loginbg`' alt='') + img(:src='`/_site/current/loginbg`' alt='')