diff --git a/.env.dist b/.env.dist index 97bc78273..e6fe31621 100644 --- a/.env.dist +++ b/.env.dist @@ -1,7 +1,5 @@ NUXT_PUBLIC_DOMAIN_BASE=http://{locale}.localhost:3000 -NUXT_PUBLIC_BASE_URL=http://localhost:3000 - SECRET=changeMeOnProd! MAILER_TRANSPORT= diff --git a/app.vue b/app.vue index 8d444e252..2c14e8d61 100644 --- a/app.vue +++ b/app.vue @@ -1,15 +1,15 @@ diff --git a/components/Account.vue b/components/Account.vue index c1e69d803..790d93226 100644 --- a/components/Account.vue +++ b/components/Account.vue @@ -347,7 +347,7 @@ import { socialProviders } from '../src/socialProviders.ts'; import { usernameRegex } from '../src/username.ts'; import { useMainStore } from '../store/index.ts'; -import { getUrlsForAllLocales } from '~/src/domain.ts'; +import { getUrlForLocale, getUrlsForAllLocales } from '~/src/domain.ts'; import type { Profile } from '~/src/profile.ts'; export default defineComponent({ @@ -371,11 +371,11 @@ export default defineComponent({ }); const config = useConfig(); - const runtimeConfig = useRuntimeConfig(); + const baseUrl = getUrlForLocale(config.locale); return { config, - universalDomains: getUrlsForAllLocales(config.locale).filter((x) => x !== runtimeConfig.public.baseUrl), + universalDomains: getUrlsForAllLocales(config.locale).filter((url) => url !== baseUrl), dialogue, user, username: ref(user.value.username), diff --git a/components/CalendarEvent.vue b/components/CalendarEvent.vue index bd2686ff0..29c0f513a 100644 --- a/components/CalendarEvent.vue +++ b/components/CalendarEvent.vue @@ -3,6 +3,8 @@ import { EventLevel } from '../src/calendar/helpers.ts'; import type { Event, Day } from '../src/calendar/helpers.ts'; import { newDate } from '../src/helpers.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; + const props = withDefaults(defineProps<{ event: Event; year?: number; @@ -61,7 +63,7 @@ const eventParam = computed((): string | null => { +import { getUrlForLocale } from '~/src/domain.ts'; + +const config = useConfig(); + +const exportStarted = ref(false); +const importSuccessful = ref(false); + +const exportLink = computed(() => { + return `${getUrlForLocale(config.locale)}/api/profile/export`; +}); + +const importLink = computed(() => { + return `${getUrlForLocale(config.locale)}/api/profile/import`; +}); + +const startExport = () => { + exportStarted.value = true; + setTimeout(() => exportStarted.value = false, 5000); +}; + +const importDone = () => { + importSuccessful.value = true; + setTimeout(() => window.location.reload(), 3000); +}; + + - - diff --git a/components/Login.vue b/components/Login.vue index 044c8268d..88d4c9d2b 100644 --- a/components/Login.vue +++ b/components/Login.vue @@ -2,11 +2,13 @@ import { importSPKI, jwtVerify } from 'jose'; import type { FetchOptions } from 'ofetch'; +import { getUrlForLocale } from '~/src/domain.ts'; import { socialProviders } from '~/src/socialProviders.ts'; import type { User } from '~/src/user.ts'; const { $setToken: setToken } = useNuxtApp(); const runtimeConfig = useRuntimeConfig(); +const config = useConfig(); const token = ref(null); const usernameOrEmail = ref(''); @@ -29,11 +31,12 @@ watchEffect(async () => { await setToken(token.value); const importedPublicKey = await importSPKI(runtimeConfig.public.publicKey, 'RS256'); + const baseUrl = getUrlForLocale(config.locale); const result = await jwtVerify(token.value, importedPublicKey, { algorithms: ['RS256'], - audience: runtimeConfig.public.baseUrl, - issuer: runtimeConfig.public.baseUrl, + audience: baseUrl, + issuer: baseUrl, }); payload.value = result.payload; diff --git a/components/pronouns/PronounsGenerator.vue b/components/pronouns/PronounsGenerator.vue index 736593ca2..f6816d9ea 100644 --- a/components/pronouns/PronounsGenerator.vue +++ b/components/pronouns/PronounsGenerator.vue @@ -1,4 +1,5 @@ diff --git a/composables/useMainPronoun.ts b/composables/useMainPronoun.ts index 74c2a6da4..353efbde2 100644 --- a/composables/useMainPronoun.ts +++ b/composables/useMainPronoun.ts @@ -1,4 +1,3 @@ -import { useRuntimeConfig } from 'nuxt/app'; import { computed } from 'vue'; import { buildPronoun, buildPronounUsage } from '../src/buildPronoun.ts'; @@ -9,6 +8,8 @@ import type { Translator } from '../src/translator'; import useConfig from './useConfig.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; + export interface PronounOpinion extends PronounUsage { link: string; opinion: string; @@ -19,7 +20,6 @@ export default ( profile: Ref> | null>, translator: Translator, ) => { - const runtimeConfig = useRuntimeConfig(); const config = useConfig(); const pronouns = pronounLibrary.pronouns; const pronounOpinions = computed((): PronounOpinion[] => { @@ -28,10 +28,11 @@ export default ( } return (profile.value.pronouns ?? []) .map(({ value: pronoun, opinion }) => { + const baseUrl = getUrlForLocale(config.locale); let link = pronoun .trim() - .replace(new RegExp(`^${runtimeConfig.public.baseUrl}`), '') - .replace(new RegExp(`^${runtimeConfig.public.baseUrl.replace(/^https?:\/\//, '')}`), '') + .replace(new RegExp(`^${baseUrl}`), '') + .replace(new RegExp(`^${baseUrl.replace(/^https?:\/\//, '')}`), '') .replace(new RegExp('^/'), ''); try { diff --git a/composables/useSimpleHead.ts b/composables/useSimpleHead.ts index 4e16ac54b..2477dc0d7 100644 --- a/composables/useSimpleHead.ts +++ b/composables/useSimpleHead.ts @@ -1,6 +1,4 @@ import type { UseSeoMetaInput } from '@unhead/vue'; -import { useRuntimeConfig } from 'nuxt/app'; -import type { RuntimeConfig } from 'nuxt/schema'; import { unref } from 'vue'; import type { MaybeRef } from 'vue'; @@ -8,12 +6,14 @@ import { clearLinkedText } from '../src/helpers.ts'; import type { Translator } from '../src/translator'; import { useSeoMeta } from '#imports'; +import type { Config } from '~/locale/config.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; -export const getDefaultSeo = (translator: Translator, runtimeConfig: RuntimeConfig) => ({ +export const getDefaultSeo = (config: Config, translator: Translator) => ({ title: translator.translate('title'), description: translator.translate('description'), keywords: (translator.translate('seo.keywords') || []).join(', '), - banner: `${runtimeConfig.public.baseUrl}/api/banner/zaimki.png`, + banner: `${getUrlForLocale(config.locale)}/api/banner/zaimki.png`, }); export interface HeadParams { @@ -30,8 +30,8 @@ export default ( { title, description, banner, noindex = false, keywords }: HeadParams, translator: Translator, ): void => { - const runtimeConfig = useRuntimeConfig(); - const defaultSeo = getDefaultSeo(translator, runtimeConfig); + const config = useConfig(); + const defaultSeo = getDefaultSeo(config, translator); const seo: UseSeoMetaInput = {}; const seoTitle = () => { @@ -66,6 +66,7 @@ export default ( seo.ogDescription = seoDescription; seo.twitterDescription = seoDescription; + const baseUrl = getUrlForLocale(config.locale); const seoImage = () => { let bannerUnwrapped = unref(banner); if (!bannerUnwrapped) { @@ -73,7 +74,7 @@ export default ( } bannerUnwrapped = bannerUnwrapped.replace(/^\//, ''); if (!bannerUnwrapped.startsWith('https://')) { - bannerUnwrapped = `${runtimeConfig.public.baseUrl}/${bannerUnwrapped}`; + bannerUnwrapped = `${baseUrl}/${bannerUnwrapped}`; } return bannerUnwrapped; }; diff --git a/composables/useSocial.ts b/composables/useSocial.ts index 55bbf1fb2..545a5b7b0 100644 --- a/composables/useSocial.ts +++ b/composables/useSocial.ts @@ -1,11 +1,8 @@ -import { useRuntimeConfig } from 'nuxt/app'; - import { useMainStore } from '../store/index.ts'; import { getUrlForLocale } from '~/src/domain.ts'; export default () => { - const runtimeConfig = useRuntimeConfig(); const store = useMainStore(); const buildSocialLoginConnectLink = ( @@ -16,7 +13,7 @@ export default () => { ): string => { const url = providerOptions.redirectViaHome ? new URL(`/api/user/social-redirect/${provider}/${locale}`, getUrlForLocale('_')) - : new URL(`/api/connect/${provider}`, runtimeConfig.public.baseUrl); + : new URL(`/api/connect/${provider}`, getUrlForLocale(locale)); if (providerOptions.instanceRequired && instance) { url.searchParams.append('instance', instance ?? ''); diff --git a/nuxt.config.ts b/nuxt.config.ts index c0d9fff3b..8b3ff91a6 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -79,7 +79,6 @@ export default defineNuxtConfig({ public: { domainBase: undefined as string | undefined, env: 'home', - baseUrl: 'https://pronouns.page', version, publicKey: '', turnstileSitekey: '', diff --git a/pages/api.vue b/pages/api.vue index b6c7cbe47..692060b05 100644 --- a/pages/api.vue +++ b/pages/api.vue @@ -3,6 +3,7 @@ import { useNuxtApp } from 'nuxt/app'; import useConfig from '~/composables/useConfig.ts'; import useSimpleHead from '~/composables/useSimpleHead.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; interface Group { enabled: boolean | undefined; @@ -120,7 +121,7 @@ const groups = computed((): Group[] => { {{ path }} { .flat())]; }); -const runtimeConfig = useRuntimeConfig(); const icsLink = computed(() => { - return `${runtimeConfig.public.baseUrl}/api/queer-calendar-${config.locale}-@${username.value}.ics`; + return `${getUrlForLocale(config.locale)}/api/queer-calendar-${config.locale}-@${username.value}.ics`; }); onMounted(async () => { diff --git a/pages/profile/editor.vue b/pages/profile/editor.vue index adaf43494..1baeebcdf 100644 --- a/pages/profile/editor.vue +++ b/pages/profile/editor.vue @@ -12,6 +12,7 @@ import type { Config } from '~/locale/config.ts'; import { birthdateRange, formatDate, parseDate } from '~/src/birthdate.ts'; import { buildPronounUsage } from '~/src/buildPronoun.ts'; import { loadCalendar, loadPronounLibrary } from '~/src/data.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; import { buildList, isValidLink } from '~/src/helpers.ts'; import { addPronounInjectionKey } from '~/src/injectionKeys.ts'; import opinions from '~/src/opinions.ts'; @@ -222,7 +223,6 @@ const visibilityIcons = { [ProfileVisibility.Internal]: ['user-shield'], } as Record; -const runtimeConfig = useRuntimeConfig(); const year = (await loadCalendar()).getCurrentYear(); const { mainPronoun } = useMainPronoun(pronounLibrary, formData, translator); @@ -282,12 +282,13 @@ const save = async (): Promise => { }; const normalisePronoun = (pronoun: string): string | null => { + const baseUrl = getUrlForLocale(config.locale); try { return decodeURIComponent(pronoun .toLowerCase() .trim() - .replace(new RegExp(`^${runtimeConfig.public.baseUrl}`), '') - .replace(new RegExp(`^${runtimeConfig.public.baseUrl.replace(/^https?:\/\//, '')}`), '') + .replace(new RegExp(`^${baseUrl}`), '') + .replace(new RegExp(`^${baseUrl.replace(/^https?:\/\//, '')}`), '') .replace(new RegExp('^/'), '')); } catch { return null; diff --git a/server/calendar.ts b/server/calendar.ts index 866c5372f..592f9b434 100644 --- a/server/calendar.ts +++ b/server/calendar.ts @@ -9,13 +9,13 @@ import type { Config } from '../locale/config.ts'; import dbConnection from './db.ts'; -import { loadSuml } from '~/server/loader.ts'; +import localeDescriptions from '~/locale/locales.ts'; +import { loadConfig } from '~/server/data.ts'; import { buildCalendar } from '~/src/calendar/calendar.ts'; +import { getLocaleUrls } from '~/src/domain.ts'; const __dirname = new URL('.', import.meta.url).pathname; -const config = loadSuml('config') as Config; - const force = process.argv[2] === '-f' || process.argv[2] === '--force'; const shoot = async (url: string, filename: string): Promise => { @@ -26,7 +26,7 @@ const shoot = async (url: string, filename: string): Promise => { headless: 'new', }, }); - pr.source(process.env.NUXT_PUBLIC_BASE_URL + url, ['1500x300']); + pr.source(url, ['1500x300']); for (const buffer of await pr.run()) { fs.mkdirSync(path.dirname(filename), { recursive: true }); console.log(filename); @@ -34,7 +34,7 @@ const shoot = async (url: string, filename: string): Promise => { } }; -const dumpNameDays = async (): Promise => { +const dumpNameDays = async (config: Config): Promise => { if (!config.names || !config.names.enabled || !config.names.namedays) { return; } @@ -53,7 +53,7 @@ const dumpNameDays = async (): Promise => { fs.writeFileSync(`${__dirname}/../locale/${config.locale}/names/namedays.json`, JSON.stringify(output)); }; -(async (): Promise => { +const run = async (config: Config, baseUrl: string): Promise => { if (!config.calendar?.enabled) { return; } @@ -69,7 +69,7 @@ const dumpNameDays = async (): Promise => { for (const day of Object.keys(current)) { const year = day.substring(0, 4); if (current[day] !== prev[day] || !fs.existsSync(`${dir}/${day}.png`) || force) { - await shoot(`/${config.calendar.route}/${day}?layout=basic`, `${dir}/${day}.png`); + await shoot(`${baseUrl}/${config.calendar.route}/${day}?layout=basic`, `${dir}/${day}.png`); changedYears.add(year); } if (!fs.existsSync(`${dir}/${year}-overview.png`) || !fs.existsSync(`${dir}/${year}-labels.png`) || force) { @@ -78,11 +78,25 @@ const dumpNameDays = async (): Promise => { } for (const year of changedYears) { - await shoot(`/${config.calendar.route}/${year}?layout=basic`, `${dir}/${year}-overview.png`); - await shoot(`/${config.calendar.route}/${year}?layout=basic&labels=true`, `${dir}/${year}-labels.png`); + await shoot( + `${baseUrl}/${config.calendar.route}/${year}?layout=basic`, + `${dir}/${year}-overview.png`, + ); + await shoot( + `${baseUrl}/${config.calendar.route}/${year}?layout=basic&labels=true`, + `${dir}/${year}-labels.png`, + ); } fs.writeFileSync(prevPath, JSON.stringify(current, null, 4)); - await dumpNameDays(); + await dumpNameDays(config); +}; + +(async () => { + const localeUrls = getLocaleUrls(process.env.NUXT_PUBLIC_DOMAIN_BASE); + for (const localeDescription of localeDescriptions) { + const config = await loadConfig(localeDescription.code); + await run(config, localeUrls[config.locale]); + } })(); diff --git a/server/cards.ts b/server/cards.ts index 776cefc50..5f0ecb28a 100644 --- a/server/cards.ts +++ b/server/cards.ts @@ -6,28 +6,20 @@ import Pageres from 'pageres'; import type { Screenshot } from 'pageres'; import { ulid } from 'ulid'; -import allLocales from '../locale/locales.ts'; - import { awsConfig, awsParams } from './aws.ts'; import dbConnection from './db.ts'; import type { Database } from './db.ts'; -import { env } from './env.ts'; import isHighLoadTime from './overload.js'; import jwt from '~/server/jwt.ts'; +import { getUrlsForAllLocales } from '~/src/domain.ts'; const s3 = new S3(awsConfig); const urlBases: Record = {}; -if ((env === 'development' || process.env.NUXT_PUBLIC_ENV === 'test') && process.env.NUXT_PUBLIC_BASE_URL) { - for (const { code } of allLocales) { - urlBases[code] = `${process.env.NUXT_PUBLIC_BASE_URL}/card/@`; - } -} else { - for (const { code, url } of allLocales) { - urlBases[code] = `${url}/card/@`; - } +for (const [locale, url] of Object.entries(getUrlsForAllLocales('_', true))) { + urlBases[locale] = `${url}/card/@`; } const sleep = (ms: number): Promise => new Promise((res) => setTimeout(res, ms)); diff --git a/server/data.ts b/server/data.ts index 8e7e5651d..596ff2c36 100644 --- a/server/data.ts +++ b/server/data.ts @@ -10,7 +10,7 @@ import { buildCalendar } from '~/src/calendar/calendar.ts'; import type { Calendar } from '~/src/calendar/helpers.ts'; import { PronounLibrary } from '~/src/classes.ts'; import type { Pronoun, PronounGroup } from '~/src/classes.ts'; -import { getLocaleForUrl } from '~/src/domain.ts'; +import { getLocaleForUrl, getUrlForLocale } from '~/src/domain.ts'; import { Translator } from '~/src/translator.ts'; import { loadTsv } from '~/src/tsv.ts'; @@ -81,8 +81,7 @@ export const loadPronounExamples = async (locale: string): Promise = new Map(); export const loadCalendar = async (locale: string): Promise => { return setDefault(calendarByLocale, locale, async () => { - const runtimeConfig = useRuntimeConfig(); const localEvents = await localeEventImports[locale](); - return buildCalendar(localEvents, runtimeConfig.public.baseUrl); + return buildCalendar(localEvents, getUrlForLocale(locale)); }); }; diff --git a/server/jwt.ts b/server/jwt.ts index 590df66a6..f8332a389 100644 --- a/server/jwt.ts +++ b/server/jwt.ts @@ -7,7 +7,7 @@ import type { KeyLike, JWTPayload } from 'jose'; import { rootDir } from './paths.ts'; -import { getUrlsForAllLocales } from '~/src/domain.ts'; +import { getUrlForLocale, getUrlsForAllLocales } from '~/src/domain.ts'; class Jwt { constructor(private privateKey: KeyLike, private publicKey: KeyLike) {} @@ -26,7 +26,7 @@ class Jwt { .setProtectedHeader({ alg: 'RS256' }) .setExpirationTime(expiresIn) .setAudience(getUrlsForAllLocales(locale)) - .setIssuer(process.env.NUXT_PUBLIC_BASE_URL!) + .setIssuer(getUrlForLocale(locale)) .sign(this.privateKey); } diff --git a/server/routes/blog.atom.get.ts b/server/routes/blog.atom.get.ts index 620289a49..732896bfd 100644 --- a/server/routes/blog.atom.get.ts +++ b/server/routes/blog.atom.get.ts @@ -3,23 +3,24 @@ import marked from 'marked'; import { getPosts } from '~/server/blog.ts'; import { getLocale, loadConfig, loadTranslator } from '~/server/data.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; import parseMarkdown from '~/src/parseMarkdown.ts'; export default defineCachedEventHandler(async (event) => { - const runtimeConfig = useRuntimeConfig(); const locale = getLocale(event); const [config, translator] = await Promise.all([loadConfig(locale), loadTranslator(locale)]); + const baseUrl = getUrlForLocale(locale); const posts = await getPosts(config); const feed = new Feed({ title: `${translator.translate('title')} • ${translator.translate('links.blog')}`, description: translator.translate('description'), - id: runtimeConfig.public.baseUrl, - link: `${runtimeConfig.public.baseUrl}/blog.atom`, + id: baseUrl, + link: `${baseUrl}/blog.atom`, language: config.locale, - image: `${runtimeConfig.public.baseUrl}/icon.png`, - favicon: `${runtimeConfig.public.baseUrl}/icon.png`, + image: `${baseUrl}/icon.png`, + favicon: `${baseUrl}/icon.png`, updated: new Date(posts[0].date), copyright: '', }); @@ -30,16 +31,16 @@ export default defineCachedEventHandler(async (event) => { feed.addItem({ title: post.title, - id: `${runtimeConfig.public.baseUrl}/${config.links.blogRoute}/${post.slug}`, - link: `${runtimeConfig.public.baseUrl}/${config.links.blogRoute}/${post.slug}`, + id: `${baseUrl}/${config.links.blogRoute}/${post.slug}`, + link: `${baseUrl}/${config.links.blogRoute}/${post.slug}`, description: parsed.intro ?? undefined, content: parsed.content ?? undefined, author: post.authors.map((author) => ({ name: author, - link: author.startsWith('@') ? `${runtimeConfig.public.baseUrl}/${author}` : undefined, + link: author.startsWith('@') ? `${baseUrl}/${author}` : undefined, })), date: new Date(post.date), - image: post.hero ? `${runtimeConfig.public.baseUrl}${post.hero.src}` : undefined, + image: post.hero ? `${baseUrl}${post.hero.src}` : undefined, }); } diff --git a/server/social.ts b/server/social.ts index 2174aa00f..046943d3c 100644 --- a/server/social.ts +++ b/server/social.ts @@ -5,6 +5,8 @@ import { importPKCS8, SignJWT } from 'jose'; import { rootDir } from './paths.ts'; +import { getUrlForLocale } from '~/src/domain.ts'; + const getAppleClientSecret = async (): Promise => { const headers = { alg: 'ES256', @@ -61,7 +63,7 @@ const enableApple = appleIsEnabled(); export const config: GrantConfig = { defaults: { - origin: process.env.NUXT_PUBLIC_BASE_URL, + origin: getUrlForLocale('_'), transport: 'session', state: true, prefix: '/api/connect', diff --git a/src/data.ts b/src/data.ts index c42197f65..245298de4 100644 --- a/src/data.ts +++ b/src/data.ts @@ -6,7 +6,7 @@ import type { Config } from '~/locale/config.ts'; import type { PronounExamplesData } from '~/locale/data.ts'; import type { Translations } from '~/locale/translations.ts'; import { buildCalendar } from '~/src/calendar/calendar.ts'; -import { getLocaleForUrl } from '~/src/domain.ts'; +import { getLocaleForUrl, getUrlForLocale } from '~/src/domain.ts'; export const getLocale = () => { return getLocaleForUrl(useRequestURL()) ?? '_'; @@ -110,8 +110,9 @@ export const loadNameCount = async () => { }; export const loadCalendar = async () => { - const runtimeConfig = useRuntimeConfig(); - const localEvents = (await import(`~/locale/${getLocale()}/calendar/events.ts`)).default; + const locale = getLocale(); + const baseUrl = getUrlForLocale(locale); + const localEvents = (await import(`~/locale/${locale}/calendar/events.ts`)).default; /* const rawNamedays = (await import(`~/locale/${config.locale}/names/namedays.json`)).default; const namedays = Object.entries(rawNamedays as Record) .flatMap(([name, namedays]) => { @@ -120,5 +121,5 @@ export const loadCalendar = async () => { return new Event(`nameday$${name}`, null, parseInt(m), day(parseInt(d)), EventLevel.Nameday); }); }); */ - return buildCalendar(localEvents, runtimeConfig.public.baseUrl); + return buildCalendar(localEvents, baseUrl); }; diff --git a/src/domain.ts b/src/domain.ts index 226efe63e..1fcf3a2a8 100644 --- a/src/domain.ts +++ b/src/domain.ts @@ -1,7 +1,7 @@ import localeDescriptions from '~/locale/locales.ts'; import buildLocaleList from '~/src/buildLocaleList.ts'; -const getLocaleUrls = (domainBase: string | undefined): Record => { +export const getLocaleUrls = (domainBase: string | undefined): Record => { if (domainBase) { return Object.fromEntries([ ['_', domainBase.replace('{locale}', '')