Collectible get by slug endpoint + fixes

This commit is contained in:
DrMint 2024-03-08 23:26:54 +01:00
parent b06918b346
commit 7b7507b7f8
11 changed files with 302 additions and 127 deletions

8
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/vollkorn": "5.0.18", "@fontsource/vollkorn": "5.0.19",
"@payloadcms/bundler-webpack": "1.0.6", "@payloadcms/bundler-webpack": "1.0.6",
"@payloadcms/db-mongodb": "1.4.3", "@payloadcms/db-mongodb": "1.4.3",
"@payloadcms/richtext-lexical": "0.7.0", "@payloadcms/richtext-lexical": "0.7.0",
@ -1953,9 +1953,9 @@
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
}, },
"node_modules/@fontsource/vollkorn": { "node_modules/@fontsource/vollkorn": {
"version": "5.0.18", "version": "5.0.19",
"resolved": "https://registry.npmjs.org/@fontsource/vollkorn/-/vollkorn-5.0.18.tgz", "resolved": "https://registry.npmjs.org/@fontsource/vollkorn/-/vollkorn-5.0.19.tgz",
"integrity": "sha512-jdZL0LnKtkHHTDo5YKJ3yiS4XhCuE1EO9IrRJbf/5N07Y3lbDATuaXj6erg8DQltqLhqrb5TTfxYpno2MKpddQ==" "integrity": "sha512-Wh72GEg5oqR5tiLDtgzs3RoFRHFdGiCWuTpf+p6cDw9P84CWSYucH59XJ2VCWtoSwtgl1FugnT1bkp8eVcSMYw=="
}, },
"node_modules/@hapi/hoek": { "node_modules/@hapi/hoek": {
"version": "9.3.0", "version": "9.3.0",

View File

@ -22,7 +22,7 @@
"start": "npm install && npm run generate:types && npm run dev" "start": "npm install && npm run generate:types && npm run dev"
}, },
"dependencies": { "dependencies": {
"@fontsource/vollkorn": "5.0.18", "@fontsource/vollkorn": "5.0.19",
"@payloadcms/bundler-webpack": "1.0.6", "@payloadcms/bundler-webpack": "1.0.6",
"@payloadcms/db-mongodb": "1.4.3", "@payloadcms/db-mongodb": "1.4.3",
"@payloadcms/richtext-lexical": "0.7.0", "@payloadcms/richtext-lexical": "0.7.0",

View File

@ -27,6 +27,7 @@ const fields = {
status: "_status", status: "_status",
slug: "slug", slug: "slug",
thumbnail: "thumbnail", thumbnail: "thumbnail",
backgroundImage: "backgroundImage",
nature: "nature", nature: "nature",
tags: "tags", tags: "tags",
languages: "languages", languages: "languages",
@ -198,6 +199,15 @@ export const Collectibles = buildVersionedCollectionConfig({
{ {
label: "Images", label: "Images",
fields: [ fields: [
imageField({
name: fields.backgroundImage,
relationTo: Collections.Images,
admin: {
description:
"The image used as background from the webpage.\
If missing, the thumbnail will be used instead.",
},
}),
{ {
name: fields.gallery, name: fields.gallery,
type: "array", type: "array",

View File

@ -1,30 +1,138 @@
import { CollectibleNature, CollectionStatus, Collections } from "../../../constants"; import { CollectibleNature, CollectionStatus, Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectible, EndpointCollectiblePreview } from "../../../sdk"; import { EndpointCollectible, EndpointCollectiblePreview, PayloadImage } from "../../../sdk";
import { Collectible } from "../../../types/collections"; import { Collectible } from "../../../types/collections";
import { isPayloadArrayType, isPayloadType, isValidPayloadImage } from "../../../utils/asserts"; import {
import { convertTagsToGroups } from "../../../utils/endpoints"; isDefined,
isPayloadArrayType,
isPayloadType,
isValidPayloadImage,
} from "../../../utils/asserts";
import { convertTagsToGroups, handleParentPages } from "../../../utils/endpoints";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
export const getBySlugEndpoint = createGetByEndpoint( export const getBySlugEndpoint = createGetByEndpoint(
Collections.Collectibles, Collections.Collectibles,
"slug", "slug",
(collectible: Collectible): EndpointCollectible => { (collectible: Collectible): EndpointCollectible => {
const { nature, urls, subitems, gallery, contents } = collectible; const {
nature,
urls,
subitems,
gallery,
contents,
priceEnabled,
price,
size,
sizeEnabled,
weight,
weightEnabled,
pageInfo,
pageInfoEnabled,
parentItems,
folders,
backgroundImage,
} = collectible;
return { return {
...convertCollectibleToPreview(collectible), ...convertCollectibleToPreview(collectible),
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
contents: handleContents(contents), contents: handleContents(contents),
gallery: gallery: handleGallery(gallery),
gallery scans: handleScans(collectible.scans),
?.map(({ image }) => image)
.flatMap((image) => (isValidPayloadImage(image) ? [image] : [])) ?? [],
nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital, nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital,
parentPages: [], // TODO: todo parentPages: handleParentPages({collectibles: parentItems, folders}),
subitems: isPayloadArrayType(subitems) ? subitems.map(convertCollectibleToPreview) : [], subitems: isPayloadArrayType(subitems) ? subitems.map(convertCollectibleToPreview) : [],
urls: urls?.map(({ url }) => ({ url, label: getLabelFromUrl(url) })) ?? [], urls: urls?.map(({ url }) => ({ url, label: getLabelFromUrl(url) })) ?? [],
...(weightEnabled && weight ? { weight: weight.amount } : {}),
...handleSize(size, sizeEnabled),
...handlePageInfo(pageInfo, pageInfoEnabled),
...handlePrice(price, priceEnabled),
}; };
} },
3
); );
export const handlePrice = (
price: Collectible["price"],
enabled: Collectible["priceEnabled"]
): { price: NonNullable<EndpointCollectible["price"]> } | {} => {
if (!price || !enabled || !isPayloadType(price.currency)) return {};
return {
price: { amount: price.amount, currency: price.currency.id },
};
};
export const handleSize = (
size: Collectible["size"],
enabled: Collectible["sizeEnabled"]
): { size: NonNullable<EndpointCollectible["size"]> } | {} => {
if (!size || !enabled) return {};
return {
size: {
width: size.width,
height: size.height,
...(isDefined(size.thickness) ? { thickness: size.thickness } : {}),
},
};
};
export const handlePageInfo = (
pageInfo: Collectible["pageInfo"],
enabled: Collectible["pageInfoEnabled"]
): { pageInfo: NonNullable<EndpointCollectible["pageInfo"]> } | {} => {
if (!pageInfo || !enabled) return {};
return {
pageInfo: {
pageCount: pageInfo.pageCount,
...(isDefined(pageInfo.bindingType) ? { bindingType: pageInfo.bindingType } : {}),
...(isDefined(pageInfo.pageOrder) ? { pageOrder: pageInfo.pageOrder } : {}),
},
};
};
export const handleGallery = (gallery: Collectible["gallery"]): EndpointCollectible["gallery"] => {
const result: PayloadImage[] = [];
if (!gallery) return result;
gallery?.forEach(({ image }) => {
if (isValidPayloadImage(image)) {
result.push(image);
}
});
return result.slice(0, 10);
};
export const handleScans = (scans: Collectible["scans"]): EndpointCollectible["scans"] => {
const result: PayloadImage[] = [];
if (!scans) return result;
scans.pages?.forEach(({ image }) => {
if (isValidPayloadImage(image)) {
result.push(image);
}
});
if (isValidPayloadImage(scans.cover?.front)) {
result.push(scans.cover.front);
}
if (isValidPayloadImage(scans.cover?.back)) {
result.push(scans.cover.back);
}
if (isValidPayloadImage(scans.dustjacket?.front)) {
result.push(scans.dustjacket.front);
}
if (isValidPayloadImage(scans.dustjacket?.back)) {
result.push(scans.dustjacket.back);
}
return result.slice(0, 10);
};
export const handleContents = ( export const handleContents = (
contents: Collectible["contents"] contents: Collectible["contents"]
): EndpointCollectible["contents"] => { ): EndpointCollectible["contents"] => {
@ -65,12 +173,20 @@ export const handleContents = (
switch (content.relationTo) { switch (content.relationTo) {
case "generic-contents": case "generic-contents":
return isPayloadType(content.value) return isPayloadType(content.value)
? { relationTo: "generic-contents", value: content.value } ? {
relationTo: "generic-contents",
value: {
translations: content.value.translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})),
},
}
: undefined; : undefined;
case "pages": case "pages":
return isPayloadType(content.value) return isPayloadType(content.value)
? { relationTo: "pages", value: content.value } ? { relationTo: "pages", value: convertPageToPreview(content.value) }
: undefined; : undefined;
default: default:

View File

@ -3,6 +3,8 @@ import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointFolder, EndpointFolderPreview } from "../../../sdk"; import { EndpointFolder, EndpointFolderPreview } from "../../../sdk";
import { Folder, Language } from "../../../types/collections"; import { Folder, Language } from "../../../types/collections";
import { isDefined, isPayloadType, isValidPayloadImage } from "../../../utils/asserts"; import { isDefined, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertCollectibleToPreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
export const getBySlugEndpoint = createGetByEndpoint( export const getBySlugEndpoint = createGetByEndpoint(
Collections.Folders, Collections.Folders,
@ -36,9 +38,9 @@ export const getBySlugEndpoint = createGetByEndpoint(
} }
switch (relationTo) { switch (relationTo) {
case "collectibles": case "collectibles":
return [{ relationTo, value }]; return [{ relationTo, value: convertCollectibleToPreview(value) }];
case "pages": case "pages":
return [{ relationTo, value }]; return [{ relationTo, value: convertPageToPreview(value) }];
} }
}) ?? [], }) ?? [],
}; };

View File

@ -23,6 +23,7 @@ const fields = {
type: "type", type: "type",
authors: "authors", authors: "authors",
thumbnail: "thumbnail", thumbnail: "thumbnail",
backgroundImage: "backgroundImage",
translations: "translations", translations: "translations",
tags: "tags", tags: "tags",
sourceLanguage: "sourceLanguage", sourceLanguage: "sourceLanguage",
@ -71,6 +72,8 @@ export const Pages = buildVersionedCollectionConfig({
}, },
endpoints: [getBySlugEndpoint], endpoints: [getBySlugEndpoint],
fields: [ fields: [
rowField([
slugField({ name: fields.slug }),
{ {
name: fields.type, name: fields.type,
type: "radio", type: "radio",
@ -81,12 +84,21 @@ export const Pages = buildVersionedCollectionConfig({
value: value, value: value,
})), })),
}, },
]),
rowField([ rowField([
slugField({ name: fields.slug }),
imageField({ imageField({
name: fields.thumbnail, name: fields.thumbnail,
relationTo: Collections.Images, relationTo: Collections.Images,
}), }),
imageField({
name: fields.backgroundImage,
relationTo: Collections.Images,
admin: {
description:
"The image used as background from the webpage.\
If missing, the thumbnail will be used instead.",
},
}),
]), ]),
rowField([ rowField([
tagsField({ name: fields.tags }), tagsField({ name: fields.tags }),

View File

@ -6,29 +6,20 @@ import {
isNodeBlockNode, isNodeBlockNode,
} from "../../../constants"; } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointPage, ParentPage, TableOfContentEntry } from "../../../sdk"; import { EndpointPage, EndpointPagePreview, TableOfContentEntry } from "../../../sdk";
import { Page } from "../../../types/collections"; import { Page } from "../../../types/collections";
import { isPayloadArrayType, isPayloadType, isValidPayloadImage } from "../../../utils/asserts"; import { isPayloadArrayType, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertTagsToGroups } from "../../../utils/endpoints"; import { convertTagsToGroups, handleParentPages } from "../../../utils/endpoints";
export const getBySlugEndpoint = createGetByEndpoint( export const getBySlugEndpoint = createGetByEndpoint(
Collections.Pages, Collections.Pages,
"slug", "slug",
({ (page: Page): EndpointPage => {
authors, const { translations, collectibles, folders, backgroundImage } = page;
slug,
translations, return {
tags, ...convertPageToPreview(page),
thumbnail, ...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
_status,
collectibles,
folders,
type,
}: Page): EndpointPage => ({
slug,
type: type as PageType,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsToGroups(tags),
translations: translations.map( translations: translations.map(
({ ({
content, content,
@ -55,10 +46,9 @@ export const getBySlugEndpoint = createGetByEndpoint(
proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(({ id }) => id) : [], proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(({ id }) => id) : [],
}) })
), ),
authors: isPayloadArrayType(authors) ? authors.map(({ id }) => id) : [],
status: _status === "published" ? "published" : "draft",
parentPages: handleParentPages({ collectibles, folders }), parentPages: handleParentPages({ collectibles, folders }),
}) };
}
); );
const handleContent = ( const handleContent = (
@ -98,40 +88,27 @@ const handleToc = (content: RichTextContent, parentPrefix = ""): TableOfContentE
children: handleToc(fields.content, `${index + 1}.`), children: handleToc(fields.content, `${index + 1}.`),
})); }));
const handleParentPages = ({
collectibles,
folders,
}: Pick<Page, "collectibles" | "folders">): ParentPage[] => {
const result: ParentPage[] = [];
if (collectibles && isPayloadArrayType(collectibles)) {
collectibles.forEach(({ slug, translations }) => { export const convertPageToPreview = ({
result.push({ authors,
collection: Collections.Collectibles,
slug, slug,
translations: translations.map(({ language, title }) => ({ translations,
tags,
thumbnail,
_status,
type,
}: Page): EndpointPagePreview => ({
slug,
type: type as PageType,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsToGroups(tags),
translations: translations.map(({ language, title, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language, language: isPayloadType(language) ? language.id : language,
name: title, // TODO: Use the entire pretitle + title + subtitle ...(pretitle ? { pretitle } : {}),
title,
...(subtitle ? { subtitle } : {}),
})), })),
tag: "collectible", authors: isPayloadArrayType(authors) ? authors.map(({ id }) => id) : [],
status: _status === "published" ? "published" : "draft",
}); });
});
}
if (folders && isPayloadArrayType(folders)) {
folders.forEach(({ slug, translations }) => {
result.push({
collection: Collections.Folders,
slug,
translations:
translations?.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})) ?? [],
tag: "folders",
});
});
}
return result;
};

View File

@ -4,7 +4,8 @@ import { CollectionEndpoint } from "../types/payload";
export const createGetByEndpoint = <C extends keyof GeneratedTypes["collections"], R>( export const createGetByEndpoint = <C extends keyof GeneratedTypes["collections"], R>(
collection: C, collection: C,
attribute: string, attribute: string,
handler: (doc: GeneratedTypes["collections"][C]) => Promise<R> | R handler: (doc: GeneratedTypes["collections"][C]) => Promise<R> | R,
depth?: number,
): CollectionEndpoint => ({ ): CollectionEndpoint => ({
path: `/${attribute}/:${attribute}`, path: `/${attribute}/:${attribute}`,
method: "get", method: "get",
@ -21,6 +22,7 @@ export const createGetByEndpoint = <C extends keyof GeneratedTypes["collections"
const result = await payload.find({ const result = await payload.find({
collection, collection,
depth,
where: { [attribute]: { equals: req.params[attribute] } }, where: { [attribute]: { equals: req.params[attribute] } },
}); });

View File

@ -6,7 +6,7 @@ import {
PageType, PageType,
RichTextContent, RichTextContent,
} from "./constants"; } from "./constants";
import { Collectible, Currency, GenericContent, Language, Page } from "./types/collections"; import { Currency, Language } from "./types/collections";
class NodeCache { class NodeCache {
constructor(_params: any) {} constructor(_params: any) {}
@ -179,11 +179,11 @@ export type EndpointFolder = EndpointFolderPreview & {
files: ( files: (
| { | {
relationTo: "collectibles"; relationTo: "collectibles";
value: Collectible; value: EndpointCollectiblePreview;
} }
| { | {
relationTo: "pages"; relationTo: "pages";
value: Page; value: EndpointPagePreview;
} }
)[]; )[];
}; };
@ -238,7 +238,7 @@ export type EndpointTagsGroup = {
tags: EndpointTag[]; tags: EndpointTag[];
}; };
export type EndpointPage = { export type EndpointPagePreview = {
slug: string; slug: string;
type: PageType; type: PageType;
thumbnail?: PayloadImage; thumbnail?: PayloadImage;
@ -246,18 +246,24 @@ export type EndpointPage = {
tagGroups: TagGroup[]; tagGroups: TagGroup[];
translations: { translations: {
language: string; language: string;
sourceLanguage: string;
pretitle?: string; pretitle?: string;
title: string; title: string;
subtitle?: string; subtitle?: string;
}[];
status: "draft" | "published";
};
export type EndpointPage = EndpointPagePreview & {
backgroundImage?: PayloadImage;
translations: (EndpointPagePreview["translations"][number] & {
sourceLanguage: string;
summary?: RichTextContent; summary?: RichTextContent;
content: RichTextContent; content: RichTextContent;
transcribers: string[]; transcribers: string[];
translators: string[]; translators: string[];
proofreaders: string[]; proofreaders: string[];
toc: TableOfContentEntry[]; toc: TableOfContentEntry[];
}[]; })[];
status: "draft" | "published";
parentPages: ParentPage[]; parentPages: ParentPage[];
}; };
@ -285,8 +291,10 @@ export type EndpointCollectiblePreview = {
}; };
export type EndpointCollectible = EndpointCollectiblePreview & { export type EndpointCollectible = EndpointCollectiblePreview & {
backgroundImage?: PayloadImage;
nature: CollectibleNature; nature: CollectibleNature;
gallery: PayloadImage[]; gallery: PayloadImage[];
scans: PayloadImage[];
urls: { url: string; label: string }[]; urls: { url: string; label: string }[];
price?: { price?: {
amount: number; amount: number;
@ -297,9 +305,7 @@ export type EndpointCollectible = EndpointCollectiblePreview & {
height: number; height: number;
thickness?: number; thickness?: number;
}; };
weight?: { weight?: number;
amount: number;
};
pageInfo?: { pageInfo?: {
pageCount: number; pageCount: number;
bindingType?: CollectibleBindingTypes; bindingType?: CollectibleBindingTypes;
@ -310,11 +316,16 @@ export type EndpointCollectible = EndpointCollectiblePreview & {
content: content:
| { | {
relationTo: "pages"; relationTo: "pages";
value: Page; value: EndpointPagePreview;
} }
| { | {
relationTo: "generic-contents"; relationTo: "generic-contents";
value: GenericContent; value: {
translations: {
language: string;
name: string;
}[];
};
}; };
range?: range?:

View File

@ -195,6 +195,7 @@ export interface Collectible {
} | null; } | null;
id?: string | null; id?: string | null;
}[]; }[];
backgroundImage?: string | Image | null;
gallery?: gallery?:
| { | {
image: string | Image; image: string | Image;
@ -480,9 +481,10 @@ export interface Currency {
*/ */
export interface Page { export interface Page {
id: string; id: string;
type: 'Content' | 'Post' | 'Generic';
slug: string; slug: string;
type: 'Content' | 'Post' | 'Generic';
thumbnail?: string | Image | null; thumbnail?: string | Image | null;
backgroundImage?: string | Image | null;
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
authors?: (string | Recorder)[] | null; authors?: (string | Recorder)[] | null;
translations: { translations: {

View File

@ -1,5 +1,6 @@
import { TagGroup } from "../sdk"; import { Collections } from "../constants";
import { Tag } from "../types/collections"; import { ParentPage, TagGroup } from "../sdk";
import { Collectible, Folder, Tag } from "../types/collections";
import { isPayloadArrayType, isPayloadType } from "./asserts"; import { isPayloadArrayType, isPayloadType } from "./asserts";
export const convertTagsToGroups = (tags: (string | Tag)[] | null | undefined): TagGroup[] => { export const convertTagsToGroups = (tags: (string | Tag)[] | null | undefined): TagGroup[] => {
@ -26,3 +27,45 @@ export const convertTagsToGroups = (tags: (string | Tag)[] | null | undefined):
return groups; return groups;
}; };
export const handleParentPages = ({
collectibles,
folders,
}: {
collectibles?: (string | Collectible)[] | null | undefined;
folders?: (string | Folder)[] | null | undefined
}): ParentPage[] => {
const result: ParentPage[] = [];
if (collectibles && isPayloadArrayType(collectibles)) {
collectibles.forEach(({ slug, translations }) => {
result.push({
collection: Collections.Collectibles,
slug,
translations: translations.map(({ language, title }) => ({
language: isPayloadType(language) ? language.id : language,
name: title, // TODO: Use the entire pretitle + title + subtitle
})),
tag: "collectible",
});
});
}
if (folders && isPayloadArrayType(folders)) {
folders.forEach(({ slug, translations }) => {
result.push({
collection: Collections.Folders,
slug,
translations:
translations?.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})) ?? [],
tag: "folders",
});
});
}
return result;
};