Added page caching

This commit is contained in:
DrMint 2024-06-29 12:54:04 +02:00
parent de9ad38835
commit cbbf0e5e3b
43 changed files with 567 additions and 382 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "v3.accords-library.com", "name": "v3.accords-library.com",
"version": "3.0.0-beta.6", "version": "3.0.0-beta.7",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev", "start": "astro dev",

View File

@ -1,10 +1,16 @@
import { writeFileSync } from "fs"; import { writeFileSync } from "fs";
import { payload } from "src/utils/payload"; import { PayloadSDK } from "src/shared/payload/payload-sdk";
const TRANSLATION_FOLDER = `${process.cwd()}/src/i18n`; const TRANSLATION_FOLDER = `${process.cwd()}/src/i18n`;
const payload = new PayloadSDK(
import.meta.env.PAYLOAD_API_URL,
import.meta.env.PAYLOAD_USER,
import.meta.env.PAYLOAD_PASSWORD
);
try { try {
const wordings = await payload.getWordings(); const { data: wordings } = await payload.getWordings();
const keys = wordings.map(({ name }) => name); const keys = wordings.map(({ name }) => name);
let result = ""; let result = "";

View File

@ -2,14 +2,16 @@ import type {
EndpointWebsiteConfig, EndpointWebsiteConfig,
EndpointWording, EndpointWording,
Language, Language,
PayloadSDK,
} from "src/shared/payload/payload-sdk"; } from "src/shared/payload/payload-sdk";
import { getLogger } from "src/utils/logger"; import { getLogger } from "src/utils/logger";
import { payload } from "src/utils/payload";
class ContextCache { export class ContextCache {
private initialized = false; private initialized = false;
private logger = getLogger("[ContextCache]"); private logger = getLogger("[ContextCache]");
constructor(private readonly payload: PayloadSDK) {}
locales: Language[] = []; locales: Language[] = [];
currencies: string[] = []; currencies: string[] = [];
wordings: EndpointWording[] = []; wordings: EndpointWording[] = [];
@ -33,26 +35,22 @@ class ContextCache {
} }
async refreshWordings() { async refreshWordings() {
this.wordings = await payload.getWordings(); this.wordings = (await this.payload.getWordings()).data;
this.logger.log("Wordings refreshed"); this.logger.log("Wordings refreshed");
} }
async refreshCurrencies() { async refreshCurrencies() {
contextCache.currencies = (await payload.getCurrencies()).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() { async refreshLocales() {
contextCache.locales = await payload.getLanguages(); this.locales = (await this.payload.getLanguages()).data;
this.logger.log("Locales refreshed"); this.logger.log("Locales refreshed");
} }
async refreshWebsiteConfig() { async refreshWebsiteConfig() {
contextCache.config = await payload.getConfig(); this.config = (await this.payload.getConfig()).data;
this.logger.log("WebsiteConfig refreshed"); this.logger.log("WebsiteConfig refreshed");
} }
} }
const contextCache = new ContextCache();
await contextCache.init();
export { contextCache };

View File

@ -1,13 +1,18 @@
import type { PayloadSDK } from "src/shared/payload/payload-sdk";
import { getLogger } from "src/utils/logger"; import { getLogger } from "src/utils/logger";
import { payload } from "src/utils/payload";
class DataCache { export class DataCache {
private readonly logger = getLogger("[DataCache]"); private readonly logger = getLogger("[DataCache]");
private initialized = false; private initialized = false;
private readonly responseCache = new Map<string, any>(); private readonly responseCache = new Map<string, any>();
private readonly idsCacheMap = new Map<string, Set<string>>(); private readonly idsCacheMap = new Map<string, Set<string>>();
constructor(
private readonly payload: PayloadSDK,
private readonly onInvalidate: (urls: string[]) => Promise<void>
) {}
async init() { async init() {
if (this.initialized) return; if (this.initialized) return;
@ -19,10 +24,10 @@ class DataCache {
} }
private async precache() { private async precache() {
const { urls } = await payload.getAllSdkUrls(); const { data } = await this.payload.getAllSdkUrls();
for (const url of urls) { for (const url of data.urls) {
try { try {
await payload.request(url); await this.payload.request(url);
} catch { } catch {
this.logger.warn("Precaching failed for url", url); this.logger.warn("Precaching failed for url", url);
} }
@ -71,14 +76,13 @@ class DataCache {
this.responseCache.delete(url); this.responseCache.delete(url);
this.logger.log("Invalidated cache for", url); this.logger.log("Invalidated cache for", url);
try { try {
await payload.request(url); await this.payload.request(url);
} catch (e) { } catch (e) {
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.");
} }
} }
export const dataCache = new DataCache();

View File

@ -1,11 +1,66 @@
import type { PayloadSDK } from "src/shared/payload/payload-sdk";
import { getLogger } from "src/utils/logger"; import { getLogger } from "src/utils/logger";
class PageCache { export class PageCache {
private readonly logger = getLogger("[PageCache]"); private readonly logger = getLogger("[PageCache]");
private readonly cache = new Map<string, Response>(); private initialized = false;
private readonly responseCache = new Map<string, Response>();
private readonly invalidationMap = new Map<string, Set<string>>();
constructor(private readonly payload: PayloadSDK) {}
async init() {
if (this.initialized) return;
if (import.meta.env.ENABLE_PRECACHING === "true") {
await this.precacheAll();
}
this.initialized = true;
}
private async precacheAll() {
const { data: languages } = await this.payload.getLanguages();
const locales = languages.map(({ id }) => id);
await this.precache("/", locales);
await this.precache("/settings", locales);
await this.precache("/timeline", locales);
const { data: folders } = await this.payload.getFolderSlugs();
for (const slug of folders) {
await this.precache(`/folders/${slug}`, locales);
}
const { data: pages } = await this.payload.getPageSlugs();
for (const slug of pages) {
await this.precache(`/pages/${slug}`, locales);
}
const { data: collectibles } = await this.payload.getCollectibleSlugs();
for (const slug of collectibles) {
await this.precache(`/collectibles/${slug}`, locales);
}
this.logger.log("Precaching completed!", this.responseCache.size, "responses cached");
}
private async precache(pathname: string, locales: string[]) {
try {
await Promise.all(
locales.map((locale) => {
const url = `http://${import.meta.env.ASTRO_HOST}:${import.meta.env.ASTRO_PORT}/${locale}${pathname}`;
return fetch(url);
})
);
} catch (e) {
this.logger.warn("Precaching failed for page", pathname);
}
}
get(url: string): Response | undefined { get(url: string): Response | undefined {
const cachedPage = this.cache.get(url); const cachedPage = this.responseCache.get(url);
if (cachedPage) { if (cachedPage) {
this.logger.log("Retrieved cached page for", url); this.logger.log("Retrieved cached page for", url);
return cachedPage?.clone(); return cachedPage?.clone();
@ -13,10 +68,40 @@ class PageCache {
return; return;
} }
set(url: string, response: Response) { set(url: string, response: Response, sdkCalls: string[]) {
this.cache.set(url, response.clone()); 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.logger.log("Cached response for", url); this.logger.log("Cached response for", url);
} }
}
export const pageCache = new PageCache(); async invalidate(sdkUrls: string[]) {
const pagesToInvalidate = new Set<string>();
sdkUrls.forEach((url) => {
const pagesForThisSDKUrl = this.invalidationMap.get(url);
if (!pagesForThisSDKUrl) return;
this.invalidationMap.delete(url);
[...pagesForThisSDKUrl].forEach((page) => pagesToInvalidate.add(page));
});
for (const url of pagesToInvalidate) {
this.responseCache.delete(url);
this.logger.log("Invalidated cache for", url);
try {
await fetch(`http://${import.meta.env.ASTRO_HOST}:${import.meta.env.ASTRO_PORT}${url}`);
} catch (e) {
this.logger.log("Revalidation fails for", url);
}
}
this.logger.log("There are currently", this.responseCache.size, "responses in cache.");
}
}

20
src/cache/tokenCache.ts vendored Normal file
View File

@ -0,0 +1,20 @@
export class TokenCache {
private token: string | undefined;
private expiration: number | undefined;
get() {
if (!this.token) return undefined;
if (!this.expiration || this.expiration < Date.now()) {
console.log("[PayloadSDK] No token to be retrieved or the token expired");
return undefined;
}
return this.token;
}
set(newToken: string, newExpiration: number) {
this.token = newToken;
this.expiration = newExpiration * 1000;
const diffInMinutes = Math.floor((this.expiration - Date.now()) / 1000 / 60);
console.log("[PayloadSDK] New token set. TTL is", diffInMinutes, "minutes.");
}
}

View File

@ -2,7 +2,7 @@
import Html from "./components/Html.astro"; import Html from "./components/Html.astro";
import Topbar from "./components/Topbar/Topbar.astro"; import Topbar from "./components/Topbar/Topbar.astro";
import Footer from "./components/Footer.astro"; import Footer from "./components/Footer.astro";
import type { EndpointSource } from "src/shared/payload/payload-sdk"; import { getSDKEndpoint, type EndpointSource } from "src/shared/payload/payload-sdk";
import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro"; import AppLayoutBackgroundImg from "./components/AppLayoutBackgroundImg.astro";
import type { ComponentProps } from "astro/types"; import type { ComponentProps } from "astro/types";
@ -15,6 +15,10 @@ 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, parentPages,

View File

@ -7,7 +7,7 @@ import type {
EndpointPayloadImage, EndpointPayloadImage,
EndpointVideo, EndpointVideo,
} from "src/shared/payload/payload-sdk"; } from "src/shared/payload/payload-sdk";
import { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import { PostProcessingTags } from "src/middleware/postProcessing"; import { PostProcessingTags } from "src/middleware/postProcessing";
interface Props { interface Props {

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import { formatCurrency } from "src/utils/currencies"; import { formatCurrency } from "src/utils/currencies";
import { import {
PostProcessingTags, PostProcessingTags,

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import { formatLocale } from "src/utils/format"; import { formatLocale } from "src/utils/format";
interface Props { interface Props {

1
src/env.d.ts vendored
View File

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

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import { capitalize, formatInlineTitle } from "src/utils/format"; import { capitalize, formatInlineTitle } from "src/utils/format";
export const defaultLocale = "en"; export const defaultLocale = "en";

View File

@ -8,8 +8,6 @@ import { requestTrackingMiddleware } from "src/middleware/requestTracking";
import { pageCachingMiddleware } from "src/middleware/pageCaching"; import { pageCachingMiddleware } from "src/middleware/pageCaching";
import { setAstroLocalsMiddleware } from "src/middleware/setAstroLocals"; import { setAstroLocalsMiddleware } from "src/middleware/setAstroLocals";
export const onRequest = sequence( export const onRequest = sequence(
// Possible redirect // Possible redirect
actionsHandlingMiddleware, actionsHandlingMiddleware,

View File

@ -1,7 +1,9 @@
import { defineMiddleware } from "astro:middleware"; import { defineMiddleware } from "astro:middleware";
import { pageCache } from "src/cache/pageCache"; import { pageCache } from "src/utils/payload";
export const pageCachingMiddleware = defineMiddleware(async ({ url, request }, next) => { const blacklist = ["/en/api/hooks/collection-operation", "/en/api/on-startup"];
export const pageCachingMiddleware = defineMiddleware(async ({ url, request, locals }, next) => {
const pathname = url.pathname; const pathname = url.pathname;
const cachedPage = pageCache.get(pathname); const cachedPage = pageCache.get(pathname);
@ -20,7 +22,10 @@ export const pageCachingMiddleware = defineMiddleware(async ({ url, request }, n
if (response.ok) { if (response.ok) {
response.headers.set("Last-Modified", new Date().toUTCString()); response.headers.set("Last-Modified", new Date().toUTCString());
pageCache.set(pathname, response);
if (!blacklist.includes(pathname)) {
pageCache.set(pathname, response, [...locals.sdkCalls]);
}
} }
return response; return response;

View File

@ -53,8 +53,6 @@ export const postProcessingMiddleware = defineMiddleware(async ({ cookies, local
let html = await response.text(); let html = await response.text();
const t0 = performance.now();
// HTML CLASS // HTML CLASS
const currentTheme = getCookieTheme(cookies) ?? "auto"; const currentTheme = getCookieTheme(cookies) ?? "auto";
html = html.replace( html = html.replace(
@ -102,7 +100,5 @@ export const postProcessingMiddleware = defineMiddleware(async ({ cookies, local
} }
}); });
console.log("Post-processing:", performance.now() - t0, "ms");
return new Response(html, response); return new Response(html, response);
}); });

View File

@ -3,5 +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();
return next(); return next();
}); });

View File

@ -1,6 +1,6 @@
import type { AstroCookies } from "astro"; import type { AstroCookies } from "astro";
import { z } from "astro:content"; import { z } from "astro:content";
import { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import acceptLanguage from "accept-language"; import acceptLanguage from "accept-language";
export const getAbsoluteLocaleUrl = (locale: string, url: string) => `/${locale}${url}`; export const getAbsoluteLocaleUrl = (locale: string, url: string) => `/${locale}${url}`;
@ -26,7 +26,7 @@ export const getCurrentLocale = (pathname: string): string | undefined => {
export const getBestAcceptedLanguage = (request: Request): string | undefined => { export 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 || header === "*") return;
acceptLanguage.languages(contextCache.locales.map(({ id }) => id)); acceptLanguage.languages(contextCache.locales.map(({ id }) => id));

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
--- ---

View File

@ -1,6 +1,5 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { contextCache } from "src/cache/contextCache"; import { contextCache, dataCache } from "src/utils/payload.ts";
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";
export const POST: APIRoute = async ({ request }) => { export const POST: APIRoute = async ({ request }) => {

View File

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

View File

@ -24,8 +24,14 @@ interface Props {
const reqUrl = new URL(Astro.request.url); const reqUrl = new URL(Astro.request.url);
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!; const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!; const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!;
const { translations, thumbnail, createdAt, updatedAt, updatedBy, attributes } = const { translations, thumbnail, createdAt, updatedAt, updatedBy, attributes } =
Astro.props.page ?? (await payload.getPage(slug)); await (async () => {
if (Astro.props.page) return Astro.props.page;
const response = await payload.getPage(slug);
Astro.locals.sdkCalls.add(response.endpointCalled);
return response.data;
})();
const { getLocalizedUrl, t, formatDate } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl, t, formatDate } = await getI18n(Astro.locals.currentLocale);
const { getLocalizedMatch } = await getI18n(lang); const { getLocalizedMatch } = await getI18n(lang);

View File

@ -12,17 +12,22 @@ import { payload } from "src/utils/payload";
export const partial = true; export const partial = true;
interface Props { interface Props {
lang: string; lang?: string;
event: EndpointChronologyEvent; event?: EndpointChronologyEvent;
id: string; id?: string;
index: number; index?: number;
} }
const reqUrl = new URL(Astro.request.url); const reqUrl = new URL(Astro.request.url);
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!; const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
const id = Astro.props.id ?? reqUrl.searchParams.get("id")!; const id = Astro.props.id ?? reqUrl.searchParams.get("id")!;
const index = Astro.props.index ?? parseInt(reqUrl.searchParams.get("index")!); const index = Astro.props.index ?? parseInt(reqUrl.searchParams.get("index")!);
const event = Astro.props.event ?? (await payload.getChronologyEventByID(id)); const event = await (async () => {
if (Astro.props.event) return Astro.props.event;
const response = await payload.getChronologyEventByID(id);
Astro.locals.sdkCalls.add(response.endpointCalled);
return response.data;
})();
const { sources, translations } = event.events[index]!; const { sources, translations } = event.events[index]!;
const { getLocalizedUrl } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);

View File

@ -13,10 +13,12 @@ import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes"; import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!; const id = Astro.params.id!;
const audio = await fetchOr404(() => payload.getAudioByID(id)); const response = await fetchOr404(() => payload.getAudioByID(id));
if (audio instanceof Response) { if (response instanceof Response) {
return audio; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const audio = response.data;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n( const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
Astro.locals.currentLocale Astro.locals.currentLocale

View File

@ -13,12 +13,13 @@ const { getLocalizedUrl, getLocalizedMatch, t, formatDate } = await getI18n(
Astro.locals.currentLocale Astro.locals.currentLocale
); );
const galleryImage = await fetchOr404(() => payload.getCollectibleGalleryImage(slug, index)); const response = await fetchOr404(() => payload.getCollectibleGalleryImage(slug, index));
if (galleryImage instanceof Response) { if (response instanceof Response) {
return galleryImage; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { parentPages, previousIndex, nextIndex, image } = response.data;
const { parentPages, previousIndex, nextIndex, image } = galleryImage;
const { filename, translations, createdAt, updatedAt, credits, attributes, mimeType } = image; const { filename, translations, createdAt, updatedAt, credits, attributes, mimeType } = image;
const { pretitle, title, subtitle, description, language } = const { pretitle, title, subtitle, description, language } =

View File

@ -11,12 +11,13 @@ import RichText from "components/RichText/RichText.astro";
const slug = Astro.params.slug!; const slug = Astro.params.slug!;
const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale);
const gallery = await fetchOr404(() => payload.getCollectibleGallery(slug)); const response = await fetchOr404(() => payload.getCollectibleGallery(slug));
if (gallery instanceof Response) { if (response instanceof Response) {
return gallery; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { translations, parentPages, images, thumbnail } = response.data;
const { translations, parentPages, images, thumbnail } = gallery;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
--- ---

View File

@ -26,10 +26,12 @@ const { getLocalizedMatch, getLocalizedUrl, t, formatDate } = await getI18n(
Astro.locals.currentLocale Astro.locals.currentLocale
); );
const collectible = await fetchOr404(() => payload.getCollectible(slug)); const response = await fetchOr404(() => payload.getCollectible(slug));
if (collectible instanceof Response) { if (response instanceof Response) {
return collectible; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const collectible = response.data;
const { const {
translations, translations,

View File

@ -12,12 +12,13 @@ const { formatScanIndexShort, getLocalizedMatch, getLocalizedUrl } = await getI1
Astro.locals.currentLocale Astro.locals.currentLocale
); );
const scanPage = await fetchOr404(() => payload.getCollectibleScanPage(slug, index)); const response = await fetchOr404(() => payload.getCollectibleScanPage(slug, index));
if (scanPage instanceof Response) { if (response instanceof Response) {
return scanPage; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { parentPages, previousIndex, nextIndex, image, translations } = response.data;
const { parentPages, previousIndex, nextIndex, image, translations } = scanPage;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
--- ---

View File

@ -12,12 +12,13 @@ import RichText from "components/RichText/RichText.astro";
const slug = Astro.params.slug!; const slug = Astro.params.slug!;
const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
const scans = await fetchOr404(() => payload.getCollectibleScans(slug)); const response = await fetchOr404(() => payload.getCollectibleScans(slug));
if (scans instanceof Response) { if (response instanceof Response) {
return scans; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { translations, credits, cover, pages, dustjacket, obi, parentPages, thumbnail } = scans; const { translations, credits, cover, pages, dustjacket, obi, parentPages, thumbnail } =
response.data;
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);

View File

@ -14,14 +14,11 @@ import { Icon } from "astro-icon/components";
import { sizesToSrcset } from "src/utils/img"; import { sizesToSrcset } from "src/utils/img";
const id = Astro.params.id!; const id = Astro.params.id!;
const video = await fetchOr404(() => payload.getFileByID(id)); const response = await fetchOr404(() => payload.getFileByID(id));
if (video instanceof Response) { if (response instanceof Response) {
return video; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
Astro.locals.currentLocale
);
const { const {
translations, translations,
attributes, attributes,
@ -33,7 +30,11 @@ const {
updatedAt, updatedAt,
createdAt, createdAt,
thumbnail, thumbnail,
} = video; } = response.data;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
Astro.locals.currentLocale
);
const { pretitle, title, subtitle, description, language } = const { pretitle, title, subtitle, description, language } =
translations.length > 0 translations.length > 0

View File

@ -18,36 +18,38 @@ import FilePreview from "components/Previews/FilePreview.astro";
const slug = Astro.params.slug!; const slug = Astro.params.slug!;
const folder = await fetchOr404(() => payload.getFolder(slug)); const response = await fetchOr404(() => payload.getFolder(slug));
if (folder instanceof Response) { if (response instanceof Response) {
return folder; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { files, parentPages, sections, translations } = response.data;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
const meta = getLocalizedMatch(folder.translations); const { language, title, description } = getLocalizedMatch(translations);
--- ---
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout <AppLayout
openGraph={{ openGraph={{
title: meta.title, title: title,
description: meta.description && formatRichTextToString(meta.description), description: description && formatRichTextToString(description),
}} }}
parentPages={folder.parentPages} parentPages={parentPages}
class="app"> class="app">
<AppLayoutTitle title={meta.title} lang={meta.language} /> <AppLayoutTitle title={title} lang={language} />
{meta.description && <RichText content={meta.description} context={{ lang: meta.language }} />} {description && <RichText content={description} context={{ lang: language }} />}
<div id="main" class:list={{ complex: folder.sections.type === "multiple" }}> <div id="main" class:list={{ complex: sections.type === "multiple" }}>
{ {
folder.sections.type === "single" && folder.sections.subfolders.length > 0 ? ( sections.type === "single" && sections.subfolders.length > 0 ? (
<FoldersSection folders={folder.sections.subfolders} /> <FoldersSection folders={sections.subfolders} />
) : ( ) : (
folder.sections.type === "multiple" && sections.type === "multiple" &&
folder.sections.sections.length > 0 && ( sections.sections.length > 0 && (
<div id="sections"> <div id="sections">
{folder.sections.sections.map(({ subfolders, translations }) => { {sections.sections.map(({ subfolders, translations }) => {
const { language, name } = getLocalizedMatch(translations); const { language, name } = getLocalizedMatch(translations);
return <FoldersSection folders={subfolders} title={name} lang={language} />; return <FoldersSection folders={subfolders} title={name} lang={language} />;
})} })}
@ -58,7 +60,7 @@ const meta = getLocalizedMatch(folder.translations);
<div id="files"> <div id="files">
{ {
folder.files.map(({ relationTo, value }) => { files.map(({ relationTo, value }) => {
switch (relationTo) { switch (relationTo) {
case Collections.Collectibles: case Collections.Collectibles:
return <CollectiblePreview collectible={value} />; return <CollectiblePreview collectible={value} />;

View File

@ -8,13 +8,15 @@ import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes"; import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!; const id = Astro.params.id!;
const image = await fetchOr404(() => payload.getImageByID(id)); const response = await fetchOr404(() => payload.getImageByID(id));
if (image instanceof Response) { if (response instanceof Response) {
return image; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const image = response.data;
const { filename, translations, attributes, credits, createdAt, updatedAt, mimeType } = image;
const { getLocalizedMatch, formatDate, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, formatDate, t } = await getI18n(Astro.locals.currentLocale);
const { filename, translations, attributes, credits, createdAt, updatedAt, mimeType } = image;
const { pretitle, title, subtitle, description, language } = const { pretitle, title, subtitle, description, language } =
translations.length > 0 translations.length > 0

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
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";

View File

@ -8,11 +8,12 @@ import { fetchOr404 } from "src/utils/responses";
const slug = Astro.params.slug!; const slug = Astro.params.slug!;
const page = await fetchOr404(() => payload.getPage(slug)); const response = await fetchOr404(() => payload.getPage(slug));
if (page instanceof Response) { if (response instanceof Response) {
return page; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const page = response.data;
const { parentPages, thumbnail, translations, backgroundImage } = page; const { parentPages, thumbnail, translations, backgroundImage } = page;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);

View File

@ -12,15 +12,15 @@ import { fetchOr404 } from "src/utils/responses";
import { sizesToSrcset } from "src/utils/img"; import { sizesToSrcset } from "src/utils/img";
const id = Astro.params.id!; const id = Astro.params.id!;
const recorder = await fetchOr404(() => payload.getRecorderByID(id)); const response = await fetchOr404(() => payload.getRecorderByID(id));
if (recorder instanceof Response) { if (response instanceof Response) {
return recorder; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
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);
const { username, languages, avatar, translations } = recorder;
const { biography, language } = const { biography, language } =
translations.length > 0 translations.length > 0
? getLocalizedMatch(translations) ? getLocalizedMatch(translations)

View File

@ -2,7 +2,7 @@
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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import { formatCurrency } from "src/utils/currencies"; import { formatCurrency } from "src/utils/currencies";
import { formatLocale } from "src/utils/format"; import { formatLocale } from "src/utils/format";
import { prepareClassForSelectedCurrencyPostProcessing } from "src/middleware/postProcessing"; import { prepareClassForSelectedCurrencyPostProcessing } from "src/middleware/postProcessing";

View File

@ -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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
interface Props { interface Props {
year: number; year: number;
@ -17,10 +17,6 @@ const eventsHaveDifferentDates = events.some(
); );
const date = eventsHaveDifferentDates ? { year } : events[0]!.date; const date = eventsHaveDifferentDates ? { year } : events[0]!.date;
const multiple = events.flatMap(({ events }) => events).length > 1; const multiple = events.flatMap(({ events }) => events).length > 1;
if (year === 856) {
console.log({ eventsHaveDifferentDates, events: events.map(({ date }) => date) });
}
--- ---
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}

View File

@ -6,10 +6,17 @@ 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 { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
import type { WordingKey } from "src/i18n/wordings-keys"; import type { WordingKey } from "src/i18n/wordings-keys";
import { fetchOr404 } from "src/utils/responses";
const response = await fetchOr404(() => payload.getChronologyEvents());
if (response instanceof Response) {
return response;
}
Astro.locals.sdkCalls.add(response.endpointCalled);
const events = response.data;
const events = await payload.getChronologyEvents();
const groupedEvents = groupBy(events, (event) => event.date.year); const groupedEvents = groupBy(events, (event) => event.date.year);
const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl, t, formatTimelineDate } = await getI18n(Astro.locals.currentLocale);
--- ---

View File

@ -13,14 +13,12 @@ import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes"; import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!; const id = Astro.params.id!;
const video = await fetchOr404(() => payload.getVideoByID(id)); const response = await fetchOr404(() => payload.getVideoByID(id));
if (video instanceof Response) { if (response instanceof Response) {
return video; return response;
} }
Astro.locals.sdkCalls.add(response.endpointCalled);
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n( const video = response.data;
Astro.locals.currentLocale
);
const { const {
translations, translations,
attributes, attributes,
@ -34,6 +32,10 @@ const {
mimeType, mimeType,
} = video; } = video;
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
Astro.locals.currentLocale
);
const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations); const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations);
const metaAttributes = [ const metaAttributes = [

View File

@ -1,4 +1,4 @@
import { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
const getUnlocalizedPathname = (pathname: string): string => { const getUnlocalizedPathname = (pathname: string): string => {
for (const locale of contextCache.locales) { for (const locale of contextCache.locales) {

View File

@ -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": 1719518400, "timestamp": 1719604800,
"base": "USD", "base": "USD",
"rates": { "rates": {
"AED": 3.673, "AED": 3.673,
"AFN": 70, "AFN": 71.063715,
"ALL": 93.656243, "ALL": 93.708843,
"AMD": 388.12, "AMD": 388.16,
"ANG": 1.803057, "ANG": 1.802366,
"AOA": 853.629, "AOA": 853.629,
"ARS": 911.0001, "ARS": 911.483,
"AUD": 1.504506, "AUD": 1.49888,
"AWG": 1.8025, "AWG": 1.8025,
"AZN": 1.7, "AZN": 1.7,
"BAM": 1.827765, "BAM": 1.828243,
"BBD": 2, "BBD": 2,
"BDT": 117.53659, "BDT": 117.567308,
"BGN": 1.826865, "BGN": 1.824066,
"BHD": 0.376928, "BHD": 0.376918,
"BIF": 2882.5, "BIF": 2877.015756,
"BMD": 1, "BMD": 1,
"BND": 1.357361, "BND": 1.355347,
"BOB": 6.912666, "BOB": 6.913636,
"BRL": 5.5062, "BRL": 5.5903,
"BSD": 1, "BSD": 1,
"BTC": 0.000016286712, "BTC": 0.00001666854,
"BTN": 83.510338, "BTN": 83.51892,
"BWP": 13.649322, "BWP": 13.588017,
"BYN": 3.274029, "BYN": 3.274442,
"BZD": 2.016506, "BZD": 2.016789,
"CAD": 1.36937, "CAD": 1.368179,
"CDF": 2860, "CDF": 2843.605253,
"CHF": 0.898769, "CHF": 0.898334,
"CLF": 0.034588, "CLF": 0.034326,
"CLP": 954.24, "CLP": 947.15,
"CNH": 7.303225, "CNH": 7.3002,
"CNY": 7.2683, "CNY": 7.2677,
"COP": 4147.064273, "COP": 4182.257769,
"CRC": 523.036765, "CRC": 523.103268,
"CUC": 1, "CUC": 1,
"CUP": 25.75, "CUP": 25.75,
"CVE": 103.063904, "CVE": 103.074514,
"CZK": 23.432, "CZK": 23.392,
"DJF": 177.5, "DJF": 177.827972,
"DKK": 6.969109, "DKK": 6.962786,
"DOP": 59.2, "DOP": 59.096817,
"DZD": 134.481574, "DZD": 134.738656,
"EGP": 48.0279, "EGP": 48.0286,
"ERN": 15, "ERN": 15,
"ETB": 57.750301, "ETB": 57.758394,
"EUR": 0.934324, "EUR": 0.933607,
"FJD": 2.24125, "FJD": 2.2387,
"FKP": 0.791234, "FKP": 0.791061,
"GBP": 0.791234, "GBP": 0.791061,
"GEL": 2.8, "GEL": 2.8,
"GGP": 0.791234, "GGP": 0.791061,
"GHS": 15.25, "GHS": 15.25842,
"GIP": 0.791234, "GIP": 0.791061,
"GMD": 67.775, "GMD": 67.775,
"GNF": 8595, "GNF": 8612.182198,
"GTQ": 7.773841, "GTQ": 7.771251,
"GYD": 209.310316, "GYD": 209.334678,
"HKD": 7.808725, "HKD": 7.809383,
"HNL": 24.762821, "HNL": 24.765136,
"HRK": 7.039709, "HRK": 7.034895,
"HTG": 132.614267, "HTG": 132.555762,
"HUF": 370.35232, "HUF": 368.794829,
"IDR": 16384.008966, "IDR": 16351.422732,
"ILS": 3.75783, "ILS": 3.76585,
"IMP": 0.791234, "IMP": 0.791061,
"INR": 83.466142, "INR": 83.388488,
"IQD": 1310.629281, "IQD": 1310.765417,
"IRR": 42100, "IRR": 42100,
"ISK": 139.14, "ISK": 138.83,
"JEP": 0.791234, "JEP": 0.791061,
"JMD": 156.065666, "JMD": 156.080264,
"JOD": 0.7087, "JOD": 0.7087,
"JPY": 160.819, "JPY": 160.8655,
"KES": 129, "KES": 129.25,
"KGS": 86.45, "KGS": 86.4454,
"KHR": 4116, "KHR": 4110.671159,
"KMF": 460.04988, "KMF": 459.849919,
"KPW": 900, "KPW": 900,
"KRW": 1387.206613, "KRW": 1379.946543,
"KWD": 0.306775, "KWD": 0.306773,
"KYD": 0.833746, "KYD": 0.833423,
"KZT": 466.751615, "KZT": 466.81538,
"LAK": 22077.44273, "LAK": 22075,
"LBP": 89576.348385, "LBP": 89600,
"LKR": 306.055904, "LKR": 306.087327,
"LRD": 194.492735, "LRD": 194.450023,
"LSL": 18.359053, "LSL": 18.18,
"LYD": 4.87349, "LYD": 4.875316,
"MAD": 9.939151, "MAD": 9.940081,
"MDL": 17.849263, "MDL": 17.830168,
"MGA": 4476.966706, "MGA": 4477.581302,
"MKD": 57.476359, "MKD": 57.45758,
"MMK": 2481.91, "MMK": 2481.91,
"MNT": 3450, "MNT": 3450,
"MOP": 8.047221, "MOP": 8.044281,
"MRU": 39.452362, "MRU": 39.462689,
"MUR": 46.899999, "MUR": 47.2,
"MVR": 15.405, "MVR": 15.405,
"MWK": 1734.657401, "MWK": 1734.567667,
"MXN": 18.41087, "MXN": 18.292389,
"MYR": 4.7195, "MYR": 4.7175,
"MZN": 63.850001, "MZN": 63.899991,
"NAD": 18.359053, "NAD": 18.36094,
"NGN": 1515.9, "NGN": 1515.9,
"NIO": 36.825702, "NIO": 36.81119,
"NOK": 10.666237, "NOK": 10.675842,
"NPR": 133.611854, "NPR": 133.327095,
"NZD": 1.643791, "NZD": 1.641672,
"OMR": 0.384955, "OMR": 0.384947,
"PAB": 1, "PAB": 1,
"PEN": 3.823237, "PEN": 3.83492,
"PGK": 3.904308, "PGK": 3.851055,
"PHP": 58.639498, "PHP": 58.433004,
"PKR": 278.503056, "PKR": 278.529263,
"PLN": 4.029254, "PLN": 4.025131,
"PYG": 7540.098869, "PYG": 7540.873261,
"QAR": 3.649042, "QAR": 3.647595,
"RON": 4.6507, "RON": 4.6472,
"RSD": 109.379523, "RSD": 109.245,
"RUB": 84.998456, "RUB": 85.744825,
"RWF": 1306.142519, "RWF": 1306.491928,
"SAR": 3.751634, "SAR": 3.751727,
"SBD": 8.43942, "SBD": 8.43942,
"SCR": 13.796937, "SCR": 13.867107,
"SDG": 601, "SDG": 601,
"SEK": 10.62955, "SEK": 10.596578,
"SGD": 1.358205, "SGD": 1.35596,
"SHP": 0.791234, "SHP": 0.791061,
"SLL": 20969.5, "SLL": 20969.5,
"SOS": 571.751991, "SOS": 571.49784,
"SRD": 30.623, "SRD": 30.8385,
"SSP": 130.26, "SSP": 130.26,
"STD": 22281.8, "STD": 22281.8,
"STN": 22.899229, "STN": 22.878594,
"SVC": 8.754335, "SVC": 8.755235,
"SYP": 2512.53, "SYP": 2512.53,
"SZL": 18.178413, "SZL": 18.183346,
"THB": 36.7685, "THB": 36.719,
"TJS": 10.65474, "TJS": 10.656085,
"TMT": 3.51, "TMT": 3.51,
"TND": 3.136769, "TND": 3.1465,
"TOP": 2.36092, "TOP": 2.363716,
"TRY": 32.836478, "TRY": 32.656998,
"TTD": 6.7978, "TTD": 6.798721,
"TWD": 32.560001, "TWD": 32.5085,
"TZS": 2626.295328, "TZS": 2635,
"UAH": 40.51595, "UAH": 40.51974,
"UGX": 3711.539326, "UGX": 3712.013854,
"USD": 1, "USD": 1,
"UYU": 39.578963, "UYU": 39.446434,
"UZS": 12585.732694, "UZS": 12586.719022,
"VES": 36.35908, "VES": 36.390223,
"VND": 25455.008685, "VND": 25455.011984,
"VUV": 118.722, "VUV": 118.722,
"WST": 2.8, "WST": 2.8,
"XAF": 612.876514, "XAF": 612.406173,
"XAG": 0.0345453, "XAG": 0.03435128,
"XAU": 0.00042988, "XAU": 0.00043009,
"XCD": 2.70255, "XCD": 2.70255,
"XDR": 0.759718, "XDR": 0.759476,
"XOF": 612.876514, "XOF": 612.406173,
"XPD": 0.00108638, "XPD": 0.00104121,
"XPF": 111.494537, "XPF": 111.408973,
"XPT": 0.00101314, "XPT": 0.00100687,
"YER": 250.399984, "YER": 250.399984,
"ZAR": 18.4643, "ZAR": 18.190651,
"ZMW": 25.735569, "ZMW": 25.739177,
"ZWL": 322 "ZWL": 322
} }
} }

View File

@ -2082,30 +2082,17 @@ export type EndpointAllSDKUrls = {
// SDK // SDK
type GetPayloadSDKParams = {
apiURL: string;
email: string;
password: string;
tokenCache?: {
set: (token: string, expirationTimestamp: number) => void;
get: () => string | undefined;
};
responseCache?: {
set: (url: string, response: any) => void;
get: (url: string) => any | undefined;
};
};
const logResponse = (res: Response) => console.log(res.status, res.statusText, res.url);
export const getSDKEndpoint = { export const getSDKEndpoint = {
getConfigEndpoint: () => `/globals/${Collections.WebsiteConfig}/config`, getConfigEndpoint: () => `/globals/${Collections.WebsiteConfig}/config`,
getFolderEndpoint: (slug: string) => `/${Collections.Folders}/slug/${slug}`, getFolderEndpoint: (slug: string) => `/${Collections.Folders}/slug/${slug}`,
getFolderSlugsEndpoint: () => `/${Collections.Folders}/slugs`,
getLanguagesEndpoint: () => `/${Collections.Languages}/all`, getLanguagesEndpoint: () => `/${Collections.Languages}/all`,
getCurrenciesEndpoint: () => `/${Collections.Currencies}/all`, getCurrenciesEndpoint: () => `/${Collections.Currencies}/all`,
getWordingsEndpoint: () => `/${Collections.Wordings}/all`, getWordingsEndpoint: () => `/${Collections.Wordings}/all`,
getPageEndpoint: (slug: string) => `/${Collections.Pages}/slug/${slug}`, getPageEndpoint: (slug: string) => `/${Collections.Pages}/slug/${slug}`,
getPageSlugsEndpoint: () => `/${Collections.Pages}/slugs`,
getCollectibleEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}`, getCollectibleEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}`,
getCollectibleSlugsEndpoint: () => `/${Collections.Collectibles}/slugs`,
getCollectibleScansEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}/scans`, getCollectibleScansEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}/scans`,
getCollectibleScanPageEndpoint: (slug: string, index: string) => getCollectibleScanPageEndpoint: (slug: string, index: string) =>
`/${Collections.Collectibles}/slug/${slug}/scans/${index}`, `/${Collections.Collectibles}/slug/${slug}/scans/${index}`,
@ -2124,21 +2111,51 @@ export const getSDKEndpoint = {
getLoginEndpoint: () => `/${Collections.Recorders}/login`, getLoginEndpoint: () => `/${Collections.Recorders}/login`,
}; };
export const getPayloadSDK = ({ type PayloadSDKResponse<T> = {
apiURL, data: T;
email, endpointCalled: string;
password, };
tokenCache,
responseCache, type PayloadTokenCache = {
}: GetPayloadSDKParams) => { set: (token: string, expirationTimestamp: number) => void;
const refreshToken = async () => { get: () => string | undefined;
const loginUrl = `${apiURL}${getSDKEndpoint.getLoginEndpoint()}`; };
type PayloadDataCache = {
set: (url: string, response: any) => void;
get: (url: string) => any | undefined;
};
export class PayloadSDK {
private tokenCache: PayloadTokenCache | undefined;
private dataCache: PayloadDataCache | undefined;
constructor(
private readonly apiURL: string,
private readonly email: string,
private readonly password: string
) {}
addTokenCache(tokenCache: PayloadTokenCache) {
this.tokenCache = tokenCache;
}
addDataCache(dataCache: PayloadDataCache) {
this.dataCache = dataCache;
}
private logResponse(res: Response) {
console.log(res.status, res.statusText, res.url);
}
private async refreshToken() {
const loginUrl = `${this.apiURL}${getSDKEndpoint.getLoginEndpoint()}`;
const loginResult = await fetch(loginUrl, { const loginResult = await fetch(loginUrl, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }), body: JSON.stringify({ email: this.email, password: this.password }),
}); });
logResponse(loginResult); this.logResponse(loginResult);
if (loginResult.status !== 200) { if (loginResult.status !== 200) {
throw new Error("Unable to login"); throw new Error("Unable to login");
@ -2148,77 +2165,104 @@ export const getPayloadSDK = ({
token: string; token: string;
exp: number; exp: number;
}; };
tokenCache?.set(token, exp); this.tokenCache?.set(token, exp);
return token; return token;
}; }
const request = async (endpoint: string): Promise<any> => { async request<T>(endpoint: string): Promise<PayloadSDKResponse<T>> {
const cachedResponse = responseCache?.get(endpoint); const cachedResponse = this.dataCache?.get(endpoint);
if (cachedResponse) { if (cachedResponse) {
return cachedResponse; return cachedResponse;
} }
const result = await fetch(`${apiURL}${endpoint}`, { const result = await fetch(`${this.apiURL}${endpoint}`, {
headers: { headers: {
Authorization: `JWT ${tokenCache?.get() ?? (await refreshToken())}`, Authorization: `JWT ${this.tokenCache?.get() ?? (await this.refreshToken())}`,
}, },
}); });
logResponse(result); this.logResponse(result);
if (!result.ok) { if (!result.ok) {
throw new Error("Unhandled fetch error"); throw new Error("Unhandled fetch error");
} }
const data = await result.json(); const response = { data: await result.json(), endpointCalled: endpoint };
responseCache?.set(endpoint, data); this.dataCache?.set(endpoint, response);
return data; return response;
}; }
return { async getConfig(): Promise<PayloadSDKResponse<EndpointWebsiteConfig>> {
getConfig: async (): Promise<EndpointWebsiteConfig> => return await this.request(getSDKEndpoint.getConfigEndpoint());
await request(getSDKEndpoint.getConfigEndpoint()), }
getFolder: async (slug: string): Promise<EndpointFolder> => async getFolder(slug: string): Promise<PayloadSDKResponse<EndpointFolder>> {
await request(getSDKEndpoint.getFolderEndpoint(slug)), return await this.request(getSDKEndpoint.getFolderEndpoint(slug));
getLanguages: async (): Promise<Language[]> => }
await request(getSDKEndpoint.getLanguagesEndpoint()), async getFolderSlugs(): Promise<PayloadSDKResponse<string[]>> {
getCurrencies: async (): Promise<Currency[]> => return await this.request(getSDKEndpoint.getFolderSlugsEndpoint());
await request(getSDKEndpoint.getCurrenciesEndpoint()), }
getWordings: async (): Promise<EndpointWording[]> => async getLanguages(): Promise<PayloadSDKResponse<Language[]>> {
await request(getSDKEndpoint.getWordingsEndpoint()), return await this.request(getSDKEndpoint.getLanguagesEndpoint());
getPage: async (slug: string): Promise<EndpointPage> => }
await request(getSDKEndpoint.getPageEndpoint(slug)), async getCurrencies(): Promise<PayloadSDKResponse<Currency[]>> {
getCollectible: async (slug: string): Promise<EndpointCollectible> => return await this.request(getSDKEndpoint.getCurrenciesEndpoint());
await request(getSDKEndpoint.getCollectibleEndpoint(slug)), }
getCollectibleScans: async (slug: string): Promise<EndpointCollectibleScans> => async getWordings(): Promise<PayloadSDKResponse<EndpointWording[]>> {
await request(getSDKEndpoint.getCollectibleScansEndpoint(slug)), return await this.request(getSDKEndpoint.getWordingsEndpoint());
getCollectibleScanPage: async ( }
async getPage(slug: string): Promise<PayloadSDKResponse<EndpointPage>> {
return await this.request(getSDKEndpoint.getPageEndpoint(slug));
}
async getPageSlugs(): Promise<PayloadSDKResponse<string[]>> {
return await this.request(getSDKEndpoint.getPageSlugsEndpoint());
}
async getCollectible(slug: string): Promise<PayloadSDKResponse<EndpointCollectible>> {
return await this.request(getSDKEndpoint.getCollectibleEndpoint(slug));
}
async getCollectibleSlugs(): Promise<PayloadSDKResponse<string[]>> {
return await this.request(getSDKEndpoint.getCollectibleSlugsEndpoint());
}
async getCollectibleScans(slug: string): Promise<PayloadSDKResponse<EndpointCollectibleScans>> {
return await this.request(getSDKEndpoint.getCollectibleScansEndpoint(slug));
}
async getCollectibleScanPage(
slug: string, slug: string,
index: string index: string
): Promise<EndpointCollectibleScanPage> => ): Promise<PayloadSDKResponse<EndpointCollectibleScanPage>> {
await request(getSDKEndpoint.getCollectibleScanPageEndpoint(slug, index)), return await this.request(getSDKEndpoint.getCollectibleScanPageEndpoint(slug, index));
getCollectibleGallery: async (slug: string): Promise<EndpointCollectibleGallery> => }
await request(getSDKEndpoint.getCollectibleGalleryEndpoint(slug)), async getCollectibleGallery(
getCollectibleGalleryImage: async ( slug: string
): Promise<PayloadSDKResponse<EndpointCollectibleGallery>> {
return await this.request(getSDKEndpoint.getCollectibleGalleryEndpoint(slug));
}
async getCollectibleGalleryImage(
slug: string, slug: string,
index: string index: string
): Promise<EndpointCollectibleGalleryImage> => ): Promise<PayloadSDKResponse<EndpointCollectibleGalleryImage>> {
await request(getSDKEndpoint.getCollectibleGalleryImageEndpoint(slug, index)), return await this.request(getSDKEndpoint.getCollectibleGalleryImageEndpoint(slug, index));
getChronologyEvents: async (): Promise<EndpointChronologyEvent[]> => }
await request(getSDKEndpoint.getChronologyEventsEndpoint()), async getChronologyEvents(): Promise<PayloadSDKResponse<EndpointChronologyEvent[]>> {
getChronologyEventByID: async (id: string): Promise<EndpointChronologyEvent> => return await this.request(getSDKEndpoint.getChronologyEventsEndpoint());
await request(getSDKEndpoint.getChronologyEventByIDEndpoint(id)), }
getImageByID: async (id: string): Promise<EndpointImage> => async getChronologyEventByID(id: string): Promise<PayloadSDKResponse<EndpointChronologyEvent>> {
await request(getSDKEndpoint.getImageByIDEndpoint(id)), return await this.request(getSDKEndpoint.getChronologyEventByIDEndpoint(id));
getAudioByID: async (id: string): Promise<EndpointAudio> => }
await request(getSDKEndpoint.getAudioByIDEndpoint(id)), async getImageByID(id: string): Promise<PayloadSDKResponse<EndpointImage>> {
getVideoByID: async (id: string): Promise<EndpointVideo> => return await this.request(getSDKEndpoint.getImageByIDEndpoint(id));
await request(getSDKEndpoint.getVideoByIDEndpoint(id)), }
getFileByID: async (id: string): Promise<EndpointFile> => async getAudioByID(id: string): Promise<PayloadSDKResponse<EndpointAudio>> {
await request(getSDKEndpoint.getFileByIDEndpoint(id)), return await this.request(getSDKEndpoint.getAudioByIDEndpoint(id));
getRecorderByID: async (id: string): Promise<EndpointRecorder> => }
await request(getSDKEndpoint.getRecorderByIDEndpoint(id)), async getVideoByID(id: string): Promise<PayloadSDKResponse<EndpointVideo>> {
getAllSdkUrls: async (): Promise<EndpointAllSDKUrls> => return await this.request(getSDKEndpoint.getVideoByIDEndpoint(id));
await request(getSDKEndpoint.getAllSDKUrlsEndpoint()), }
request: async (pathname: string): Promise<any> => await request(pathname), async getFileByID(id: string): Promise<PayloadSDKResponse<EndpointFile>> {
}; return await this.request(getSDKEndpoint.getFileByIDEndpoint(id));
}; }
async getRecorderByID(id: string): Promise<PayloadSDKResponse<EndpointRecorder>> {
return await this.request(getSDKEndpoint.getRecorderByIDEndpoint(id));
}
async getAllSdkUrls(): Promise<PayloadSDKResponse<EndpointAllSDKUrls>> {
return await this.request(getSDKEndpoint.getAllSDKUrlsEndpoint());
}
}

View File

@ -6,7 +6,7 @@ import {
type RichTextContent, type RichTextContent,
type RichTextNode, type RichTextNode,
} from "src/shared/payload/payload-sdk"; } from "src/shared/payload/payload-sdk";
import { contextCache } from "src/cache/contextCache"; import { contextCache } from "src/utils/payload";
export const formatLocale = (code: string): string => export const formatLocale = (code: string): string =>
contextCache.locales.find(({ id }) => id === code)?.name ?? code; contextCache.locales.find(({ id }) => id === code)?.name ?? code;

View File

@ -1,28 +1,20 @@
import { dataCache } from "src/cache/dataCache"; import { ContextCache } from "src/cache/contextCache";
import { getPayloadSDK } from "src/shared/payload/payload-sdk"; import { DataCache } from "src/cache/dataCache";
import { PageCache } from "src/cache/pageCache";
import { TokenCache } from "src/cache/tokenCache";
import { PayloadSDK } from "src/shared/payload/payload-sdk";
let token: string | undefined = undefined; const payload = new PayloadSDK(
let expiration: number | undefined = undefined; import.meta.env.PAYLOAD_API_URL,
import.meta.env.PAYLOAD_USER,
import.meta.env.PAYLOAD_PASSWORD
);
export const payload = getPayloadSDK({ const contextCache = new ContextCache(payload);
apiURL: import.meta.env.PAYLOAD_API_URL, const pageCache = new PageCache(payload);
email: import.meta.env.PAYLOAD_USER, const dataCache = new DataCache(payload, (urls) => pageCache.invalidate(urls));
password: import.meta.env.PAYLOAD_PASSWORD,
tokenCache: { payload.addTokenCache(new TokenCache());
get: () => { payload.addDataCache(dataCache);
if (!token) return undefined;
if (!expiration || expiration < Date.now()) { export { payload, contextCache, pageCache, dataCache };
console.log("[PayloadSDK] No token to be retrieved or the token expired");
return undefined;
}
return token;
},
set: (newToken, newExpiration) => {
token = newToken;
expiration = newExpiration * 1000;
const diffInMinutes = Math.floor((expiration - Date.now()) / 1000 / 60);
console.log("[PayloadSDK] New token set. TTL is", diffInMinutes, "minutes.");
},
},
responseCache: dataCache,
});