Organized caching + groundwork for page caching
This commit is contained in:
parent
d9ef48d811
commit
2cacccae86
1
TODO.md
1
TODO.md
|
@ -10,7 +10,6 @@
|
||||||
## Short term
|
## Short term
|
||||||
|
|
||||||
- [Bugs] On android Chrome, the setting button in the header flashes for a few ms when the page is loading
|
- [Bugs] On android Chrome, the setting button in the header flashes for a few ms when the page is loading
|
||||||
- [Feat] [caching] Use getURLs for precaching + precache everything
|
|
||||||
- [Bugs] Make sure uploads name are slug-like and with an extension.
|
- [Bugs] Make sure uploads name are slug-like and with an extension.
|
||||||
- [Bugs] Nyupun can't upload subtitles files
|
- [Bugs] Nyupun can't upload subtitles files
|
||||||
- [Bugs] https://v3.accords-library.com/en/collectibles/dod-original-soundtrack/scans obi is way too big
|
- [Bugs] https://v3.accords-library.com/en/collectibles/dod-original-soundtrack/scans obi is way too big
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import type {
|
||||||
|
EndpointWebsiteConfig,
|
||||||
|
EndpointWording,
|
||||||
|
Language,
|
||||||
|
} from "src/shared/payload/payload-sdk";
|
||||||
|
import { getLogger } from "src/utils/logger";
|
||||||
|
import { payload } from "src/utils/payload";
|
||||||
|
|
||||||
|
class ContextCache {
|
||||||
|
private initialized = false;
|
||||||
|
private logger = getLogger("[ContextCache]");
|
||||||
|
|
||||||
|
locales: Language[] = [];
|
||||||
|
currencies: string[] = [];
|
||||||
|
wordings: EndpointWording[] = [];
|
||||||
|
config: EndpointWebsiteConfig = {
|
||||||
|
home: { folders: [] },
|
||||||
|
timeline: { breaks: [], eras: [], eventCount: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (this.initialized) return;
|
||||||
|
await this.refreshAll();
|
||||||
|
this.initialized = true;
|
||||||
|
this.logger.log("Init complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refreshAll() {
|
||||||
|
await this.refreshCurrencies();
|
||||||
|
await this.refreshLocales();
|
||||||
|
await this.refreshWebsiteConfig();
|
||||||
|
await this.refreshWordings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshWordings() {
|
||||||
|
this.wordings = await payload.getWordings();
|
||||||
|
this.logger.log("Wordings refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshCurrencies() {
|
||||||
|
contextCache.currencies = (await payload.getCurrencies()).map(({ id }) => id);
|
||||||
|
this.logger.log("Currencies refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshLocales() {
|
||||||
|
contextCache.locales = await payload.getLanguages();
|
||||||
|
this.logger.log("Locales refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshWebsiteConfig() {
|
||||||
|
contextCache.config = await payload.getConfig();
|
||||||
|
this.logger.log("WebsiteConfig refreshed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextCache = new ContextCache();
|
||||||
|
await contextCache.init();
|
||||||
|
export { contextCache };
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { getLogger } from "src/utils/logger";
|
||||||
|
import { payload } from "src/utils/payload";
|
||||||
|
|
||||||
|
class DataCache {
|
||||||
|
private readonly logger = getLogger("[DataCache]");
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
private readonly responseCache = new Map<string, any>();
|
||||||
|
private readonly idsCacheMap = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (this.initialized) return;
|
||||||
|
|
||||||
|
if (import.meta.env.ENABLE_PRECACHING === "true") {
|
||||||
|
await this.precache();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async precache() {
|
||||||
|
const { urls } = await payload.getAllSdkUrls();
|
||||||
|
for (const url of urls) {
|
||||||
|
try {
|
||||||
|
await payload.request(url);
|
||||||
|
} catch {
|
||||||
|
this.logger.warn("Precaching failed for url", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.logger.log("Precaching completed!", this.responseCache.size, "responses cached");
|
||||||
|
}
|
||||||
|
|
||||||
|
get(url: string) {
|
||||||
|
const cachedResponse = this.responseCache.get(url);
|
||||||
|
if (cachedResponse) {
|
||||||
|
this.logger.log("Retrieved cached response for", url);
|
||||||
|
return structuredClone(cachedResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(url: string, response: any) {
|
||||||
|
const stringData = JSON.stringify(response);
|
||||||
|
const regex = /[a-f0-9]{24}/g;
|
||||||
|
const ids = [...stringData.matchAll(regex)].map((match) => match[0]);
|
||||||
|
const uniqueIds = [...new Set(ids)];
|
||||||
|
|
||||||
|
uniqueIds.forEach((id) => {
|
||||||
|
const current = this.idsCacheMap.get(id);
|
||||||
|
if (current) {
|
||||||
|
current.add(url);
|
||||||
|
} else {
|
||||||
|
this.idsCacheMap.set(id, new Set([url]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.responseCache.set(url, response);
|
||||||
|
this.logger.log("Cached response for", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async invalidate(ids: string[], urls: string[]) {
|
||||||
|
const urlsToInvalidate = new Set<string>(urls);
|
||||||
|
|
||||||
|
ids.forEach((id) => {
|
||||||
|
const urlsForThisId = this.idsCacheMap.get(id);
|
||||||
|
if (!urlsForThisId) return;
|
||||||
|
this.idsCacheMap.delete(id);
|
||||||
|
[...urlsForThisId].forEach((url) => urlsToInvalidate.add(url));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const url of urlsToInvalidate) {
|
||||||
|
this.responseCache.delete(url);
|
||||||
|
this.logger.log("Invalidated cache for", url);
|
||||||
|
try {
|
||||||
|
await payload.request(url);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.log("Revalidation fails for", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log("There are currently", this.responseCache.size, "responses in cache.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataCache = new DataCache();
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
EndpointPayloadImage,
|
EndpointPayloadImage,
|
||||||
EndpointVideo,
|
EndpointVideo,
|
||||||
} from "src/shared/payload/payload-sdk";
|
} from "src/shared/payload/payload-sdk";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
openGraph?:
|
openGraph?:
|
||||||
|
@ -28,7 +28,9 @@ const { openGraph = {} } = Astro.props;
|
||||||
const { description = t("global.meta.description"), audio, video } = openGraph;
|
const { description = t("global.meta.description"), audio, video } = openGraph;
|
||||||
|
|
||||||
const thumbnail =
|
const thumbnail =
|
||||||
openGraph.thumbnail?.openGraph ?? openGraph.thumbnail ?? cache.config.defaultOpenGraphImage;
|
openGraph.thumbnail?.openGraph ??
|
||||||
|
openGraph.thumbnail ??
|
||||||
|
contextCache.config.defaultOpenGraphImage;
|
||||||
|
|
||||||
const title = openGraph.title
|
const title = openGraph.title
|
||||||
? `${openGraph.title} – ${t("global.siteName")}`
|
? `${openGraph.title} – ${t("global.siteName")}`
|
||||||
|
@ -38,8 +40,6 @@ const userAgent = Astro.request.headers.get("user-agent") ?? "";
|
||||||
const parser = new UAParser(userAgent);
|
const parser = new UAParser(userAgent);
|
||||||
const isIOS = parser.getOS().name === "iOS";
|
const isIOS = parser.getOS().name === "iOS";
|
||||||
|
|
||||||
const { currentTheme } = Astro.locals;
|
|
||||||
|
|
||||||
/* Keep that separator here or else it breaks the HTML
|
/* Keep that separator here or else it breaks the HTML
|
||||||
----------------------------------------------- HTML -------------------------------------------- */
|
----------------------------------------------- HTML -------------------------------------------- */
|
||||||
---
|
---
|
||||||
|
@ -47,9 +47,7 @@ const { currentTheme } = Astro.locals;
|
||||||
<html
|
<html
|
||||||
lang={currentLocale}
|
lang={currentLocale}
|
||||||
class:list={{
|
class:list={{
|
||||||
"manual-theme": currentTheme !== "auto",
|
POST_PROCESS_HTML_CLASS: true,
|
||||||
"light-theme": currentTheme === "light",
|
|
||||||
"dark-theme": currentTheme === "dark",
|
|
||||||
"texture-dots": !isIOS,
|
"texture-dots": !isIOS,
|
||||||
"font-m": true,
|
"font-m": true,
|
||||||
"debug-lang": false,
|
"debug-lang": false,
|
||||||
|
@ -310,7 +308,7 @@ const { currentTheme } = Astro.locals;
|
||||||
color: var(--color-base-1000);
|
color: var(--color-base-1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not(.manual-theme) {
|
html:not(.light-theme, .dark-theme) {
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
& .when-dark-theme {
|
& .when-dark-theme {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Button from "components/Button.astro";
|
import Button from "components/Button.astro";
|
||||||
import Tooltip from "components/Tooltip.astro";
|
import Tooltip from "components/Tooltip.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import { formatCurrency } from "src/utils/currencies";
|
import { formatCurrency } from "src/utils/currencies";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -21,14 +21,13 @@ const { currentCurrency } = Astro.locals;
|
||||||
<Tooltip trigger="click" {...otherProps.class ? otherProps : {}}>
|
<Tooltip trigger="click" {...otherProps.class ? otherProps : {}}>
|
||||||
<div id="content" slot="tooltip-content">
|
<div id="content" slot="tooltip-content">
|
||||||
{
|
{
|
||||||
cache.currencies.map((id) => (
|
contextCache.currencies.map((id) => (
|
||||||
<a
|
<a
|
||||||
class:list={{
|
class:list={{
|
||||||
current: currentCurrency === id,
|
current: currentCurrency === id,
|
||||||
"pressable-link": true,
|
"pressable-link": true,
|
||||||
}}
|
}}
|
||||||
href={`?action-currency=${id}`}
|
href={`?action-currency=${id}`}>
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{`${id} (${formatCurrency(id)})`}
|
{`${id} (${formatCurrency(id)})`}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Button from "components/Button.astro";
|
import Button from "components/Button.astro";
|
||||||
import Tooltip from "components/Tooltip.astro";
|
import Tooltip from "components/Tooltip.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import { formatLocale } from "src/utils/format";
|
import { formatLocale } from "src/utils/format";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -21,11 +21,10 @@ const { t } = await getI18n(currentLocale);
|
||||||
<Tooltip trigger="click" {...otherProps.class ? otherProps : {}}>
|
<Tooltip trigger="click" {...otherProps.class ? otherProps : {}}>
|
||||||
<div id="content" slot="tooltip-content">
|
<div id="content" slot="tooltip-content">
|
||||||
{
|
{
|
||||||
cache.locales.map(({ id }) => (
|
contextCache.locales.map(({ id }) => (
|
||||||
<a
|
<a
|
||||||
class:list={{ current: currentLocale === id, "pressable-link": true }}
|
class:list={{ current: currentLocale === id, "pressable-link": true }}
|
||||||
href={`?action-lang=${id}`}
|
href={`?action-lang=${id}`}>
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{formatLocale(id)}
|
{formatLocale(id)}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Button from "components/Button.astro";
|
||||||
import Tooltip from "components/Tooltip.astro";
|
import Tooltip from "components/Tooltip.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
|
|
||||||
const { currentLocale, currentTheme } = Astro.locals;
|
const { currentLocale } = Astro.locals;
|
||||||
const { t } = await getI18n(currentLocale);
|
const { t } = await getI18n(currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -11,19 +11,13 @@ const { t } = await getI18n(currentLocale);
|
||||||
|
|
||||||
<Tooltip trigger="click">
|
<Tooltip trigger="click">
|
||||||
<div id="content" slot="tooltip-content">
|
<div id="content" slot="tooltip-content">
|
||||||
<a
|
<a class="pressable-link underline-when-dark" href="?action-theme=dark">
|
||||||
class:list={{ current: currentTheme === "dark", "pressable-link": true }}
|
|
||||||
href="?action-theme=dark">
|
|
||||||
{t("global.theme.dark")}
|
{t("global.theme.dark")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a class="pressable-link underline-when-auto" href="?action-theme=auto">
|
||||||
class:list={{ current: currentTheme === "auto", "pressable-link": true }}
|
|
||||||
href="?action-theme=auto">
|
|
||||||
{t("global.theme.auto")}
|
{t("global.theme.auto")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a class="pressable-link underline-when-light" href="?action-theme=light">
|
||||||
class:list={{ current: currentTheme === "light", "pressable-link": true }}
|
|
||||||
href="?action-theme=light">
|
|
||||||
{t("global.theme.light")}
|
{t("global.theme.light")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,10 +39,12 @@ const { t } = await getI18n(currentLocale);
|
||||||
#content {
|
#content {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
& > .current {
|
:global(html.light-theme) a.underline-when-light,
|
||||||
color: var(--color-base-750);
|
:global(html.dark-theme) a.underline-when-dark,
|
||||||
text-decoration: underline 0.08em var(--color-base-650);
|
:global(html:not(.light-theme, .dark-theme)) a.underline-when-auto {
|
||||||
}
|
color: var(--color-base-750);
|
||||||
|
text-decoration: underline 0.08em var(--color-base-650);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
declare namespace App {
|
declare namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
currentLocale: string;
|
currentLocale: string;
|
||||||
currentTheme: "dark" | "auto" | "light";
|
|
||||||
currentCurrency: string;
|
currentCurrency: string;
|
||||||
notFound: boolean;
|
notFound: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { WordingKey } from "src/i18n/wordings-keys";
|
import type { WordingKey } from "src/i18n/wordings-keys";
|
||||||
import type { ChronologyEvent, EndpointSource } from "src/shared/payload/payload-sdk";
|
import type { ChronologyEvent, EndpointSource } from "src/shared/payload/payload-sdk";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import { capitalize, formatInlineTitle } from "src/utils/format";
|
import { capitalize, formatInlineTitle } from "src/utils/format";
|
||||||
|
|
||||||
export const defaultLocale = "en";
|
export const defaultLocale = "en";
|
||||||
|
@ -113,7 +113,7 @@ export const getI18n = async (locale: string) => {
|
||||||
options[0]!; // We will consider that there will always be at least one option.
|
options[0]!; // We will consider that there will always be at least one option.
|
||||||
|
|
||||||
const t = (key: WordingKey, values: Record<string, any> = {}): string => {
|
const t = (key: WordingKey, values: Record<string, any> = {}): string => {
|
||||||
const wording = cache.wordings.find(({ name }) => name === key);
|
const wording = contextCache.wordings.find(({ name }) => name === key);
|
||||||
const fallbackString = `«${key}»`;
|
const fallbackString = `«${key}»`;
|
||||||
|
|
||||||
if (!wording) {
|
if (!wording) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { defineMiddleware, sequence } from "astro:middleware";
|
import { defineMiddleware, sequence } from "astro:middleware";
|
||||||
import { cache } from "src/utils/payload";
|
|
||||||
import acceptLanguage from "accept-language";
|
import acceptLanguage from "accept-language";
|
||||||
import type { AstroCookies } from "astro";
|
import type { AstroCookies } from "astro";
|
||||||
import { z } from "astro:content";
|
import { z } from "astro:content";
|
||||||
import { trackRequest, trackEvent } from "src/shared/analytics/analytics";
|
import { trackRequest, trackEvent } from "src/shared/analytics/analytics";
|
||||||
import { defaultLocale } from "src/i18n/i18n";
|
import { defaultLocale } from "src/i18n/i18n";
|
||||||
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
const ninetyDaysInSeconds = 60 * 60 * 24 * 90;
|
const ninetyDaysInSeconds = 60 * 60 * 24 * 90;
|
||||||
|
|
||||||
|
@ -141,20 +141,22 @@ const refreshCookiesMaxAge = defineMiddleware(async ({ cookies }, next) => {
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
const addContentLanguageResponseHeader = defineMiddleware(async ({ url }, next) => {
|
const addCommonHeaders = defineMiddleware(async ({ url }, next) => {
|
||||||
const currentLocale = getCurrentLocale(url.pathname);
|
const currentLocale = getCurrentLocale(url.pathname);
|
||||||
|
|
||||||
const response = await next();
|
const response = await next();
|
||||||
if (response.status === 200 && currentLocale) {
|
if (response.ok && currentLocale) {
|
||||||
response.headers.set("Content-Language", currentLocale);
|
response.headers.set("Content-Language", currentLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response.headers.set("Vary", "Cookie");
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
const provideLocalsToRequest = defineMiddleware(async ({ url, locals, cookies }, next) => {
|
const provideLocalsToRequest = defineMiddleware(async ({ url, locals, cookies }, next) => {
|
||||||
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
|
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
|
||||||
locals.currentCurrency = getCookieCurrency(cookies) ?? "USD";
|
locals.currentCurrency = getCookieCurrency(cookies) ?? "USD";
|
||||||
locals.currentTheme = getCookieTheme(cookies) ?? "auto";
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -165,19 +167,40 @@ const analytics = defineMiddleware(async (context, next) => {
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const postProcess = defineMiddleware(async ({ cookies }, next) => {
|
||||||
|
const response = await next();
|
||||||
|
if (!response.ok) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = await response.text();
|
||||||
|
|
||||||
|
const currentTheme = getCookieTheme(cookies) ?? "auto";
|
||||||
|
html = html.replace(
|
||||||
|
"POST_PROCESS_HTML_CLASS",
|
||||||
|
currentTheme === "dark" ? "dark-theme" : currentTheme === "light" ? "light-theme" : ""
|
||||||
|
);
|
||||||
|
return new Response(html, response);
|
||||||
|
});
|
||||||
|
|
||||||
export const onRequest = sequence(
|
export const onRequest = sequence(
|
||||||
addContentLanguageResponseHeader,
|
// Possible redirect
|
||||||
handleActionsSearchParams,
|
handleActionsSearchParams,
|
||||||
refreshCookiesMaxAge,
|
|
||||||
localeNegotiator,
|
localeNegotiator,
|
||||||
|
|
||||||
|
addCommonHeaders,
|
||||||
|
|
||||||
|
// Get a response
|
||||||
|
analytics,
|
||||||
|
refreshCookiesMaxAge,
|
||||||
provideLocalsToRequest,
|
provideLocalsToRequest,
|
||||||
analytics
|
postProcess
|
||||||
);
|
);
|
||||||
|
|
||||||
/* LOCALE */
|
/* LOCALE */
|
||||||
|
|
||||||
const getCurrentLocale = (pathname: string): string | undefined => {
|
const getCurrentLocale = (pathname: string): string | undefined => {
|
||||||
for (const locale of cache.locales) {
|
for (const locale of contextCache.locales) {
|
||||||
if (pathname.split("/")[1] === locale.id) {
|
if (pathname.split("/")[1] === locale.id) {
|
||||||
return locale.id;
|
return locale.id;
|
||||||
}
|
}
|
||||||
|
@ -189,7 +212,7 @@ const getBestAcceptedLanguage = (request: Request): string | undefined => {
|
||||||
const header = request.headers.get("Accept-Language");
|
const header = request.headers.get("Accept-Language");
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
acceptLanguage.languages(cache.locales.map(({ id }) => id));
|
acceptLanguage.languages(contextCache.locales.map(({ id }) => id));
|
||||||
|
|
||||||
return acceptLanguage.get(request.headers.get("Accept-Language")) ?? undefined;
|
return acceptLanguage.get(request.headers.get("Accept-Language")) ?? undefined;
|
||||||
};
|
};
|
||||||
|
@ -220,10 +243,12 @@ export const getCookieTheme = (cookies: AstroCookies): z.infer<typeof themeSchem
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidCurrency = (currency: string | null | undefined): currency is string =>
|
export const isValidCurrency = (currency: string | null | undefined): currency is string =>
|
||||||
currency !== null && currency != undefined && cache.currencies.includes(currency);
|
currency !== null && currency != undefined && contextCache.currencies.includes(currency);
|
||||||
|
|
||||||
export const isValidLocale = (locale: string | null | undefined): locale is string =>
|
export const isValidLocale = (locale: string | null | undefined): locale is string =>
|
||||||
locale !== null && locale != undefined && cache.locales.map(({ id }) => id).includes(locale);
|
locale !== null &&
|
||||||
|
locale != undefined &&
|
||||||
|
contextCache.locales.map(({ id }) => id).includes(locale);
|
||||||
|
|
||||||
export const isValidTheme = (
|
export const isValidTheme = (
|
||||||
theme: string | null | undefined
|
theme: string | null | undefined
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import LibraryCard from "./LibraryCard.astro";
|
import LibraryCard from "./LibraryCard.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||||
---
|
---
|
||||||
|
@ -9,7 +9,7 @@ const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.curren
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
{
|
{
|
||||||
cache.config.home.folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => (
|
contextCache.config.home.folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => (
|
||||||
<LibraryCard
|
<LibraryCard
|
||||||
img={
|
img={
|
||||||
darkThumbnail && lightThumbnail ? { dark: darkThumbnail, light: lightThumbnail } : undefined
|
darkThumbnail && lightThumbnail ? { dark: darkThumbnail, light: lightThumbnail } : undefined
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
import { dataCache } from "src/cache/dataCache";
|
||||||
import { Collections, type AfterOperationWebHookMessage } from "src/shared/payload/payload-sdk";
|
import { Collections, type AfterOperationWebHookMessage } from "src/shared/payload/payload-sdk";
|
||||||
import {
|
|
||||||
invalidateDataCache,
|
|
||||||
refreshCurrencies,
|
|
||||||
refreshLocales,
|
|
||||||
refreshWebsiteConfig,
|
|
||||||
refreshWordings,
|
|
||||||
} from "src/utils/payload";
|
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
const auth = request.headers.get("Authorization");
|
const auth = request.headers.get("Authorization");
|
||||||
|
@ -30,23 +25,23 @@ const handleWebHookMessage = async ({
|
||||||
urls,
|
urls,
|
||||||
id,
|
id,
|
||||||
}: AfterOperationWebHookMessage) => {
|
}: AfterOperationWebHookMessage) => {
|
||||||
await invalidateDataCache([...(id ? [id] : []), ...addedDependantIds], urls);
|
await dataCache.invalidate([...(id ? [id] : []), ...addedDependantIds], urls);
|
||||||
|
|
||||||
switch (collection) {
|
switch (collection) {
|
||||||
case Collections.Wordings:
|
case Collections.Wordings:
|
||||||
await refreshWordings();
|
await contextCache.refreshWordings();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Collections.Currencies:
|
case Collections.Currencies:
|
||||||
await refreshCurrencies();
|
await contextCache.refreshCurrencies();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Collections.Languages:
|
case Collections.Languages:
|
||||||
await refreshLocales();
|
await contextCache.refreshLocales();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Collections.WebsiteConfig:
|
case Collections.WebsiteConfig:
|
||||||
await refreshWebsiteConfig();
|
await contextCache.refreshWebsiteConfig();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { initPayload } from "src/utils/payload";
|
import { dataCache } from "src/cache/dataCache";
|
||||||
|
|
||||||
export const GET: APIRoute = async () => {
|
export const GET: APIRoute = async () => {
|
||||||
await initPayload();
|
await dataCache.init();
|
||||||
return new Response(null, { status: 200, statusText: "Ok" });
|
return new Response(null, { status: 200, statusText: "Ok" });
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
||||||
const { baseColors, theme } = Astro.props;
|
const { baseColors, theme } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div id="container" class:list={[theme, "manual-theme"]}>
|
<div id="container" class:list={theme}>
|
||||||
<h4 class="font-xl">Base colors</h4>
|
<h4 class="font-xl">Base colors</h4>
|
||||||
<div class="colors">
|
<div class="colors">
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Button from "components/Button.astro";
|
||||||
import LibraryGrid from "./_components/LibraryGrid.astro";
|
import LibraryGrid from "./_components/LibraryGrid.astro";
|
||||||
import LinkCard from "./_components/LinkCard.astro";
|
import LinkCard from "./_components/LinkCard.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
import HomeTitle from "./_components/HomeTitle.astro";
|
import HomeTitle from "./_components/HomeTitle.astro";
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
<AppLayout
|
<AppLayout
|
||||||
openGraph={{ title: t("home.title") }}
|
openGraph={{ title: t("home.title") }}
|
||||||
backgroundImage={cache.config.home.backgroundImage}
|
backgroundImage={contextCache.config.home.backgroundImage}
|
||||||
hideFooterLinks
|
hideFooterLinks
|
||||||
hideHomeButton
|
hideHomeButton
|
||||||
class="app">
|
class="app">
|
||||||
|
@ -77,8 +77,8 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
icon="material-symbols:calendar-month"
|
icon="material-symbols:calendar-month"
|
||||||
title={t("footer.links.timeline.title")}
|
title={t("footer.links.timeline.title")}
|
||||||
subtitle={t("footer.links.timeline.subtitle", {
|
subtitle={t("footer.links.timeline.subtitle", {
|
||||||
eraCount: cache.config.timeline.eras.length,
|
eraCount: contextCache.config.timeline.eras.length,
|
||||||
eventCount: cache.config.timeline.eventCount,
|
eventCount: contextCache.config.timeline.eventCount,
|
||||||
})}
|
})}
|
||||||
href={getLocalizedUrl("/timeline")}
|
href={getLocalizedUrl("/timeline")}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import { formatCurrency } from "src/utils/currencies";
|
import { formatCurrency } from "src/utils/currencies";
|
||||||
import { formatLocale } from "src/utils/format";
|
import { formatLocale } from "src/utils/format";
|
||||||
|
|
||||||
const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
const { currentLocale, currentCurrency } = Astro.locals;
|
||||||
const { t } = await getI18n(currentLocale);
|
const { t } = await getI18n(currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -20,11 +20,10 @@ const { t } = await getI18n(currentLocale);
|
||||||
<h2>{t("settings.language.title")}</h2>
|
<h2>{t("settings.language.title")}</h2>
|
||||||
<p>{t("settings.language.description")}</p><br />
|
<p>{t("settings.language.description")}</p><br />
|
||||||
{
|
{
|
||||||
cache.locales.map(({ id }) => (
|
contextCache.locales.map(({ id }) => (
|
||||||
<a
|
<a
|
||||||
class:list={{ current: currentLocale === id, "pressable-link": true }}
|
class:list={{ current: currentLocale === id, "pressable-link": true }}
|
||||||
href={`?action-lang=${id}`}
|
href={`?action-lang=${id}`}>
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{formatLocale(id)}
|
{formatLocale(id)}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
@ -34,22 +33,13 @@ const { t } = await getI18n(currentLocale);
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{t("settings.theme.title")}</h2>
|
<h2>{t("settings.theme.title")}</h2>
|
||||||
<p>{t("settings.theme.description")}</p><br />
|
<p>{t("settings.theme.description")}</p><br />
|
||||||
<a
|
<a class="pressable-link underline-when-dark" href="?action-theme=dark">
|
||||||
class:list={{ current: currentTheme === "dark", "pressable-link": true }}
|
|
||||||
href="?action-theme=dark"
|
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{t("global.theme.dark")}
|
{t("global.theme.dark")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a class="pressable-link underline-when-auto" href="?action-theme=auto">
|
||||||
class:list={{ current: currentTheme === "auto", "pressable-link": true }}
|
|
||||||
href="?action-theme=auto"
|
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{t("global.theme.auto")}
|
{t("global.theme.auto")}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a class="pressable-link underline-when-light" href="?action-theme=light">
|
||||||
class:list={{ current: currentTheme === "light", "pressable-link": true }}
|
|
||||||
href="?action-theme=light"
|
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{t("global.theme.light")}
|
{t("global.theme.light")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,11 +48,10 @@ const { t } = await getI18n(currentLocale);
|
||||||
<h2>{t("settings.currency.title")}</h2>
|
<h2>{t("settings.currency.title")}</h2>
|
||||||
<p>{t("settings.currency.description")}</p><br />
|
<p>{t("settings.currency.description")}</p><br />
|
||||||
{
|
{
|
||||||
cache.currencies.map((id) => (
|
contextCache.currencies.map((id) => (
|
||||||
<a
|
<a
|
||||||
class:list={{ current: currentCurrency === id, "pressable-link": true }}
|
class:list={{ current: currentCurrency === id, "pressable-link": true }}
|
||||||
href={`?action-currency=${id}`}
|
href={`?action-currency=${id}`}>
|
||||||
data-astro-prefetch="tap">
|
|
||||||
{`${id} (${formatCurrency(id)})`}
|
{`${id} (${formatCurrency(id)})`}
|
||||||
</a>
|
</a>
|
||||||
))
|
))
|
||||||
|
@ -86,6 +75,13 @@ const { t } = await getI18n(currentLocale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(html.light-theme) a.underline-when-light,
|
||||||
|
:global(html.dark-theme) a.underline-when-dark,
|
||||||
|
:global(html:not(.light-theme, .dark-theme)) a.underline-when-auto {
|
||||||
|
color: var(--color-base-750);
|
||||||
|
text-decoration: underline 0.08em var(--color-base-650);
|
||||||
|
}
|
||||||
|
|
||||||
#main {
|
#main {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { EndpointChronologyEvent } from "src/shared/payload/payload-sdk";
|
import type { EndpointChronologyEvent } from "src/shared/payload/payload-sdk";
|
||||||
import TimelineEvent from "./TimelineEvent.astro";
|
import TimelineEvent from "./TimelineEvent.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
year: number;
|
year: number;
|
||||||
|
@ -25,7 +25,7 @@ if (year === 856) {
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
{cache.config.timeline.breaks.includes(year) && <hr id={`hr-${year}`} />}
|
{contextCache.config.timeline.breaks.includes(year) && <hr id={`hr-${year}`} />}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="font-2xl" class:list={{ multiple }} id={year.toString()}>
|
<h2 class="font-2xl" class:list={{ multiple }} id={year.toString()}>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||||
import Card from "components/Card.astro";
|
import Card from "components/Card.astro";
|
||||||
import { getI18n } from "src/i18n/i18n";
|
import { getI18n } from "src/i18n/i18n";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
import type { WordingKey } from "src/i18n/wordings-keys";
|
import type { WordingKey } from "src/i18n/wordings-keys";
|
||||||
|
|
||||||
const events = await payload.getChronologyEvents();
|
const events = await payload.getChronologyEvents();
|
||||||
|
@ -16,7 +16,7 @@ const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.cu
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
<AppLayout backgroundImage={cache.config.timeline.backgroundImage} class="app">
|
<AppLayout backgroundImage={contextCache.config.timeline.backgroundImage} class="app">
|
||||||
<AppLayoutTitle title={t("timeline.title")} />
|
<AppLayoutTitle title={t("timeline.title")} />
|
||||||
<p class="prose" set:html={t("timeline.description")} />
|
<p class="prose" set:html={t("timeline.description")} />
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.cu
|
||||||
<h3>{t("timeline.jumpTo")}</h3>
|
<h3>{t("timeline.jumpTo")}</h3>
|
||||||
|
|
||||||
{
|
{
|
||||||
cache.config.timeline.eras.map(({ name, startingYear, endingYear }) => (
|
contextCache.config.timeline.eras.map(({ name, startingYear, endingYear }) => (
|
||||||
<p
|
<p
|
||||||
set:html={t(name as WordingKey, {
|
set:html={t(name as WordingKey, {
|
||||||
start: `<a href="#${startingYear}">${formatTimelineDate({ year: startingYear })}</a>`,
|
start: `<a href="#${startingYear}">${formatTimelineDate({ year: startingYear })}</a>`,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
const getUnlocalizedPathname = (pathname: string): string => {
|
const getUnlocalizedPathname = (pathname: string): string => {
|
||||||
for (const locale of cache.locales) {
|
for (const locale of contextCache.locales) {
|
||||||
if (pathname.startsWith(`/${locale.id}`)) {
|
if (pathname.startsWith(`/${locale.id}`)) {
|
||||||
return pathname.substring(`/${locale.id}`.length) || "/";
|
return pathname.substring(`/${locale.id}`.length) || "/";
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ export const trackRequest = (request: Request, { clientAddress, locals }: TrackR
|
||||||
attributes: {
|
attributes: {
|
||||||
locale: locals.currentLocale,
|
locale: locals.currentLocale,
|
||||||
currency: locals.currentCurrency,
|
currency: locals.currentCurrency,
|
||||||
theme: locals.currentTheme,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
|
|
|
@ -1,177 +1,177 @@
|
||||||
{
|
{
|
||||||
"disclaimer": "Usage subject to terms: https://openexchangerates.org/terms",
|
"disclaimer": "Usage subject to terms: https://openexchangerates.org/terms",
|
||||||
"license": "https://openexchangerates.org/license",
|
"license": "https://openexchangerates.org/license",
|
||||||
"timestamp": 1719374401,
|
"timestamp": 1719518400,
|
||||||
"base": "USD",
|
"base": "USD",
|
||||||
"rates": {
|
"rates": {
|
||||||
"AED": 3.673,
|
"AED": 3.673,
|
||||||
"AFN": 70.737389,
|
"AFN": 70,
|
||||||
"ALL": 93.591883,
|
"ALL": 93.656243,
|
||||||
"AMD": 388.464511,
|
"AMD": 388.12,
|
||||||
"ANG": 1.804398,
|
"ANG": 1.803057,
|
||||||
"AOA": 855.209667,
|
"AOA": 853.629,
|
||||||
"ARS": 909.2076,
|
"ARS": 911.0001,
|
||||||
"AUD": 1.498775,
|
"AUD": 1.504506,
|
||||||
"AWG": 1.8025,
|
"AWG": 1.8025,
|
||||||
"AZN": 1.7,
|
"AZN": 1.7,
|
||||||
"BAM": 1.825905,
|
"BAM": 1.827765,
|
||||||
"BBD": 2,
|
"BBD": 2,
|
||||||
"BDT": 117.631657,
|
"BDT": 117.53659,
|
||||||
"BGN": 1.8261,
|
"BGN": 1.826865,
|
||||||
"BHD": 0.376925,
|
"BHD": 0.376928,
|
||||||
"BIF": 2879.142593,
|
"BIF": 2882.5,
|
||||||
"BMD": 1,
|
"BMD": 1,
|
||||||
"BND": 1.354432,
|
"BND": 1.357361,
|
||||||
"BOB": 6.917863,
|
"BOB": 6.912666,
|
||||||
"BRL": 5.452199,
|
"BRL": 5.5062,
|
||||||
"BSD": 1,
|
"BSD": 1,
|
||||||
"BTC": 0.000016147327,
|
"BTC": 0.000016286712,
|
||||||
"BTN": 83.525391,
|
"BTN": 83.510338,
|
||||||
"BWP": 13.566127,
|
"BWP": 13.649322,
|
||||||
"BYN": 3.276229,
|
"BYN": 3.274029,
|
||||||
"BZD": 2.017943,
|
"BZD": 2.016506,
|
||||||
"CAD": 1.365775,
|
"CAD": 1.36937,
|
||||||
"CDF": 2845.344278,
|
"CDF": 2860,
|
||||||
"CHF": 0.895078,
|
"CHF": 0.898769,
|
||||||
"CLF": 0.034108,
|
"CLF": 0.034588,
|
||||||
"CLP": 943.396226,
|
"CLP": 954.24,
|
||||||
"CNH": 7.292088,
|
"CNH": 7.303225,
|
||||||
"CNY": 7.2661,
|
"CNY": 7.2683,
|
||||||
"COP": 4092.688822,
|
"COP": 4147.064273,
|
||||||
"CRC": 524.018922,
|
"CRC": 523.036765,
|
||||||
"CUC": 1,
|
"CUC": 1,
|
||||||
"CUP": 25.75,
|
"CUP": 25.75,
|
||||||
"CVE": 102.941746,
|
"CVE": 103.063904,
|
||||||
"CZK": 23.187699,
|
"CZK": 23.432,
|
||||||
"DJF": 178.249371,
|
"DJF": 177.5,
|
||||||
"DKK": 6.9642,
|
"DKK": 6.969109,
|
||||||
"DOP": 59.114309,
|
"DOP": 59.2,
|
||||||
"DZD": 134.5516,
|
"DZD": 134.481574,
|
||||||
"EGP": 48.377851,
|
"EGP": 48.0279,
|
||||||
"ERN": 15,
|
"ERN": 15,
|
||||||
"ETB": 57.353303,
|
"ETB": 57.750301,
|
||||||
"EUR": 0.933624,
|
"EUR": 0.934324,
|
||||||
"FJD": 2.2362,
|
"FJD": 2.24125,
|
||||||
"FKP": 0.788219,
|
"FKP": 0.791234,
|
||||||
"GBP": 0.788219,
|
"GBP": 0.791234,
|
||||||
"GEL": 2.81,
|
"GEL": 2.8,
|
||||||
"GGP": 0.788219,
|
"GGP": 0.791234,
|
||||||
"GHS": 15.216498,
|
"GHS": 15.25,
|
||||||
"GIP": 0.788219,
|
"GIP": 0.791234,
|
||||||
"GMD": 67.75,
|
"GMD": 67.775,
|
||||||
"GNF": 8617.038799,
|
"GNF": 8595,
|
||||||
"GTQ": 7.774894,
|
"GTQ": 7.773841,
|
||||||
"GYD": 209.365764,
|
"GYD": 209.310316,
|
||||||
"HKD": 7.809496,
|
"HKD": 7.808725,
|
||||||
"HNL": 24.774423,
|
"HNL": 24.762821,
|
||||||
"HRK": 7.034221,
|
"HRK": 7.039709,
|
||||||
"HTG": 132.791853,
|
"HTG": 132.614267,
|
||||||
"HUF": 369.47,
|
"HUF": 370.35232,
|
||||||
"IDR": 16421.779805,
|
"IDR": 16384.008966,
|
||||||
"ILS": 3.74785,
|
"ILS": 3.75783,
|
||||||
"IMP": 0.788219,
|
"IMP": 0.791234,
|
||||||
"INR": 83.447347,
|
"INR": 83.466142,
|
||||||
"IQD": 1311.401658,
|
"IQD": 1310.629281,
|
||||||
"IRR": 42087.5,
|
"IRR": 42100,
|
||||||
"ISK": 139.21,
|
"ISK": 139.14,
|
||||||
"JEP": 0.788219,
|
"JEP": 0.791234,
|
||||||
"JMD": 156.444628,
|
"JMD": 156.065666,
|
||||||
"JOD": 0.7087,
|
"JOD": 0.7087,
|
||||||
"JPY": 159.8375,
|
"JPY": 160.819,
|
||||||
"KES": 129.646953,
|
"KES": 129,
|
||||||
"KGS": 86.5868,
|
"KGS": 86.45,
|
||||||
"KHR": 4119.876268,
|
"KHR": 4116,
|
||||||
"KMF": 460.124977,
|
"KMF": 460.04988,
|
||||||
"KPW": 900,
|
"KPW": 900,
|
||||||
"KRW": 1390.097786,
|
"KRW": 1387.206613,
|
||||||
"KWD": 0.306622,
|
"KWD": 0.306775,
|
||||||
"KYD": 0.834247,
|
"KYD": 0.833746,
|
||||||
"KZT": 467.84526,
|
"KZT": 466.751615,
|
||||||
"LAK": 22038.352227,
|
"LAK": 22077.44273,
|
||||||
"LBP": 89647.700512,
|
"LBP": 89576.348385,
|
||||||
"LKR": 305.644882,
|
"LKR": 306.055904,
|
||||||
"LRD": 194.349995,
|
"LRD": 194.492735,
|
||||||
"LSL": 18.152071,
|
"LSL": 18.359053,
|
||||||
"LYD": 4.855,
|
"LYD": 4.87349,
|
||||||
"MAD": 9.950426,
|
"MAD": 9.939151,
|
||||||
"MDL": 17.920244,
|
"MDL": 17.849263,
|
||||||
"MGA": 4473.169164,
|
"MGA": 4476.966706,
|
||||||
"MKD": 57.439335,
|
"MKD": 57.476359,
|
||||||
"MMK": 2481.91,
|
"MMK": 2481.91,
|
||||||
"MNT": 3450,
|
"MNT": 3450,
|
||||||
"MOP": 8.051048,
|
"MOP": 8.047221,
|
||||||
"MRU": 39.352282,
|
"MRU": 39.452362,
|
||||||
"MUR": 46.929999,
|
"MUR": 46.899999,
|
||||||
"MVR": 15.405,
|
"MVR": 15.405,
|
||||||
"MWK": 1735.964257,
|
"MWK": 1734.657401,
|
||||||
"MXN": 18.104717,
|
"MXN": 18.41087,
|
||||||
"MYR": 4.71,
|
"MYR": 4.7195,
|
||||||
"MZN": 63.850001,
|
"MZN": 63.850001,
|
||||||
"NAD": 18.152071,
|
"NAD": 18.359053,
|
||||||
"NGN": 1518.12,
|
"NGN": 1515.9,
|
||||||
"NIO": 36.848246,
|
"NIO": 36.825702,
|
||||||
"NOK": 10.607752,
|
"NOK": 10.666237,
|
||||||
"NPR": 133.640175,
|
"NPR": 133.611854,
|
||||||
"NZD": 1.635653,
|
"NZD": 1.643791,
|
||||||
"OMR": 0.384952,
|
"OMR": 0.384955,
|
||||||
"PAB": 1,
|
"PAB": 1,
|
||||||
"PEN": 3.815747,
|
"PEN": 3.823237,
|
||||||
"PGK": 3.906082,
|
"PGK": 3.904308,
|
||||||
"PHP": 58.837749,
|
"PHP": 58.639498,
|
||||||
"PKR": 278.808073,
|
"PKR": 278.503056,
|
||||||
"PLN": 4.011559,
|
"PLN": 4.029254,
|
||||||
"PYG": 7549.795422,
|
"PYG": 7540.098869,
|
||||||
"QAR": 3.651064,
|
"QAR": 3.649042,
|
||||||
"RON": 4.644,
|
"RON": 4.6507,
|
||||||
"RSD": 109.292759,
|
"RSD": 109.379523,
|
||||||
"RUB": 87.502462,
|
"RUB": 84.998456,
|
||||||
"RWF": 1326.548635,
|
"RWF": 1306.142519,
|
||||||
"SAR": 3.751926,
|
"SAR": 3.751634,
|
||||||
"SBD": 8.43942,
|
"SBD": 8.43942,
|
||||||
"SCR": 13.861839,
|
"SCR": 13.796937,
|
||||||
"SDG": 601,
|
"SDG": 601,
|
||||||
"SEK": 10.509533,
|
"SEK": 10.62955,
|
||||||
"SGD": 1.354893,
|
"SGD": 1.358205,
|
||||||
"SHP": 0.788219,
|
"SHP": 0.791234,
|
||||||
"SLL": 20969.5,
|
"SLL": 20969.5,
|
||||||
"SOS": 572.148966,
|
"SOS": 571.751991,
|
||||||
"SRD": 31.0905,
|
"SRD": 30.623,
|
||||||
"SSP": 130.26,
|
"SSP": 130.26,
|
||||||
"STD": 22281.8,
|
"STD": 22281.8,
|
||||||
"STN": 22.872934,
|
"STN": 22.899229,
|
||||||
"SVC": 8.759785,
|
"SVC": 8.754335,
|
||||||
"SYP": 2512.53,
|
"SYP": 2512.53,
|
||||||
"SZL": 18.159157,
|
"SZL": 18.178413,
|
||||||
"THB": 36.7755,
|
"THB": 36.7685,
|
||||||
"TJS": 10.696778,
|
"TJS": 10.65474,
|
||||||
"TMT": 3.51,
|
"TMT": 3.51,
|
||||||
"TND": 3.133858,
|
"TND": 3.136769,
|
||||||
"TOP": 2.359259,
|
"TOP": 2.36092,
|
||||||
"TRY": 33.0006,
|
"TRY": 32.836478,
|
||||||
"TTD": 6.801133,
|
"TTD": 6.7978,
|
||||||
"TWD": 32.533,
|
"TWD": 32.560001,
|
||||||
"TZS": 2638.159,
|
"TZS": 2626.295328,
|
||||||
"UAH": 40.666635,
|
"UAH": 40.51595,
|
||||||
"UGX": 3709.12772,
|
"UGX": 3711.539326,
|
||||||
"USD": 1,
|
"USD": 1,
|
||||||
"UYU": 39.322323,
|
"UYU": 39.578963,
|
||||||
"UZS": 12613.6,
|
"UZS": 12585.732694,
|
||||||
"VES": 36.320372,
|
"VES": 36.35908,
|
||||||
"VND": 25461.48991,
|
"VND": 25455.008685,
|
||||||
"VUV": 118.722,
|
"VUV": 118.722,
|
||||||
"WST": 2.8,
|
"WST": 2.8,
|
||||||
"XAF": 612.41733,
|
"XAF": 612.876514,
|
||||||
"XAG": 0.03457994,
|
"XAG": 0.0345453,
|
||||||
"XAU": 0.00043156,
|
"XAU": 0.00042988,
|
||||||
"XCD": 2.70255,
|
"XCD": 2.70255,
|
||||||
"XDR": 0.761402,
|
"XDR": 0.759718,
|
||||||
"XOF": 612.41733,
|
"XOF": 612.876514,
|
||||||
"XPD": 0.00107841,
|
"XPD": 0.00108638,
|
||||||
"XPF": 111.411003,
|
"XPF": 111.494537,
|
||||||
"XPT": 0.00101239,
|
"XPT": 0.00101314,
|
||||||
"YER": 250.399984,
|
"YER": 250.399984,
|
||||||
"ZAR": 18.227543,
|
"ZAR": 18.4643,
|
||||||
"ZMW": 25.80335,
|
"ZMW": 25.735569,
|
||||||
"ZWL": 322
|
"ZWL": 322
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,10 +6,10 @@ import {
|
||||||
type RichTextContent,
|
type RichTextContent,
|
||||||
type RichTextNode,
|
type RichTextNode,
|
||||||
} from "src/shared/payload/payload-sdk";
|
} from "src/shared/payload/payload-sdk";
|
||||||
import { cache } from "src/utils/payload";
|
import { contextCache } from "src/cache/contextCache";
|
||||||
|
|
||||||
export const formatLocale = (code: string): string =>
|
export const formatLocale = (code: string): string =>
|
||||||
cache.locales.find(({ id }) => id === code)?.name ?? code;
|
contextCache.locales.find(({ id }) => id === code)?.name ?? code;
|
||||||
|
|
||||||
export const formatInlineTitle = ({
|
export const formatInlineTitle = ({
|
||||||
pretitle,
|
pretitle,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const getLogger = (prefix: string): Pick<Console, "log" | "error" | "warn" | "debug"> => ({
|
||||||
|
debug: (...message) => console.debug(prefix, ...message),
|
||||||
|
log: (...message) => console.log(prefix, ...message),
|
||||||
|
warn: (...message) => console.warn(prefix, ...message),
|
||||||
|
error: (...message) => console.error(prefix, ...message),
|
||||||
|
});
|
|
@ -1,18 +1,9 @@
|
||||||
import {
|
import { dataCache } from "src/cache/dataCache";
|
||||||
type Language,
|
|
||||||
type EndpointWording,
|
|
||||||
type EndpointWebsiteConfig,
|
|
||||||
} from "src/shared/payload/payload-sdk";
|
|
||||||
import { getPayloadSDK } from "src/shared/payload/payload-sdk";
|
import { getPayloadSDK } from "src/shared/payload/payload-sdk";
|
||||||
|
|
||||||
let token: string | undefined = undefined;
|
let token: string | undefined = undefined;
|
||||||
let expiration: number | undefined = undefined;
|
let expiration: number | undefined = undefined;
|
||||||
|
|
||||||
const responseCache = new Map<string, any>();
|
|
||||||
const idsCacheMap = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
const isPrecachingEnabled = import.meta.env.ENABLE_PRECACHING === "true";
|
|
||||||
|
|
||||||
export const payload = getPayloadSDK({
|
export const payload = getPayloadSDK({
|
||||||
apiURL: import.meta.env.PAYLOAD_API_URL,
|
apiURL: import.meta.env.PAYLOAD_API_URL,
|
||||||
email: import.meta.env.PAYLOAD_USER,
|
email: import.meta.env.PAYLOAD_USER,
|
||||||
|
@ -33,108 +24,5 @@ export const payload = getPayloadSDK({
|
||||||
console.log("[PayloadSDK] New token set. TTL is", diffInMinutes, "minutes.");
|
console.log("[PayloadSDK] New token set. TTL is", diffInMinutes, "minutes.");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
responseCache: {
|
responseCache: dataCache,
|
||||||
get: (url) => {
|
|
||||||
const cachedResponse = responseCache.get(url);
|
|
||||||
if (cachedResponse) {
|
|
||||||
console.log("[ResponseCaching] Retrieved cache response for", url);
|
|
||||||
return structuredClone(cachedResponse);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (url, response) => {
|
|
||||||
const stringData = JSON.stringify(response);
|
|
||||||
const regex = /[a-f0-9]{24}/g;
|
|
||||||
const ids = [...stringData.matchAll(regex)].map((match) => match[0]);
|
|
||||||
const uniqueIds = [...new Set(ids)];
|
|
||||||
|
|
||||||
uniqueIds.forEach((id) => {
|
|
||||||
const current = idsCacheMap.get(id);
|
|
||||||
if (current) {
|
|
||||||
current.add(url);
|
|
||||||
} else {
|
|
||||||
idsCacheMap.set(id, new Set([url]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[ResponseCaching] Caching response for", url);
|
|
||||||
responseCache.set(url, response);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const invalidateDataCache = async (ids: string[], urls: string[]) => {
|
|
||||||
const urlsToInvalidate = new Set<string>(urls);
|
|
||||||
|
|
||||||
ids.forEach((id) => {
|
|
||||||
const urlsForThisId = idsCacheMap.get(id);
|
|
||||||
if (!urlsForThisId) return;
|
|
||||||
idsCacheMap.delete(id);
|
|
||||||
[...urlsForThisId].forEach((url) => urlsToInvalidate.add(url));
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const url of urlsToInvalidate) {
|
|
||||||
responseCache.delete(url);
|
|
||||||
console.log("[ResponseCaching][Invalidation] Deleted cache for", url);
|
|
||||||
try {
|
|
||||||
await payload.request(url);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("[ResponseCaching][Revalidation] Failure for", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("[ResponseCaching] There are currently", responseCache.size, "responses in cache.");
|
|
||||||
};
|
|
||||||
|
|
||||||
type Cache = {
|
|
||||||
locales: Language[];
|
|
||||||
currencies: string[];
|
|
||||||
wordings: EndpointWording[];
|
|
||||||
config: EndpointWebsiteConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchNewData = async (): Promise<Cache> => ({
|
|
||||||
locales: await payload.getLanguages(),
|
|
||||||
currencies: (await payload.getCurrencies()).map(({ id }) => id),
|
|
||||||
wordings: await payload.getWordings(),
|
|
||||||
config: await payload.getConfig(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export let cache = await fetchNewData();
|
|
||||||
|
|
||||||
export const refreshWordings = async () => {
|
|
||||||
cache.wordings = await payload.getWordings();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshCurrencies = async () => {
|
|
||||||
cache.currencies = (await payload.getCurrencies()).map(({ id }) => id);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshLocales = async () => {
|
|
||||||
cache.locales = await payload.getLanguages();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshWebsiteConfig = async () => {
|
|
||||||
cache.config = await payload.getConfig();
|
|
||||||
};
|
|
||||||
|
|
||||||
let payloadInitialized = false;
|
|
||||||
export const initPayload = async () => {
|
|
||||||
if (!payloadInitialized) {
|
|
||||||
if (!isPrecachingEnabled) {
|
|
||||||
payloadInitialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { urls } = await payload.getAllSdkUrls();
|
|
||||||
for (const url of urls) {
|
|
||||||
try {
|
|
||||||
await payload.request(url);
|
|
||||||
} catch {
|
|
||||||
console.warn("[ResponseCaching] Precaching failed for url", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("[ResponseCaching] Precaching completed!", responseCache.size, "responses cached");
|
|
||||||
|
|
||||||
payloadInitialized = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue