Use backlinks and rework the caching system

This commit is contained in:
DrMint 2024-07-26 09:22:33 +02:00
parent f1b37a31a9
commit 7dd91f5847
33 changed files with 240 additions and 280 deletions

View File

@ -3,7 +3,8 @@ import type {
EndpointWebsiteConfig, EndpointWebsiteConfig,
EndpointWording, EndpointWording,
} from "src/shared/payload/endpoint-types"; } from "src/shared/payload/endpoint-types";
import type { PayloadSDK } from "src/shared/payload/sdk"; import { SDKEndpointNames, type PayloadSDK } from "src/shared/payload/sdk";
import type { EndpointChange } from "src/shared/payload/webhooks";
import { getLogger } from "src/utils/logger"; import { getLogger } from "src/utils/logger";
export class ContextCache { export class ContextCache {
@ -35,24 +36,42 @@ export class ContextCache {
await this.refreshWordings(); await this.refreshWordings();
} }
async refreshWordings() { async invalidate(changes: EndpointChange[]) {
for (const change of changes) {
switch (change.type) {
case SDKEndpointNames.getWordings:
return await this.refreshWordings();
case SDKEndpointNames.getLanguages:
return await this.refreshLocales();
case SDKEndpointNames.getCurrencies:
return await this.refreshCurrencies();
case SDKEndpointNames.getWebsiteConfig:
return await this.refreshWebsiteConfig();
}
}
}
private async refreshWordings() {
this.wordings = (await this.payload.getWordings()).data; this.wordings = (await this.payload.getWordings()).data;
this.logger.log("Wordings refreshed"); this.logger.log("Wordings refreshed");
} }
async refreshCurrencies() { private async refreshCurrencies() {
this.currencies = (await this.payload.getCurrencies()).data.map(({ id }) => id); this.currencies = (await this.payload.getCurrencies()).data.map(({ id }) => id);
this.logger.log("Currencies refreshed"); this.logger.log("Currencies refreshed");
} }
async refreshLocales() { private async refreshLocales() {
this.languages = (await this.payload.getLanguages()).data; this.languages = (await this.payload.getLanguages()).data;
this.locales = this.languages.filter(({ selectable }) => selectable).map(({ id }) => id); this.locales = this.languages.filter(({ selectable }) => selectable).map(({ id }) => id);
this.logger.log("Locales refreshed"); this.logger.log("Locales refreshed");
} }
async refreshWebsiteConfig() { private async refreshWebsiteConfig() {
this.config = (await this.payload.getConfig()).data; this.config = (await this.payload.getWebsiteConfig()).data;
this.logger.log("WebsiteConfig refreshed"); this.logger.log("WebsiteConfig refreshed");
} }
} }

View File

@ -2,6 +2,7 @@ import { getLogger } from "src/utils/logger";
import { writeFile, mkdir, readFile } from "fs/promises"; import { writeFile, mkdir, readFile } from "fs/promises";
import { existsSync } from "fs"; import { existsSync } from "fs";
import type { PayloadSDK } from "src/shared/payload/sdk"; import type { PayloadSDK } from "src/shared/payload/sdk";
import type { EndpointChange } from "src/shared/payload/webhooks";
const ON_DISK_ROOT = `.cache/dataCache`; const ON_DISK_ROOT = `.cache/dataCache`;
const ON_DISK_RESPONSE_CACHE_FILE = `${ON_DISK_ROOT}/responseCache.json`; const ON_DISK_RESPONSE_CACHE_FILE = `${ON_DISK_ROOT}/responseCache.json`;
@ -11,14 +12,12 @@ export class DataCache {
private initialized = false; private initialized = false;
private readonly responseCache = new Map<string, any>(); private readonly responseCache = new Map<string, any>();
private readonly invalidationMap = new Map<string, Set<string>>();
private scheduleSaveTimeout: NodeJS.Timeout | undefined; private scheduleSaveTimeout: NodeJS.Timeout | undefined;
constructor( constructor(
private readonly payload: PayloadSDK, private readonly payload: PayloadSDK,
private readonly uncachedPayload: PayloadSDK, private readonly uncachedPayload: PayloadSDK
private readonly onInvalidate: (urls: string[]) => Promise<void>
) {} ) {}
async init() { async init() {
@ -32,8 +31,8 @@ export class DataCache {
} }
private async precache() { private async precache() {
// Get all keys from CMS // Get all documents from CMS
const allSDKUrls = (await this.uncachedPayload.getAllSdkUrls()).data.urls; const allDocs = (await this.uncachedPayload.getAll()).data;
// Load cache from disk if available // Load cache from disk if available
if (existsSync(ON_DISK_RESPONSE_CACHE_FILE)) { if (existsSync(ON_DISK_RESPONSE_CACHE_FILE)) {
@ -42,20 +41,20 @@ export class DataCache {
const data = JSON.parse(buffer.toString()) as [string, any][]; const data = JSON.parse(buffer.toString()) as [string, any][];
for (const [key, value] of data) { for (const [key, value] of data) {
// Do not include cache where the key is no longer in the CMS // Do not include cache where the key is no longer in the CMS
if (!allSDKUrls.includes(key)) continue; if (!allDocs.find(({ url }) => url === key)) continue;
this.set(key, value); this.set(key, value);
} }
} }
const cacheSizeBeforePrecaching = this.responseCache.size; const cacheSizeBeforePrecaching = this.responseCache.size;
for (const url of allSDKUrls) { for (const doc of allDocs) {
// Do not precache response if already included in the loaded cache from disk // Do not precache response if already included in the loaded cache from disk
if (this.responseCache.has(url)) continue; if (this.responseCache.has(doc.url)) continue;
try { try {
await this.payload.request(url); await this.payload.request(doc.url);
} catch { } catch {
this.logger.warn("Precaching failed for url", url); this.logger.warn("Precaching failed for url", doc.url);
} }
} }
@ -77,20 +76,6 @@ export class DataCache {
set(url: string, response: any) { set(url: string, response: any) {
if (import.meta.env.DATA_CACHING !== "true") return; if (import.meta.env.DATA_CACHING !== "true") return;
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.invalidationMap.get(id);
if (current) {
current.add(url);
} else {
this.invalidationMap.set(id, new Set([url]));
}
});
this.responseCache.set(url, response); this.responseCache.set(url, response);
this.logger.log("Cached response for", url); this.logger.log("Cached response for", url);
if (this.initialized) { if (this.initialized) {
@ -98,18 +83,11 @@ export class DataCache {
} }
} }
async invalidate(ids: string[], urls: string[]) { async invalidate(changes: EndpointChange[]) {
if (import.meta.env.DATA_CACHING !== "true") return; if (import.meta.env.DATA_CACHING !== "true") return;
const urlsToInvalidate = new Set<string>(urls);
ids.forEach((id) => { const urls = changes.map(({ url }) => url);
const urlsForThisId = this.invalidationMap.get(id); for (const url of urls) {
if (!urlsForThisId) return;
this.invalidationMap.delete(id);
[...urlsForThisId].forEach((url) => urlsToInvalidate.add(url));
});
for (const url of urlsToInvalidate) {
this.responseCache.delete(url); this.responseCache.delete(url);
this.logger.log("Invalidated cache for", url); this.logger.log("Invalidated cache for", url);
try { try {
@ -118,8 +96,6 @@ export class DataCache {
this.logger.log("Revalidation fails for", url); this.logger.log("Revalidation fails for", url);
} }
} }
this.onInvalidate([...urlsToInvalidate]);
this.logger.log("There are currently", this.responseCache.size, "responses in cache."); this.logger.log("There are currently", this.responseCache.size, "responses in cache.");
if (this.initialized) { if (this.initialized) {
this.scheduleSave(); this.scheduleSave();

143
src/cache/pageCache.ts vendored
View File

@ -6,22 +6,25 @@ import {
serializeResponse, serializeResponse,
type SerializableResponse, type SerializableResponse,
} from "src/utils/responses"; } from "src/utils/responses";
import type { PayloadSDK } from "src/shared/payload/sdk"; import { SDKEndpointNames, type PayloadSDK } from "src/shared/payload/sdk";
import type { EndpointChange } from "src/shared/payload/webhooks";
import type { ContextCache } from "src/cache/contextCache";
const ON_DISK_ROOT = `.cache/pageCache`; const ON_DISK_ROOT = `.cache/pageCache`;
const ON_DISK_RESPONSE_CACHE_FILE = `${ON_DISK_ROOT}/responseCache.json`; const ON_DISK_RESPONSE_CACHE_FILE = `${ON_DISK_ROOT}/responseCache.json`;
const ON_DISK_INVALIDATION_MAP_FILE = `${ON_DISK_ROOT}/invalidationMap.json`;
export class PageCache { export class PageCache {
private readonly logger = getLogger("[PageCache]"); private readonly logger = getLogger("[PageCache]");
private initialized = false; private initialized = false;
private responseCache = new Map<string, Response>(); private responseCache = new Map<string, Response>();
private invalidationMap = new Map<string, Set<string>>();
private scheduleSaveTimeout: NodeJS.Timeout | undefined; private scheduleSaveTimeout: NodeJS.Timeout | undefined;
constructor(private readonly uncachedPayload: PayloadSDK) {} constructor(
private readonly uncachedPayload: PayloadSDK,
private readonly contextCache: ContextCache
) {}
async init() { async init() {
if (this.initialized) return; if (this.initialized) return;
@ -35,31 +38,16 @@ export class PageCache {
private async precache() { private async precache() {
if (import.meta.env.DATA_CACHING !== "true") return; if (import.meta.env.DATA_CACHING !== "true") return;
const { data: languages } = await this.uncachedPayload.getLanguages();
const locales = languages.filter(({ selectable }) => selectable).map(({ id }) => id);
// Get all pages urls from CMS // Get all pages urls from CMS
const allIds = (await this.uncachedPayload.getAllIds()).data; const allDocs = (await this.uncachedPayload.getAll()).data;
const allPageUrls = allDocs.flatMap((doc) => this.getUrlFromEndpointChange(doc));
const allPagesUrls = [ // TODO: Add static pages likes "/" and "/settings"
"/",
...allIds.audios.ids.map((id) => `/audios/${id}`),
...allIds.collectibles.slugs.map((slug) => `/collectibles/${slug}`),
...allIds.files.ids.map((id) => `/files/${id}`),
...allIds.folders.slugs.map((slug) => `/folders/${slug}`),
...allIds.images.ids.map((id) => `/images/${id}`),
...allIds.pages.slugs.map((slug) => `/pages/${slug}`),
...allIds.recorders.ids.map((id) => `/recorders/${id}`),
"/settings",
"/timeline",
...allIds.videos.ids.map((id) => `/videos/${id}`),
].flatMap((url) => locales.map((id) => `/${id}${url}`));
// Load cache from disk if available // Load cache from disk if available
if (existsSync(ON_DISK_RESPONSE_CACHE_FILE) && existsSync(ON_DISK_INVALIDATION_MAP_FILE)) { if (existsSync(ON_DISK_RESPONSE_CACHE_FILE)) {
this.logger.log("Loading cache from disk..."); this.logger.log("Loading cache from disk...");
// Handle RESPONSE_CACHE_FILE
{
const buffer = await readFile(ON_DISK_RESPONSE_CACHE_FILE); const buffer = await readFile(ON_DISK_RESPONSE_CACHE_FILE);
const data = JSON.parse(buffer.toString()) as [string, SerializableResponse][]; const data = JSON.parse(buffer.toString()) as [string, SerializableResponse][];
let deserializedData = data.map<[string, Response]>(([key, value]) => [ let deserializedData = data.map<[string, Response]>(([key, value]) => [
@ -68,26 +56,14 @@ export class PageCache {
]); ]);
// Do not include cache where the key is no longer in the CMS // Do not include cache where the key is no longer in the CMS
deserializedData = deserializedData.filter(([key]) => allPagesUrls.includes(key)); deserializedData = deserializedData.filter(([key]) => allPageUrls.includes(key));
this.responseCache = new Map(deserializedData); this.responseCache = new Map(deserializedData);
} }
// Handle INVALIDATION_MAP_FILE
{
const buffer = await readFile(ON_DISK_INVALIDATION_MAP_FILE);
const data = JSON.parse(buffer.toString()) as [string, string[]][];
const deserialize = data.map<[string, Set<string>]>(([key, value]) => [
key,
new Set(value),
]);
this.invalidationMap = new Map(deserialize);
}
}
const cacheSizeBeforePrecaching = this.responseCache.size; const cacheSizeBeforePrecaching = this.responseCache.size;
for (const url of allPagesUrls) { for (const url of allPageUrls) {
// Do not precache response if already included in the loaded cache from disk // Do not precache response if already included in the loaded cache from disk
if (this.responseCache.has(url)) continue; if (this.responseCache.has(url)) continue;
try { try {
@ -114,17 +90,8 @@ export class PageCache {
return; return;
} }
set(url: string, response: Response, sdkCalls: string[]) { set(url: string, response: Response) {
if (import.meta.env.PAGE_CACHING !== "true") return; if (import.meta.env.PAGE_CACHING !== "true") return;
sdkCalls.forEach((id) => {
const current = this.invalidationMap.get(id);
if (current) {
current.add(url);
} else {
this.invalidationMap.set(id, new Set([url]));
}
});
this.responseCache.set(url, response.clone()); this.responseCache.set(url, response.clone());
this.logger.log("Cached response for", url); this.logger.log("Cached response for", url);
if (this.initialized) { if (this.initialized) {
@ -132,16 +99,70 @@ export class PageCache {
} }
} }
async invalidate(sdkUrls: string[]) { private getUrlFromEndpointChange(change: EndpointChange): string[] {
if (import.meta.env.PAGE_CACHING !== "true") return; const getUnlocalizedUrl = (): string[] => {
const pagesToInvalidate = new Set<string>(); switch (change.type) {
case SDKEndpointNames.getFolder:
return [`/folders/${change.slug}`];
sdkUrls.forEach((url) => { case SDKEndpointNames.getCollectible:
const pagesForThisSDKUrl = this.invalidationMap.get(url); return [`/collectibles/${change.slug}`];
if (!pagesForThisSDKUrl) return;
this.invalidationMap.delete(url); case SDKEndpointNames.getCollectibleGallery:
[...pagesForThisSDKUrl].forEach((page) => pagesToInvalidate.add(page)); return [`/collectibles/${change.slug}/gallery`];
});
case SDKEndpointNames.getCollectibleGalleryImage:
return [`/collectibles/${change.slug}/gallery/${change.index}`];
case SDKEndpointNames.getCollectibleScans:
return [`/collectibles/${change.slug}/scans`];
case SDKEndpointNames.getCollectibleScanPage:
return [`/collectibles/${change.slug}/scans/${change.index}`];
case SDKEndpointNames.getPage:
return [`/pages/${change.slug}`];
case SDKEndpointNames.getAudioByID:
return [`/audios/${change.id}`];
case SDKEndpointNames.getImageByID:
return [`/images/${change.id}`];
case SDKEndpointNames.getVideoByID:
return [`/videos/${change.id}`];
case SDKEndpointNames.getFileByID:
return [`/files/${change.id}`];
case SDKEndpointNames.getRecorderByID:
return [`/recorders/${change.id}`];
case SDKEndpointNames.getChronologyEvents:
case SDKEndpointNames.getChronologyEventByID:
return [`/timeline`];
case SDKEndpointNames.getWebsiteConfig:
case SDKEndpointNames.getLanguages:
case SDKEndpointNames.getCurrencies:
case SDKEndpointNames.getWordings:
return [...this.responseCache.keys()];
default:
return [];
}
};
return getUnlocalizedUrl().flatMap((url) =>
this.contextCache.locales.map((id) => `/${id}${url}`)
);
}
async invalidate(changes: EndpointChange[]) {
if (import.meta.env.PAGE_CACHING !== "true") return;
const pagesToInvalidate = new Set<string>(
changes.flatMap((change) => this.getUrlFromEndpointChange(change))
);
for (const url of pagesToInvalidate) { for (const url of pagesToInvalidate) {
this.responseCache.delete(url); this.responseCache.delete(url);
@ -181,13 +202,5 @@ export class PageCache {
encoding: "utf-8", encoding: "utf-8",
}); });
this.logger.log("Saved", ON_DISK_RESPONSE_CACHE_FILE); this.logger.log("Saved", ON_DISK_RESPONSE_CACHE_FILE);
const serializedIdsCache = JSON.stringify(
[...this.invalidationMap].map(([key, value]) => [key, [...value]])
);
await writeFile(ON_DISK_INVALIDATION_MAP_FILE, serializedIdsCache, {
encoding: "utf-8",
});
this.logger.log("Saved", ON_DISK_INVALIDATION_MAP_FILE);
} }
} }

View File

@ -4,12 +4,11 @@ import Topbar from "./components/Topbar/Topbar.astro";
import Footer from "./components/Footer.astro"; import Footer from "./components/Footer.astro";
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro"; import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
import type { ComponentProps } from "astro/types"; import type { ComponentProps } from "astro/types";
import type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
import { getSDKEndpoint } from "src/shared/payload/sdk";
interface Props { interface Props {
openGraph?: ComponentProps<typeof Html>["openGraph"]; openGraph?: ComponentProps<typeof Html>["openGraph"];
parentPages?: EndpointSource[]; backlinks?: EndpointRelation[];
backgroundImage?: ComponentProps<typeof AppLayoutBackgroundImg>["img"] | undefined; backgroundImage?: ComponentProps<typeof AppLayoutBackgroundImg>["img"] | undefined;
hideFooterLinks?: boolean; hideFooterLinks?: boolean;
hideHomeButton?: boolean; hideHomeButton?: boolean;
@ -17,13 +16,9 @@ interface Props {
class?: string | undefined; class?: string | undefined;
} }
Astro.locals.sdkCalls.add(getSDKEndpoint.getCurrenciesEndpoint());
Astro.locals.sdkCalls.add(getSDKEndpoint.getLanguagesEndpoint());
Astro.locals.sdkCalls.add(getSDKEndpoint.getWordingsEndpoint());
const { const {
openGraph, openGraph,
parentPages, backlinks,
backgroundImage, backgroundImage,
hideFooterLinks = false, hideFooterLinks = false,
hideHomeButton = false, hideHomeButton = false,
@ -38,7 +33,7 @@ const {
{backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />} {backgroundImage && <AppLayoutBackgroundImg img={backgroundImage} />}
<header> <header>
<Topbar <Topbar
parentPages={parentPages} backlinks={backlinks}
hideHomeButton={hideHomeButton} hideHomeButton={hideHomeButton}
hideSearchButton={hideSearchButton} hideSearchButton={hideSearchButton}
/> />

View File

@ -6,15 +6,15 @@ import LanguageSelector from "./components/LanguageSelector.astro";
import CurrencySelector from "./components/CurrencySelector.astro"; import CurrencySelector from "./components/CurrencySelector.astro";
import ParentPagesButton from "./components/ParentPagesButton.astro"; import ParentPagesButton from "./components/ParentPagesButton.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
interface Props { interface Props {
parentPages?: EndpointSource[] | undefined; backlinks?: EndpointRelation[] | undefined;
hideHomeButton?: boolean; hideHomeButton?: boolean;
hideSearchButton?: boolean; hideSearchButton?: boolean;
} }
const { parentPages = [], hideHomeButton = false, hideSearchButton = false } = Astro.props; const { backlinks = [], hideHomeButton = false, hideSearchButton = false } = Astro.props;
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale); const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
--- ---
@ -23,14 +23,14 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
<nav id="topbar" class="when-no-print"> <nav id="topbar" class="when-no-print">
{ {
(!hideHomeButton || parentPages.length > 0) && ( (!hideHomeButton || backlinks.length > 0) && (
<div id="left" class="hide-scrollbar"> <div id="left" class="hide-scrollbar">
<a href={getLocalizedUrl("/")} class="pressable-label"> <a href={getLocalizedUrl("/")} class="pressable-label">
<Icon name="material-symbols:home" width={16} height={16} /> <Icon name="material-symbols:home" width={16} height={16} />
<p>{t("home.title")}</p> <p>{t("home.title")}</p>
</a> </a>
{parentPages.length > 0 && <ParentPagesButton parentPages={parentPages} />} {backlinks.length > 0 && <ParentPagesButton backlinks={backlinks} />}
</div> </div>
) )
} }

View File

@ -3,14 +3,14 @@ import Tooltip from "components/Tooltip.astro";
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import ReturnToButton from "./ReturnToButton.astro"; import ReturnToButton from "./ReturnToButton.astro";
import SourceRow from "components/SourceRow.astro"; import RelationRow from "components/RelationRow.astro";
import type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
interface Props { interface Props {
parentPages: EndpointSource[]; backlinks: EndpointRelation[];
} }
const { parentPages } = Astro.props; const { backlinks } = Astro.props;
const { t } = await getI18n(Astro.locals.currentLocale); const { t } = await getI18n(Astro.locals.currentLocale);
--- ---
@ -18,15 +18,15 @@ const { t } = await getI18n(Astro.locals.currentLocale);
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
{ {
parentPages.length === 1 && parentPages[0] ? ( backlinks.length === 1 && backlinks[0] ? (
<ReturnToButton parentPage={parentPages[0]} /> <ReturnToButton relation={backlinks[0]} />
) : ( ) : (
<Tooltip trigger="click" class="when-js"> <Tooltip trigger="click" class="when-js">
<div id="tooltip-content" slot="tooltip-content"> <div id="tooltip-content" slot="tooltip-content">
<p>{t("header.nav.parentPages.tooltip")}</p> <p>{t("header.nav.parentPages.tooltip")}</p>
<div> <div>
{parentPages.map((parentPage) => ( {backlinks.map((relation) => (
<SourceRow source={parentPage} /> <RelationRow relation={relation} />
))} ))}
</div> </div>
</div> </div>
@ -34,7 +34,7 @@ const { t } = await getI18n(Astro.locals.currentLocale);
<Icon name="material-symbols:keyboard-return" /> <Icon name="material-symbols:keyboard-return" />
<p> <p>
{t("header.nav.parentPages.label", { {t("header.nav.parentPages.label", {
count: parentPages.length, count: backlinks.length,
})} })}
</p> </p>
</button> </button>

View File

@ -1,14 +1,14 @@
--- ---
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
interface Props { interface Props {
parentPage: EndpointSource; relation: EndpointRelation;
} }
const { parentPage } = Astro.props; const { relation } = Astro.props;
const { formatEndpointSource } = await getI18n(Astro.locals.currentLocale); const { formatEndpointRelation } = await getI18n(Astro.locals.currentLocale);
const { const {
href, href,
@ -17,7 +17,7 @@ const {
target = undefined, target = undefined,
rel = undefined, rel = undefined,
lang, lang,
} = formatEndpointSource(parentPage); } = formatEndpointRelation(relation);
--- ---
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}

View File

@ -1,6 +1,7 @@
--- ---
import GenericPreview from "components/Previews/GenericPreview.astro"; import GenericPreview from "components/Previews/GenericPreview.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import { Collections } from "src/shared/payload/constants";
import type { EndpointFolder } from "src/shared/payload/endpoint-types"; import type { EndpointFolder } from "src/shared/payload/endpoint-types";
import type { Attribute } from "src/utils/attributes"; import type { Attribute } from "src/utils/attributes";
@ -11,7 +12,7 @@ interface Props {
const { getLocalizedUrl, getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl, getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
const { const {
folder: { translations, slug, files, sections, parentPages }, folder: { translations, slug, files, sections, backlinks },
} = Astro.props; } = Astro.props;
const { language, title } = getLocalizedMatch(translations); const { language, title } = getLocalizedMatch(translations);
@ -32,9 +33,9 @@ const attributes: Attribute[] = [
{ {
icon: "material-symbols:keyboard-return", icon: "material-symbols:keyboard-return",
title: t("global.folders.attributes.parent"), title: t("global.folders.attributes.parent"),
values: parentPages.flatMap((parent) => { values: backlinks.flatMap((link) => {
if (parent.type !== "folder") return []; if (link.type !== Collections.Folders) return [];
const name = getLocalizedMatch(parent.folder.translations).title; const name = getLocalizedMatch(link.value.translations).title;
return { name }; return { name };
}), }),
}, },

View File

@ -1,13 +1,13 @@
--- ---
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
interface Props { interface Props {
source: EndpointSource; relation: EndpointRelation;
} }
const { source } = Astro.props; const { relation } = Astro.props;
const { formatEndpointSource } = await getI18n(Astro.locals.currentLocale); const { formatEndpointRelation } = await getI18n(Astro.locals.currentLocale);
const { const {
href, href,
@ -16,7 +16,7 @@ const {
target = undefined, target = undefined,
rel = undefined, rel = undefined,
lang, lang,
} = formatEndpointSource(source); } = formatEndpointRelation(relation);
--- ---
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}

1
src/env.d.ts vendored
View File

@ -4,7 +4,6 @@
declare namespace App { declare namespace App {
interface Locals { interface Locals {
currentLocale: string; currentLocale: string;
sdkCalls: Set<string>;
pageCaching: boolean; pageCaching: boolean;
} }
} }

View File

@ -1,7 +1,8 @@
import type { WordingKey } from "src/i18n/wordings-keys"; import type { WordingKey } from "src/i18n/wordings-keys";
import { contextCache } from "src/services"; import { contextCache } from "src/services";
import { capitalize, formatInlineTitle } from "src/utils/format"; import { capitalize, formatInlineTitle } from "src/utils/format";
import type { EndpointChronologyEvent, EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointChronologyEvent, EndpointRelation } from "src/shared/payload/endpoint-types";
import { Collections } from "src/shared/payload/constants";
export const defaultLocale = "en"; export const defaultLocale = "en";
@ -236,8 +237,8 @@ export const getI18n = async (locale: string) => {
} }
}; };
const formatEndpointSource = ( const formatEndpointRelation = (
source: EndpointSource relation: EndpointRelation
): { ): {
href: string; href: string;
typeLabel: string; typeLabel: string;
@ -246,98 +247,84 @@ export const getI18n = async (locale: string) => {
target?: string; target?: string;
rel?: string; rel?: string;
} => { } => {
switch (source.type) { switch (relation.type) {
case "url": { case "url":
return { return {
href: source.url, href: relation.url,
typeLabel: t("global.sources.typeLabel.url"), typeLabel: t("global.sources.typeLabel.url"),
label: source.label, label: relation.label,
target: "_blank", target: "_blank",
rel: "noopener noreferrer", rel: "noopener noreferrer",
}; };
}
case "collectible": { case Collections.Collectibles: {
const rangeLabel = (() => { const getRangeLabel = () => {
switch (source.range?.type) { switch (relation.range?.type) {
case "timestamp": case "timestamp":
return t("global.sources.typeLabel.collectible.range.timestamp", { return t("global.sources.typeLabel.collectible.range.timestamp", {
page: source.range.timestamp, page: relation.range.timestamp,
}); });
case "page": case "page":
return t("global.sources.typeLabel.collectible.range.page", { return t("global.sources.typeLabel.collectible.range.page", {
page: source.range.page, page: relation.range.page,
}); });
case "custom": case "custom":
return t("global.sources.typeLabel.collectible.range.custom", { return t("global.sources.typeLabel.collectible.range.custom", {
note: getLocalizedMatch(source.range.translations).note, note: getLocalizedMatch(relation.range.translations).note,
}); });
case undefined: case undefined:
default: default:
return ""; return "";
} }
})(); };
const translation = getLocalizedMatch(source.collectible.translations); const translation = getLocalizedMatch(relation.value.translations);
return { return {
href: getLocalizedUrl(`/collectibles/${source.collectible.slug}`), href: getLocalizedUrl(`/collectibles/${relation.value.slug}`),
typeLabel: t("global.sources.typeLabel.collectible"), typeLabel: t("global.sources.typeLabel.collectible"),
label: formatInlineTitle(translation) + rangeLabel, label: formatInlineTitle(translation) + getRangeLabel(),
lang: translation.language, lang: translation.language,
}; };
} }
case "page": { case Collections.Pages: {
const translation = getLocalizedMatch(source.page.translations); const translation = getLocalizedMatch(relation.value.translations);
return { return {
href: getLocalizedUrl(`/pages/${source.page.slug}`), href: getLocalizedUrl(`/pages/${relation.value.slug}`),
typeLabel: t("global.sources.typeLabel.page"), typeLabel: t("global.sources.typeLabel.page"),
label: formatInlineTitle(translation), label: formatInlineTitle(translation),
lang: translation.language, lang: translation.language,
}; };
} }
case "folder": { case Collections.Folders: {
const translation = getLocalizedMatch(source.folder.translations); const translation = getLocalizedMatch(relation.value.translations);
return { return {
href: getLocalizedUrl(`/folders/${source.folder.slug}`), href: getLocalizedUrl(`/folders/${relation.value.slug}`),
typeLabel: t("global.sources.typeLabel.folder"), typeLabel: t("global.sources.typeLabel.folder"),
label: formatInlineTitle(translation), label: formatInlineTitle(translation),
lang: translation.language, lang: translation.language,
}; };
} }
case "scans": { /* TODO: Handle other types of relations */
const translation = getLocalizedMatch(source.collectible.translations); case Collections.Audios:
return { case Collections.ChronologyEvents:
href: getLocalizedUrl(`/collectibles/${source.collectible.slug}/scans`), case Collections.Files:
typeLabel: t("global.sources.typeLabel.scans"), case Collections.Images:
label: formatInlineTitle(translation), case Collections.Recorders:
lang: translation.language, case Collections.Tags:
}; case Collections.Videos:
} default:
case "gallery": {
const translation = getLocalizedMatch(source.collectible.translations);
return {
href: getLocalizedUrl(`/collectibles/${source.collectible.slug}/gallery`),
typeLabel: t("global.sources.typeLabel.gallery"),
label: formatInlineTitle(translation),
lang: translation.language,
};
}
default: {
return { return {
href: "/404", href: "/404",
label: `Invalid type ${source["type"]}`, label: `Invalid type ${relation["type"]}`,
typeLabel: "Error", typeLabel: "Error",
}; };
} }
}
}; };
return { return {
@ -353,7 +340,7 @@ export const getI18n = async (locale: string) => {
formatMillimeters, formatMillimeters,
formatNumber, formatNumber,
formatTimelineDate, formatTimelineDate,
formatEndpointSource, formatEndpointRelation,
formatScanIndexShort, formatScanIndexShort,
formatFilesize, formatFilesize,
}; };

View File

@ -26,7 +26,7 @@ export const pageCachingMiddleware = defineMiddleware(async ({ url, request, loc
response.headers.set("Last-Modified", new Date().toUTCString()); response.headers.set("Last-Modified", new Date().toUTCString());
if (locals.pageCaching) { if (locals.pageCaching) {
pageCache.set(pathname, response, [...locals.sdkCalls]); pageCache.set(pathname, response);
} }
} }

View File

@ -3,7 +3,6 @@ import { getCurrentLocale } from "src/middleware/utils";
export const setAstroLocalsMiddleware = defineMiddleware(async ({ url, locals }, next) => { export const setAstroLocalsMiddleware = defineMiddleware(async ({ url, locals }, next) => {
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en"; locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
locals.sdkCalls = new Set();
locals.pageCaching = true; locals.pageCaching = true;
return next(); return next();
}); });

View File

@ -1,7 +1,6 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { Collections } from "src/shared/payload/constants"; import { contextCache, dataCache, pageCache } from "src/services.ts";
import type { AfterOperationWebHookMessage } from "src/shared/payload/webhooks"; import type { EndpointChange } from "src/shared/payload/webhooks";
import { contextCache, dataCache } from "src/services.ts";
export const POST: APIRoute = async ({ request, locals }) => { export const POST: APIRoute = async ({ request, locals }) => {
locals.pageCaching = false; locals.pageCaching = false;
@ -11,38 +10,17 @@ export const POST: APIRoute = async ({ request, locals }) => {
return new Response(null, { status: 403, statusText: "Forbidden" }); return new Response(null, { status: 403, statusText: "Forbidden" });
} }
const message = (await request.json()) as AfterOperationWebHookMessage; const changes = (await request.json()) as EndpointChange[];
console.log("[Webhook] Received messages from CMS:", message); console.log("[Webhook] Received messages from CMS:", changes);
// Not awaiting on purpose to respond with a 202 and not block the CMS // Not awaiting on purpose to respond with a 202 and not block the CMS
handleWebHookMessage(message); handleWebHookMessage(changes);
return new Response(null, { status: 202, statusText: "Accepted" }); return new Response(null, { status: 202, statusText: "Accepted" });
}; };
const handleWebHookMessage = async ({ const handleWebHookMessage = async (changes: EndpointChange[]) => {
addedDependantIds, await dataCache.invalidate(changes);
collection, await contextCache.invalidate(changes);
urls, await pageCache.invalidate(changes);
id,
}: AfterOperationWebHookMessage) => {
await dataCache.invalidate([...(id ? [id] : []), ...addedDependantIds], urls);
switch (collection) {
case Collections.Wordings:
await contextCache.refreshWordings();
break;
case Collections.Currencies:
await contextCache.refreshCurrencies();
break;
case Collections.Languages:
await contextCache.refreshLocales();
break;
case Collections.WebsiteConfig:
await contextCache.refreshWebsiteConfig();
break;
}
}; };

View File

@ -1,9 +1,10 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { dataCache, pageCache } from "src/services"; import { contextCache, dataCache, pageCache } from "src/services";
export const GET: APIRoute = async ({ locals }) => { export const GET: APIRoute = async ({ locals }) => {
locals.pageCaching = false; locals.pageCaching = false;
await contextCache.init();
await dataCache.init(); await dataCache.init();
await pageCache.init(); await pageCache.init();
return new Response(null, { status: 200, statusText: "Ok" }); return new Response(null, { status: 200, statusText: "Ok" });

View File

@ -32,7 +32,6 @@ const { translations, thumbnail, createdAt, updatedAt, updatedBy, attributes } =
await (async () => { await (async () => {
if (Astro.props.page) return Astro.props.page; if (Astro.props.page) return Astro.props.page;
const response = await payload.getPage(slug); const response = await payload.getPage(slug);
Astro.locals.sdkCalls.add(response.endpointCalled);
return response.data; return response.data;
})(); })();

View File

@ -29,7 +29,6 @@ const index = Astro.props.index ?? parseInt(reqUrl.searchParams.get("index")!);
const event = await (async () => { const event = await (async () => {
if (Astro.props.event) return Astro.props.event; if (Astro.props.event) return Astro.props.event;
const response = await payload.getChronologyEventByID(id); const response = await payload.getChronologyEventByID(id);
Astro.locals.sdkCalls.add(response.endpointCalled);
return response.data; return response.data;
})(); })();
const { sources, translations } = event.events[index]!; const { sources, translations } = event.events[index]!;

View File

@ -17,7 +17,6 @@ const response = await fetchOr404(() => payload.getAudioByID(id));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const audio = response.data; const audio = response.data;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n( const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
@ -34,6 +33,7 @@ const {
updatedAt, updatedAt,
thumbnail, thumbnail,
mimeType, mimeType,
backlinks,
} = audio; } = audio;
const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations); const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations);
@ -73,6 +73,7 @@ const metaAttributes = [
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout <AppLayout
backlinks={backlinks}
openGraph={{ openGraph={{
title: formatInlineTitle({ pretitle, title, subtitle }), title: formatInlineTitle({ pretitle, title, subtitle }),
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),

View File

@ -17,8 +17,7 @@ const response = await fetchOr404(() => payload.getCollectibleGalleryImage(slug,
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled); const { backlinks, previousIndex, nextIndex, image } = response.data;
const { parentPages, previousIndex, nextIndex, image } = response.data;
const { filename, translations, createdAt, updatedAt, credits, attributes, mimeType } = image; const { filename, translations, createdAt, updatedAt, credits, attributes, mimeType } = image;
@ -67,7 +66,7 @@ const metaAttributes = [
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),
thumbnail: image, thumbnail: image,
}} }}
parentPages={parentPages}> backlinks={backlinks}>
<Lightbox <Lightbox
image={image} image={image}
pretitle={pretitle} pretitle={pretitle}

View File

@ -15,8 +15,7 @@ const response = await fetchOr404(() => payload.getCollectibleGallery(slug));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled); const { translations, backlinks, images, thumbnail } = response.data;
const { translations, parentPages, images, thumbnail } = response.data;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
--- ---
@ -29,7 +28,7 @@ const translation = getLocalizedMatch(translations);
description: translation.description && formatRichTextToString(translation.description), description: translation.description && formatRichTextToString(translation.description),
thumbnail, thumbnail,
}} }}
parentPages={parentPages} backlinks={backlinks}
class="app"> class="app">
<AppLayoutTitle <AppLayoutTitle
title={translation.title} title={translation.title}

View File

@ -30,7 +30,6 @@ const response = await fetchOr404(() => payload.getCollectible(slug));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const collectible = response.data; const collectible = response.data;
const { const {
@ -47,7 +46,7 @@ const {
scans, scans,
subitems, subitems,
files, files,
parentPages, backlinks,
attributes, attributes,
contents, contents,
createdAt, createdAt,
@ -128,7 +127,7 @@ if (languages.length > 0) {
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),
thumbnail, thumbnail,
}} }}
parentPages={parentPages} backlinks={backlinks}
backgroundImage={backgroundImage ?? thumbnail}> backgroundImage={backgroundImage ?? thumbnail}>
<AsideLayout reducedAsideWidth> <AsideLayout reducedAsideWidth>
<Fragment slot="header"> <Fragment slot="header">

View File

@ -16,8 +16,7 @@ const response = await fetchOr404(() => payload.getCollectibleScanPage(slug, ind
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled); const { backlinks, previousIndex, nextIndex, image, translations } = response.data;
const { parentPages, previousIndex, nextIndex, image, translations } = response.data;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
--- ---
@ -29,7 +28,7 @@ const translation = getLocalizedMatch(translations);
title: `${formatInlineTitle(translation)} (${index})`, title: `${formatInlineTitle(translation)} (${index})`,
description: translation.description && formatRichTextToString(translation.description), description: translation.description && formatRichTextToString(translation.description),
}} }}
parentPages={parentPages}> backlinks={backlinks}>
<Lightbox <Lightbox
image={image} image={image}
title={formatScanIndexShort(index)} title={formatScanIndexShort(index)}

View File

@ -16,8 +16,7 @@ const response = await fetchOr404(() => payload.getCollectibleScans(slug));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled); const { translations, credits, cover, pages, dustjacket, obi, backlinks, thumbnail } =
const { translations, credits, cover, pages, dustjacket, obi, parentPages, thumbnail } =
response.data; response.data;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
@ -46,7 +45,7 @@ const hasOutsideObi = obi ? Object.keys(obi).some((value) => !value.includes("in
description: translation.description && formatRichTextToString(translation.description), description: translation.description && formatRichTextToString(translation.description),
thumbnail, thumbnail,
}} }}
parentPages={parentPages} backlinks={backlinks}
class="app"> class="app">
<AppLayoutTitle <AppLayoutTitle
title={translation.title} title={translation.title}

View File

@ -18,7 +18,6 @@ const response = await fetchOr404(() => payload.getFileByID(id));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { const {
translations, translations,
attributes, attributes,
@ -30,6 +29,7 @@ const {
updatedAt, updatedAt,
createdAt, createdAt,
thumbnail, thumbnail,
backlinks,
} = response.data; } = response.data;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n( const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
@ -84,6 +84,7 @@ const smallTitle = title === filename;
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout <AppLayout
backlinks={backlinks}
openGraph={{ openGraph={{
title: formatInlineTitle({ pretitle, title, subtitle }), title: formatInlineTitle({ pretitle, title, subtitle }),
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),

View File

@ -22,8 +22,7 @@ const response = await fetchOr404(() => payload.getFolder(slug));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled); const { files, backlinks, sections, translations } = response.data;
const { files, parentPages, sections, translations } = response.data;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
const { language, title, description } = getLocalizedMatch(translations); const { language, title, description } = getLocalizedMatch(translations);
@ -36,7 +35,7 @@ const { language, title, description } = getLocalizedMatch(translations);
title: title, title: title,
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),
}} }}
parentPages={parentPages} backlinks={backlinks}
class="app"> class="app">
<AppLayoutTitle title={title} lang={language} /> <AppLayoutTitle title={title} lang={language} />
{description && <RichText content={description} context={{ lang: language }} />} {description && <RichText content={description} context={{ lang: language }} />}

View File

@ -12,7 +12,6 @@ const response = await fetchOr404(() => payload.getImageByID(id));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const image = response.data; const image = response.data;
const { const {
filename, filename,
@ -25,6 +24,7 @@ const {
filesize, filesize,
width, width,
height, height,
backlinks,
} = image; } = image;
const { getLocalizedMatch, formatDate, t, formatFilesize, formatNumber } = await getI18n( const { getLocalizedMatch, formatDate, t, formatFilesize, formatNumber } = await getI18n(
@ -83,6 +83,7 @@ const metaAttributes = [
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout <AppLayout
backlinks={backlinks}
openGraph={{ openGraph={{
title: formatInlineTitle({ pretitle, title, subtitle }), title: formatInlineTitle({ pretitle, title, subtitle }),
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),

View File

@ -12,9 +12,8 @@ const response = await fetchOr404(() => payload.getPage(slug));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const page = response.data; const page = response.data;
const { parentPages, thumbnail, translations, backgroundImage } = page; const { backlinks, thumbnail, translations, backgroundImage } = page;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
const meta = getLocalizedMatch(translations); const meta = getLocalizedMatch(translations);
@ -28,7 +27,7 @@ const meta = getLocalizedMatch(translations);
description: meta.summary && formatRichTextToString(meta.summary), description: meta.summary && formatRichTextToString(meta.summary),
thumbnail: thumbnail, thumbnail: thumbnail,
}} }}
parentPages={parentPages} backlinks={backlinks}
backgroundImage={backgroundImage ?? thumbnail}> backgroundImage={backgroundImage ?? thumbnail}>
<Page slug={slug} lang={Astro.locals.currentLocale} page={page} /> <Page slug={slug} lang={Astro.locals.currentLocale} page={page} />
</AppLayout> </AppLayout>

View File

@ -16,7 +16,6 @@ const response = await fetchOr404(() => payload.getRecorderByID(id));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { username, languages, avatar, translations } = response.data; const { username, languages, avatar, translations } = response.data;
const { t, getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale); const { t, getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);

View File

@ -1,12 +1,12 @@
--- ---
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import SourceRow from "components/SourceRow.astro"; import RelationRow from "components/RelationRow.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 type { EndpointSource } from "src/shared/payload/endpoint-types"; import type { EndpointRelation } from "src/shared/payload/endpoint-types";
interface Props { interface Props {
sources: EndpointSource[]; sources: EndpointRelation[];
} }
const { sources } = Astro.props; const { sources } = Astro.props;
@ -26,7 +26,7 @@ const { t } = await getI18n(Astro.locals.currentLocale);
<Tooltip trigger="click"> <Tooltip trigger="click">
<div id="tooltip-content" slot="tooltip-content"> <div id="tooltip-content" slot="tooltip-content">
{sources.map((source) => ( {sources.map((source) => (
<SourceRow source={source} /> <RelationRow relation={source} />
))} ))}
</div> </div>
<button class="pressable-label"> <button class="pressable-label">

View File

@ -14,7 +14,6 @@ const response = await fetchOr404(() => payload.getChronologyEvents());
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const events = response.data; const events = response.data;
const groupedEvents = groupBy(events, (event) => event.date.year); const groupedEvents = groupBy(events, (event) => event.date.year);

View File

@ -17,7 +17,6 @@ const response = await fetchOr404(() => payload.getVideoByID(id));
if (response instanceof Response) { if (response instanceof Response) {
return response; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const video = response.data; const video = response.data;
const { const {
translations, translations,
@ -30,6 +29,7 @@ const {
createdAt, createdAt,
thumbnail, thumbnail,
mimeType, mimeType,
backlinks,
} = video; } = video;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n( const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
@ -73,6 +73,7 @@ const metaAttributes = [
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout <AppLayout
backlinks={backlinks}
openGraph={{ openGraph={{
title: formatInlineTitle({ pretitle, title, subtitle }), title: formatInlineTitle({ pretitle, title, subtitle }),
description: description && formatRichTextToString(description), description: description && formatRichTextToString(description),

View File

@ -22,6 +22,13 @@ export const analytics = import.meta.env.ANALYTICS_URL
const tokenCache = new TokenCache(); const tokenCache = new TokenCache();
const uncachedPayload = new PayloadSDK(
import.meta.env.PAYLOAD_API_URL,
import.meta.env.PAYLOAD_USER,
import.meta.env.PAYLOAD_PASSWORD
);
uncachedPayload.addTokenCache(tokenCache);
export const payload = new PayloadSDK( export const payload = new PayloadSDK(
import.meta.env.PAYLOAD_API_URL, import.meta.env.PAYLOAD_API_URL,
import.meta.env.PAYLOAD_USER, import.meta.env.PAYLOAD_USER,
@ -29,19 +36,11 @@ export const payload = new PayloadSDK(
); );
payload.addTokenCache(tokenCache); payload.addTokenCache(tokenCache);
const uncachedPayload = new PayloadSDK(
import.meta.env.PAYLOAD_API_URL,
import.meta.env.PAYLOAD_USER,
import.meta.env.PAYLOAD_PASSWORD
);
uncachedPayload.addTokenCache(tokenCache);
export const pageCache = new PageCache(uncachedPayload);
// Loading context cache first so that the server can still serve responses while precaching. // Loading context cache first so that the server can still serve responses while precaching.
export const contextCache = new ContextCache(payload); export const contextCache = new ContextCache(payload);
await contextCache.init(); await contextCache.init();
export const dataCache = new DataCache(payload, uncachedPayload, (urls) => export const dataCache = new DataCache(payload, uncachedPayload);
pageCache.invalidate(urls)
);
payload.addDataCache(dataCache); payload.addDataCache(dataCache);
export const pageCache = new PageCache(uncachedPayload, contextCache);

@ -1 +1 @@
Subproject commit 47c990080173a2330f0c7a9837dac34dba4e0811 Subproject commit caa79dee9eca5b9b6959e6f5a721245202423612