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

View File

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

View File

@ -27,6 +27,7 @@ const fields = {
status: "_status",
slug: "slug",
thumbnail: "thumbnail",
backgroundImage: "backgroundImage",
nature: "nature",
tags: "tags",
languages: "languages",
@ -198,6 +199,15 @@ export const Collectibles = buildVersionedCollectionConfig({
{
label: "Images",
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,
type: "array",

View File

@ -1,30 +1,138 @@
import { CollectibleNature, CollectionStatus, Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectible, EndpointCollectiblePreview } from "../../../sdk";
import { EndpointCollectible, EndpointCollectiblePreview, PayloadImage } from "../../../sdk";
import { Collectible } from "../../../types/collections";
import { isPayloadArrayType, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertTagsToGroups } from "../../../utils/endpoints";
import {
isDefined,
isPayloadArrayType,
isPayloadType,
isValidPayloadImage,
} from "../../../utils/asserts";
import { convertTagsToGroups, handleParentPages } from "../../../utils/endpoints";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
export const getBySlugEndpoint = createGetByEndpoint(
Collections.Collectibles,
"slug",
(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 {
...convertCollectibleToPreview(collectible),
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
contents: handleContents(contents),
gallery:
gallery
?.map(({ image }) => image)
.flatMap((image) => (isValidPayloadImage(image) ? [image] : [])) ?? [],
gallery: handleGallery(gallery),
scans: handleScans(collectible.scans),
nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital,
parentPages: [], // TODO: todo
parentPages: handleParentPages({collectibles: parentItems, folders}),
subitems: isPayloadArrayType(subitems) ? subitems.map(convertCollectibleToPreview) : [],
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 = (
contents: Collectible["contents"]
): EndpointCollectible["contents"] => {
@ -65,12 +173,20 @@ export const handleContents = (
switch (content.relationTo) {
case "generic-contents":
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;
case "pages":
return isPayloadType(content.value)
? { relationTo: "pages", value: content.value }
? { relationTo: "pages", value: convertPageToPreview(content.value) }
: undefined;
default:

View File

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

View File

@ -23,6 +23,7 @@ const fields = {
type: "type",
authors: "authors",
thumbnail: "thumbnail",
backgroundImage: "backgroundImage",
translations: "translations",
tags: "tags",
sourceLanguage: "sourceLanguage",
@ -71,22 +72,33 @@ export const Pages = buildVersionedCollectionConfig({
},
endpoints: [getBySlugEndpoint],
fields: [
{
name: fields.type,
type: "radio",
required: true,
defaultValue: PageType.Generic,
options: Object.entries(PageType).map(([_, value]) => ({
label: value,
value: value,
})),
},
rowField([
slugField({ name: fields.slug }),
{
name: fields.type,
type: "radio",
required: true,
defaultValue: PageType.Generic,
options: Object.entries(PageType).map(([_, value]) => ({
label: value,
value: value,
})),
},
]),
rowField([
imageField({
name: fields.thumbnail,
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([
tagsField({ name: fields.tags }),

View File

@ -6,59 +6,49 @@ import {
isNodeBlockNode,
} from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointPage, ParentPage, TableOfContentEntry } from "../../../sdk";
import { EndpointPage, EndpointPagePreview, TableOfContentEntry } from "../../../sdk";
import { Page } from "../../../types/collections";
import { isPayloadArrayType, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertTagsToGroups } from "../../../utils/endpoints";
import { convertTagsToGroups, handleParentPages } from "../../../utils/endpoints";
export const getBySlugEndpoint = createGetByEndpoint(
Collections.Pages,
"slug",
({
authors,
slug,
translations,
tags,
thumbnail,
_status,
collectibles,
folders,
type,
}: Page): EndpointPage => ({
slug,
type: type as PageType,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsToGroups(tags),
translations: translations.map(
({
content,
language,
sourceLanguage,
title,
pretitle,
subtitle,
proofreaders,
summary,
transcribers,
translators,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(pretitle ? { pretitle } : {}),
title,
...(subtitle ? { subtitle } : {}),
...(summary ? { summary } : {}),
content: handleContent(content),
toc: handleToc(content),
translators: isPayloadArrayType(translators) ? translators.map(({ id }) => id) : [],
transcribers: isPayloadArrayType(transcribers) ? transcribers.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 }),
})
(page: Page): EndpointPage => {
const { translations, collectibles, folders, backgroundImage } = page;
return {
...convertPageToPreview(page),
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
translations: translations.map(
({
content,
language,
sourceLanguage,
title,
pretitle,
subtitle,
proofreaders,
summary,
transcribers,
translators,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(pretitle ? { pretitle } : {}),
title,
...(subtitle ? { subtitle } : {}),
...(summary ? { summary } : {}),
content: handleContent(content),
toc: handleToc(content),
translators: isPayloadArrayType(translators) ? translators.map(({ id }) => id) : [],
transcribers: isPayloadArrayType(transcribers) ? transcribers.map(({ id }) => id) : [],
proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(({ id }) => id) : [],
})
),
parentPages: handleParentPages({ collectibles, folders }),
};
}
);
const handleContent = (
@ -98,40 +88,27 @@ const handleToc = (content: RichTextContent, parentPrefix = ""): TableOfContentE
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 }) => {
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;
};
export const convertPageToPreview = ({
authors,
slug,
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,
...(pretitle ? { pretitle } : {}),
title,
...(subtitle ? { subtitle } : {}),
})),
authors: isPayloadArrayType(authors) ? authors.map(({ id }) => id) : [],
status: _status === "published" ? "published" : "draft",
});

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { TagGroup } from "../sdk";
import { Tag } from "../types/collections";
import { Collections } from "../constants";
import { ParentPage, TagGroup } from "../sdk";
import { Collectible, Folder, Tag } from "../types/collections";
import { isPayloadArrayType, isPayloadType } from "./asserts";
export const convertTagsToGroups = (tags: (string | Tag)[] | null | undefined): TagGroup[] => {
@ -26,3 +27,45 @@ export const convertTagsToGroups = (tags: (string | Tag)[] | null | undefined):
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;
};