Move from using parentPages to backlinks

This commit is contained in:
DrMint 2024-07-21 10:24:14 +02:00
parent 8cd8a67778
commit 5acaf65ade
31 changed files with 555 additions and 566 deletions

24
package-lock.json generated
View File

@ -10,7 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/vollkorn": "5.0.20", "@fontsource/vollkorn": "5.0.20",
"@iconify-json/material-symbols": "^1.1.84", "@iconify-json/material-symbols": "^1.1.85",
"@payloadcms/bundler-webpack": "1.0.7", "@payloadcms/bundler-webpack": "1.0.7",
"@payloadcms/db-mongodb": "1.6.0", "@payloadcms/db-mongodb": "1.6.0",
"@payloadcms/richtext-lexical": "0.11.2", "@payloadcms/richtext-lexical": "0.11.2",
@ -19,9 +19,10 @@
"language-tags": "1.0.9", "language-tags": "1.0.9",
"luxon": "3.4.4", "luxon": "3.4.4",
"payload": "2.24.0", "payload": "2.24.0",
"payloadcms-relationships": "github:DrMint/payloadcms-relationships",
"payloadcms-sftp-storage": "1.0.1", "payloadcms-sftp-storage": "1.0.1",
"sharp": "0.33.4", "sharp": "0.33.4",
"styled-components": "6.1.11" "styled-components": "6.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "4.17.21", "@types/express": "4.17.21",
@ -1962,9 +1963,9 @@
} }
}, },
"node_modules/@iconify-json/material-symbols": { "node_modules/@iconify-json/material-symbols": {
"version": "1.1.84", "version": "1.1.85",
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.1.84.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.1.85.tgz",
"integrity": "sha512-Vio0Ns2rzdTsushHGBiyAcIIbGNWXI6nTNsrVqcq1EEPkfNkuchufBMvErs9vdc1vN1Fj7Ul3uWA3r988TIvfQ==", "integrity": "sha512-GJXTScAIdaxxMPcp6GCd4qbntvHpG9UrF/2V03PMUuD7+1fMU5vHG5w0IGDdvqOnI9HpEcUFa7CFDVQHOpBeDA==",
"dependencies": { "dependencies": {
"@iconify/types": "*" "@iconify/types": "*"
} }
@ -9055,6 +9056,13 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/payloadcms-relationships": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/DrMint/payloadcms-relationships.git#aa65c94f14fa36abe1b482a56fd82d4df3cbfb3e",
"dependencies": {
"payload": "^2.24.0"
}
},
"node_modules/payloadcms-sftp-storage": { "node_modules/payloadcms-sftp-storage": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/payloadcms-sftp-storage/-/payloadcms-sftp-storage-1.0.1.tgz", "resolved": "https://registry.npmjs.org/payloadcms-sftp-storage/-/payloadcms-sftp-storage-1.0.1.tgz",
@ -11545,9 +11553,9 @@
} }
}, },
"node_modules/styled-components": { "node_modules/styled-components": {
"version": "6.1.11", "version": "6.1.12",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz",
"integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==",
"dependencies": { "dependencies": {
"@emotion/is-prop-valid": "1.2.2", "@emotion/is-prop-valid": "1.2.2",
"@emotion/unitless": "0.8.1", "@emotion/unitless": "0.8.1",

View File

@ -22,7 +22,7 @@
}, },
"dependencies": { "dependencies": {
"@fontsource/vollkorn": "5.0.20", "@fontsource/vollkorn": "5.0.20",
"@iconify-json/material-symbols": "^1.1.84", "@iconify-json/material-symbols": "^1.1.85",
"@payloadcms/bundler-webpack": "1.0.7", "@payloadcms/bundler-webpack": "1.0.7",
"@payloadcms/db-mongodb": "1.6.0", "@payloadcms/db-mongodb": "1.6.0",
"@payloadcms/richtext-lexical": "0.11.2", "@payloadcms/richtext-lexical": "0.11.2",
@ -31,9 +31,10 @@
"language-tags": "1.0.9", "language-tags": "1.0.9",
"luxon": "3.4.4", "luxon": "3.4.4",
"payload": "2.24.0", "payload": "2.24.0",
"payloadcms-relationships": "github:DrMint/payloadcms-relationships",
"payloadcms-sftp-storage": "1.0.1", "payloadcms-sftp-storage": "1.0.1",
"sharp": "0.33.4", "sharp": "0.33.4",
"styled-components": "6.1.11" "styled-components": "6.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "4.17.21", "@types/express": "4.17.21",

View File

@ -7,6 +7,7 @@ import {
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage, convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC, convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId, getLanguageId,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
@ -15,6 +16,7 @@ import {
EndpointAudioPreview, EndpointAudioPreview,
EndpointAudio, EndpointAudio,
} from "../../../shared/payload/endpoint-types"; } from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = { export const getByID: CollectionEndpoint = {
method: "get", method: "get",
@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = {
return res.sendStatus(404); return res.sendStatus(404);
} }
return res.status(200).json(convertAudioToEndpointAudio(result)); return res.status(200).json(await convertAudioToEndpointAudio(result));
} catch { } catch {
return res.sendStatus(404); return res.sendStatus(404);
} }
@ -79,8 +81,8 @@ export const convertAudioToEndpointAudioPreview = ({
: {}), : {}),
}); });
const convertAudioToEndpointAudio = (audio: Audio & PayloadMedia): EndpointAudio => { const convertAudioToEndpointAudio = async (audio: Audio & PayloadMedia): Promise<EndpointAudio> => {
const { translations, createdAt, updatedAt, filesize, credits } = audio; const { translations, createdAt, updatedAt, filesize, credits, id } = audio;
return { return {
...convertAudioToEndpointAudioPreview(audio), ...convertAudioToEndpointAudioPreview(audio),
createdAt, createdAt,
@ -95,5 +97,8 @@ const convertAudioToEndpointAudio = (audio: Audio & PayloadMedia): EndpointAudio
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [], })) ?? [],
credits: convertCreditsToEndpointCredits(credits), credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Audios, id)
),
}; };
}; };

View File

@ -6,7 +6,11 @@ import { convertCreditsToEndpointCredits, getDomainFromUrl } from "../../../util
import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint"; import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint"; import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
import { EndpointChronologyEvent, EndpointSource } from "../../../shared/payload/endpoint-types"; import {
EndpointChronologyEvent,
EndpointCollectibleRelationRange,
EndpointRelation,
} from "../../../shared/payload/endpoint-types";
export const getAllEndpoint: CollectionEndpoint = { export const getAllEndpoint: CollectionEndpoint = {
method: "get", method: "get",
@ -48,13 +52,13 @@ export const getAllEndpoint: CollectionEndpoint = {
if (aDay !== bDay) return aDay - bDay; if (aDay !== bDay) return aDay - bDay;
return 0; return 0;
}) })
.map<EndpointChronologyEvent>(eventToEndpointEvent); .map<EndpointChronologyEvent>(convertEventToEndpointEvent);
res.status(200).json(events); res.status(200).json(events);
}, },
}; };
export const eventToEndpointEvent = ({ export const convertEventToEndpointEvent = ({
date: { year, day, month }, date: { year, day, month },
events, events,
id, id,
@ -80,24 +84,26 @@ export const eventToEndpointEvent = ({
})), })),
}); });
const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): EndpointSource[] => { const handleSources = (
sources: ChronologyEvent["events"][number]["sources"]
): EndpointRelation[] => {
return ( return (
sources?.flatMap<EndpointSource>((source) => { sources?.flatMap<EndpointRelation>((source) => {
switch (source.blockType) { switch (source.blockType) {
case "collectibleBlock": case "collectibleBlock":
const range = handleRange(source.range); const range = handleRange(source.range);
if (!isPayloadType(source.collectible)) return []; if (!isPayloadType(source.collectible)) return [];
return { return {
type: "collectible", type: Collections.Collectibles,
collectible: convertCollectibleToEndpointCollectiblePreview(source.collectible), value: convertCollectibleToEndpointCollectiblePreview(source.collectible),
...(isDefined(range) ? { range } : {}), ...(isDefined(range) ? { range } : {}),
}; };
case "pageBlock": case "pageBlock":
if (!isPayloadType(source.page)) return []; if (!isPayloadType(source.page)) return [];
return { return {
type: "page", type: Collections.Pages,
page: convertPageToEndpointPagePreview(source.page), value: convertPageToEndpointPagePreview(source.page),
}; };
case "urlBlock": case "urlBlock":
@ -113,7 +119,7 @@ const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): E
const handleRange = ( const handleRange = (
rawRange: CollectibleBlock["range"] rawRange: CollectibleBlock["range"]
): Extract<EndpointSource, { type: "collectible" }>["range"] => { ): EndpointCollectibleRelationRange | undefined => {
const range = rawRange?.[0]; const range = rawRange?.[0];
switch (range?.blockType) { switch (range?.blockType) {

View File

@ -1,6 +1,6 @@
import payload from "payload"; import payload from "payload";
import { CollectionEndpoint } from "../../../types/payload"; import { CollectionEndpoint } from "../../../types/payload";
import { eventToEndpointEvent } from "./getAllEndpoint"; import { convertEventToEndpointEvent } from "./getAllEndpoint";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
export const getByID: CollectionEndpoint = { export const getByID: CollectionEndpoint = {
@ -27,7 +27,7 @@ export const getByID: CollectionEndpoint = {
id: req.params.id, id: req.params.id,
}); });
return res.status(200).json(eventToEndpointEvent(result)); return res.status(200).json(convertEventToEndpointEvent(result));
} catch { } catch {
return res.sendStatus(404); return res.sendStatus(404);
} }

View File

@ -1,7 +1,5 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types"; import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { Where } from "payload/types";
import { attributesField } from "../../fields/attributesField/attributesField"; import { attributesField } from "../../fields/attributesField/attributesField";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { componentField } from "../../fields/componentField/componentField"; import { componentField } from "../../fields/componentField/componentField";
import { creditsField } from "../../fields/creditsField/creditsField"; import { creditsField } from "../../fields/creditsField/creditsField";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
@ -11,8 +9,6 @@ import { translatedFields } from "../../fields/translatedFields/translatedFields
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping"; import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { Collectible } from "../../types/collections";
import { isPayloadType } from "../../utils/asserts";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { RowLabel } from "./components/RowLabel"; import { RowLabel } from "./components/RowLabel";
@ -695,42 +691,9 @@ export const Collectibles = buildVersionedCollectionConfig({
}, },
], ],
}, },
rowField([
backPropagationField({
name: fields.folders,
relationTo: Collections.Folders,
hasMany: true,
where: ({ id }) => ({
and: [
{ "files.value": { equals: id } },
{ "files.relationTo": { equals: Collections.Collectibles } },
] as Where[],
}),
admin: {
description: `You can go to the "Folders" collection to include this collectible in a folder.`,
},
}),
backPropagationField({
name: fields.parentItems,
relationTo: Collections.Collectibles,
hasMany: true,
where: ({ id }) => ({ [fields.subitems]: { equals: id } }),
}),
]),
], ],
}, },
], ],
}, },
], ],
custom: {
getBackPropagatedRelationships: ({ subitems, contents }: Collectible) => {
const result: string[] = [];
subitems?.forEach((subitem) => result.push(isPayloadType(subitem) ? subitem.id : subitem));
contents?.forEach(({ content: { relationTo, value } }) => {
if (relationTo === "pages") result.push(isPayloadType(value) ? value.id : value);
});
return result;
},
},
}); });

View File

@ -20,8 +20,8 @@ import {
import { import {
convertAttributesToEndpointAttributes, convertAttributesToEndpointAttributes,
convertImageToEndpointPayloadImage, convertImageToEndpointPayloadImage,
convertRelationshipsToEndpointRelations,
convertScanToEndpointScanImage, convertScanToEndpointScanImage,
convertSourceToEndpointSource,
getDomainFromUrl, getDomainFromUrl,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID"; import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID";
@ -29,12 +29,13 @@ import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID"
import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint"; import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID"; import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID";
import { convertVideoToEndpointVideoPreview } from "../../Videos/endpoints/getByID"; import { convertVideoToEndpointVideoPreview } from "../../Videos/endpoints/getByID";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getBySlugEndpoint = createGetByEndpoint({ export const getBySlugEndpoint = createGetByEndpoint({
collection: Collections.Collectibles, collection: Collections.Collectibles,
attribute: "slug", attribute: "slug",
depth: 3, depth: 3,
handler: (collectible) => convertCollectibleToEndpointCollectible(collectible), handler: async (collectible) => await convertCollectibleToEndpointCollectible(collectible),
}); });
export const convertCollectibleToEndpointCollectiblePreview = ({ export const convertCollectibleToEndpointCollectiblePreview = ({
@ -64,8 +65,11 @@ export const convertCollectibleToEndpointCollectiblePreview = ({
...handlePrice(price, priceEnabled), ...handlePrice(price, priceEnabled),
}); });
const convertCollectibleToEndpointCollectible = (collectible: Collectible): EndpointCollectible => { const convertCollectibleToEndpointCollectible = async (
collectible: Collectible
): Promise<EndpointCollectible> => {
const { const {
id,
nature, nature,
urls, urls,
subitems, subitems,
@ -80,8 +84,6 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp
weightEnabled, weightEnabled,
pageInfo, pageInfo,
pageInfoEnabled, pageInfoEnabled,
parentItems,
folders,
backgroundImage, backgroundImage,
translations, translations,
scans: rawScans, scans: rawScans,
@ -128,7 +130,9 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp
...(isPayloadType(updatedBy) ...(isPayloadType(updatedBy)
? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) } ? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) }
: {}), : {}),
parentPages: convertSourceToEndpointSource({ collectibles: parentItems, folders }), backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Collectibles, id)
),
}; };
}; };

View File

@ -2,10 +2,8 @@ import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
import { EndpointCollectibleGallery } from "../../../shared/payload/endpoint-types"; import { EndpointCollectibleGallery } from "../../../shared/payload/endpoint-types";
import { isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts"; import { isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts";
import { import { convertImageToEndpointPayloadImage } from "../../../utils/endpoints";
convertImageToEndpointPayloadImage, import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint";
convertSourceToEndpointSource,
} from "../../../utils/endpoints";
export const getBySlugEndpointGallery = createGetByEndpoint({ export const getBySlugEndpointGallery = createGetByEndpoint({
collection: Collections.Collectibles, collection: Collections.Collectibles,
@ -29,7 +27,12 @@ export const getBySlugEndpointGallery = createGetByEndpoint({
gallery?.flatMap(({ image }) => gallery?.flatMap(({ image }) =>
isImage(image) ? convertImageToEndpointPayloadImage(image) : [] isImage(image) ? convertImageToEndpointPayloadImage(image) : []
) ?? [], ) ?? [],
parentPages: convertSourceToEndpointSource({ collectibles: [collectible] }), backlinks: [
{
type: Collections.Collectibles,
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
}; };
}, },
}); });

View File

@ -2,10 +2,10 @@ import payload from "payload";
import { Collectible, Image } from "../../../types/collections"; import { Collectible, Image } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload"; import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts"; import { isDefined, isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts";
import { convertSourceToEndpointSource } from "../../../utils/endpoints";
import { convertImageToEndpointImage } from "../../Images/endpoints/getByID"; import { convertImageToEndpointImage } from "../../Images/endpoints/getByID";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
import { EndpointCollectibleGalleryImage } from "../../../shared/payload/endpoint-types"; import { EndpointCollectibleGalleryImage } from "../../../shared/payload/endpoint-types";
import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint";
export const getBySlugEndpointGalleryImage: CollectionEndpoint = { export const getBySlugEndpointGalleryImage: CollectionEndpoint = {
path: "/slug/:slug/gallery/:index", path: "/slug/:slug/gallery/:index",
@ -51,8 +51,7 @@ export const getBySlugEndpointGalleryImage: CollectionEndpoint = {
const nextIndex = getNextIndex(index, collectible.gallery); const nextIndex = getNextIndex(index, collectible.gallery);
const scanPage: EndpointCollectibleGalleryImage = { const scanPage: EndpointCollectibleGalleryImage = {
image: convertImageToEndpointImage(image), image: await convertImageToEndpointImage(image),
parentPages: convertSourceToEndpointSource({ gallery: [collectible] }),
slug, slug,
translations: translations:
collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({ collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({
@ -64,6 +63,12 @@ export const getBySlugEndpointGalleryImage: CollectionEndpoint = {
})) ?? [], })) ?? [],
...(isDefined(previousIndex) ? { previousIndex } : {}), ...(isDefined(previousIndex) ? { previousIndex } : {}),
...(isDefined(nextIndex) ? { nextIndex } : {}), ...(isDefined(nextIndex) ? { nextIndex } : {}),
backlinks: [
{
type: Collections.Collectibles,
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
}; };
res.status(200).send(scanPage); res.status(200).send(scanPage);

View File

@ -2,12 +2,10 @@ import payload from "payload";
import { Collectible, Scan } from "../../../types/collections"; import { Collectible, Scan } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload"; import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isNotEmpty, isPayloadType, isScan } from "../../../utils/asserts"; import { isDefined, isNotEmpty, isPayloadType, isScan } from "../../../utils/asserts";
import { import { convertScanToEndpointScanImage } from "../../../utils/endpoints";
convertScanToEndpointScanImage,
convertSourceToEndpointSource,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
import { EndpointCollectibleScanPage } from "../../../shared/payload/endpoint-types"; import { EndpointCollectibleScanPage } from "../../../shared/payload/endpoint-types";
import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint";
export const getBySlugEndpointScanPage: CollectionEndpoint = { export const getBySlugEndpointScanPage: CollectionEndpoint = {
path: "/slug/:slug/scans/:index", path: "/slug/:slug/scans/:index",
@ -54,7 +52,6 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = {
const scanPage: EndpointCollectibleScanPage = { const scanPage: EndpointCollectibleScanPage = {
image: convertScanToEndpointScanImage(scan, index), image: convertScanToEndpointScanImage(scan, index),
parentPages: convertSourceToEndpointSource({ scans: [collectible] }),
slug, slug,
translations: translations:
collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({ collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({
@ -66,6 +63,12 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = {
})) ?? [], })) ?? [],
...(isDefined(previousIndex) ? { previousIndex } : {}), ...(isDefined(previousIndex) ? { previousIndex } : {}),
...(isDefined(nextIndex) ? { nextIndex } : {}), ...(isDefined(nextIndex) ? { nextIndex } : {}),
backlinks: [
{
type: Collections.Collectibles,
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
}; };
res.status(200).send(scanPage); res.status(200).send(scanPage);

View File

@ -7,8 +7,8 @@ import {
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertImageToEndpointPayloadImage, convertImageToEndpointPayloadImage,
convertScanToEndpointScanImage, convertScanToEndpointScanImage,
convertSourceToEndpointSource,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint";
export const getBySlugEndpointScans = createGetByEndpoint({ export const getBySlugEndpointScans = createGetByEndpoint({
collection: Collections.Collectibles, collection: Collections.Collectibles,
@ -29,7 +29,12 @@ export const getBySlugEndpointScans = createGetByEndpoint({
})) ?? [], })) ?? [],
...(isImage(thumbnail) ? { thumbnail: convertImageToEndpointPayloadImage(thumbnail) } : {}), ...(isImage(thumbnail) ? { thumbnail: convertImageToEndpointPayloadImage(thumbnail) } : {}),
...(scansEnabled && scans ? handleScans(scans) : { credits: [], pages: [] }), ...(scansEnabled && scans ? handleScans(scans) : { credits: [], pages: [] }),
parentPages: convertSourceToEndpointSource({ collectibles: [collectible] }), backlinks: [
{
type: Collections.Collectibles,
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
}; };
}, },
}); });

View File

@ -7,6 +7,7 @@ import {
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage, convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC, convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId, getLanguageId,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
@ -15,6 +16,7 @@ import {
EndpointFilePreview, EndpointFilePreview,
EndpointFile, EndpointFile,
} from "../../../shared/payload/endpoint-types"; } from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = { export const getByID: CollectionEndpoint = {
method: "get", method: "get",
@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = {
return res.sendStatus(404); return res.sendStatus(404);
} }
return res.status(200).json(convertFileToEndpointFile(result)); return res.status(200).json(await convertFileToEndpointFile(result));
} catch { } catch {
return res.sendStatus(404); return res.sendStatus(404);
} }
@ -79,8 +81,8 @@ export const convertFileToEndpointFilePreview = ({
: {}), : {}),
}); });
const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => { const convertFileToEndpointFile = async (file: File & PayloadMedia): Promise<EndpointFile> => {
const { translations, createdAt, updatedAt, filesize, credits } = file; const { translations, createdAt, updatedAt, filesize, credits, id } = file;
return { return {
...convertFileToEndpointFilePreview(file), ...convertFileToEndpointFilePreview(file),
@ -96,5 +98,8 @@ const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => {
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [], })) ?? [],
credits: convertCreditsToEndpointCredits(credits), credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Files, id)
),
}; };
}; };

View File

@ -1,11 +1,8 @@
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { iconField } from "../../fields/iconField/iconField"; import { iconField } from "../../fields/iconField/iconField";
import { rowField } from "../../fields/rowField/rowField"; import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { Collections, CollectionGroups } from "../../shared/payload/constants"; import { Collections, CollectionGroups } from "../../shared/payload/constants";
import { Folder } from "../../types/collections";
import { isPayloadType } from "../../utils/asserts";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint"; import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint";
@ -44,16 +41,7 @@ export const Folders = buildCollectionConfig({
}, },
endpoints: [getBySlugEndpoint], endpoints: [getBySlugEndpoint],
fields: [ fields: [
rowField([ rowField([slugField({ name: fields.slug }), iconField({ name: fields.icon })]),
slugField({ name: fields.slug }),
iconField({ name: fields.icon }),
backPropagationField({
name: fields.parentFolders,
relationTo: Collections.Folders,
hasMany: true,
where: ({ id }) => ({ "sections.subfolders": { equals: id } }),
}),
]),
translatedFields({ translatedFields({
name: fields.translations, name: fields.translations,
admin: { useAsTitle: fields.translationsName }, admin: { useAsTitle: fields.translationsName },
@ -112,21 +100,4 @@ export const Folders = buildCollectionConfig({
hasMany: true, hasMany: true,
}, },
], ],
custom: {
getBackPropagatedRelationships: ({ files, sections }: Folder) => {
const result: string[] = [];
files?.forEach(({ relationTo, value }) => {
if (relationTo === "collectibles" || relationTo === "pages") {
result.push(isPayloadType(value) ? value.id : value);
}
});
sections?.forEach(({ subfolders }) =>
subfolders?.forEach((folder) => {
result.push(isPayloadType(folder) ? folder.id : folder);
})
);
return result;
},
},
}); });

View File

@ -1,4 +1,5 @@
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { findIncomingRelationships } from "payloadcms-relationships";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
import { EndpointFolderPreview, EndpointFolder } from "../../../shared/payload/endpoint-types"; import { EndpointFolderPreview, EndpointFolder } from "../../../shared/payload/endpoint-types";
import { Folder, Language } from "../../../types/collections"; import { Folder, Language } from "../../../types/collections";
@ -12,7 +13,7 @@ import {
isPublished, isPublished,
isVideo, isVideo,
} from "../../../utils/asserts"; } from "../../../utils/asserts";
import { convertSourceToEndpointSource, getLanguageId } from "../../../utils/endpoints"; import { convertRelationshipsToEndpointRelations, getLanguageId } from "../../../utils/endpoints";
import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID"; import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID";
import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint"; import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID"; import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID";
@ -43,8 +44,8 @@ export const convertFolderToEndpointFolderPreview = ({
})) ?? [], })) ?? [],
}); });
const convertFolderToEndpointFolder = (folder: Folder): EndpointFolder => { const convertFolderToEndpointFolder = async (folder: Folder): Promise<EndpointFolder> => {
const { translations, sections, files, parentFolders } = folder; const { translations, sections, files, id } = folder;
return { return {
...convertFolderToEndpointFolderPreview(folder), ...convertFolderToEndpointFolderPreview(folder),
@ -116,7 +117,9 @@ const convertFolderToEndpointFolder = (folder: Folder): EndpointFolder => {
return []; return [];
} }
}) ?? [], }) ?? [],
parentPages: convertSourceToEndpointSource({ folders: parentFolders }), backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Folders, id)
),
}; };
}; };

View File

@ -6,6 +6,7 @@ import {
convertAttributesToEndpointAttributes, convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertRTCToEndpointRTC, convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
convertSizesToPayloadImages, convertSizesToPayloadImages,
getLanguageId, getLanguageId,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
@ -15,6 +16,7 @@ import {
EndpointImagePreview, EndpointImagePreview,
EndpointImage, EndpointImage,
} from "../../../shared/payload/endpoint-types"; } from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = { export const getByID: CollectionEndpoint = {
method: "get", method: "get",
@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = {
return res.sendStatus(404); return res.sendStatus(404);
} }
return res.status(200).json(convertImageToEndpointImage(result)); return res.status(200).json(await convertImageToEndpointImage(result));
} catch { } catch {
return res.sendStatus(404); return res.sendStatus(404);
} }
@ -93,8 +95,10 @@ export const convertImageToEndpointImagePreview = ({
...(isPayloadImage(sizes?.og) ? { openGraph: sizes.og } : {}), ...(isPayloadImage(sizes?.og) ? { openGraph: sizes.og } : {}),
}); });
export const convertImageToEndpointImage = (image: Image & PayloadImage): EndpointImage => { export const convertImageToEndpointImage = async (
const { translations, createdAt, updatedAt, filesize, credits } = image; image: Image & PayloadImage
): Promise<EndpointImage> => {
const { translations, createdAt, updatedAt, filesize, credits, id } = image;
return { return {
...convertImageToEndpointImagePreview(image), ...convertImageToEndpointImagePreview(image),
createdAt, createdAt,
@ -109,5 +113,8 @@ export const convertImageToEndpointImage = (image: Image & PayloadImage): Endpoi
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [], })) ?? [],
credits: convertCreditsToEndpointCredits(credits), credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Images, id)
),
}; };
}; };

View File

@ -1,10 +1,8 @@
import { Where } from "payload/types";
import { breakBlock } from "../../blocks/breakBlock"; import { breakBlock } from "../../blocks/breakBlock";
import { sectionBlock } from "../../blocks/sectionBlock"; import { sectionBlock } from "../../blocks/sectionBlock";
import { transcriptBlock } from "../../blocks/transcriptBlock"; import { transcriptBlock } from "../../blocks/transcriptBlock";
import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters"; import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters";
import { attributesField } from "../../fields/attributesField/attributesField"; import { attributesField } from "../../fields/attributesField/attributesField";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { creditsField } from "../../fields/creditsField/creditsField"; import { creditsField } from "../../fields/creditsField/creditsField";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField"; import { rowField } from "../../fields/rowField/rowField";
@ -125,29 +123,5 @@ export const Pages = buildVersionedCollectionConfig({
creditsField({ name: fields.credits }), creditsField({ name: fields.credits }),
], ],
}), }),
rowField([
backPropagationField({
name: fields.folders,
relationTo: Collections.Folders,
hasMany: true,
where: ({ id }) => ({
and: [
{ "files.value": { equals: id } },
{ "files.relationTo": { equals: Collections.Pages } },
] as Where[],
}),
}),
backPropagationField({
name: fields.collectibles,
hasMany: true,
relationTo: Collections.Collectibles,
where: ({ id }) => ({
and: [
{ "contents.content.value": { equals: id } },
{ "contents.content.relationTo": { equals: Collections.Pages } },
] as Where[],
}),
}),
]),
], ],
}); });

View File

@ -1,4 +1,5 @@
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { findIncomingRelationships } from "payloadcms-relationships";
import { Collections, BreakBlockType } from "../../../shared/payload/constants"; import { Collections, BreakBlockType } from "../../../shared/payload/constants";
import { import {
EndpointPagePreview, EndpointPagePreview,
@ -18,7 +19,7 @@ import {
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertImageToEndpointPayloadImage, convertImageToEndpointPayloadImage,
convertRTCToEndpointRTC, convertRTCToEndpointRTC,
convertSourceToEndpointSource, convertRelationshipsToEndpointRelations,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID"; import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID";
@ -49,8 +50,8 @@ export const convertPageToEndpointPagePreview = ({
updatedAt, updatedAt,
}); });
const convertPageToEndpointPage = (page: Page): EndpointPage => { const convertPageToEndpointPage = async (page: Page): Promise<EndpointPage> => {
const { translations, collectibles, folders, backgroundImage, createdAt, updatedBy } = page; const { translations, backgroundImage, createdAt, updatedBy, id } = page;
return { return {
...convertPageToEndpointPagePreview(page), ...convertPageToEndpointPagePreview(page),
@ -74,7 +75,9 @@ const convertPageToEndpointPage = (page: Page): EndpointPage => {
...(isPayloadType(updatedBy) ...(isPayloadType(updatedBy)
? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) } ? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) }
: {}), : {}),
parentPages: convertSourceToEndpointSource({ collectibles, folders }), backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Pages, id)
),
}; };
}; };

View File

@ -5,8 +5,6 @@ import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField"; import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { Collections, CollectionGroups } from "../../shared/payload/constants"; import { Collections, CollectionGroups } from "../../shared/payload/constants";
import { Video } from "../../types/collections";
import { isPayloadType } from "../../utils/asserts";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor"; import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID"; import { getByID } from "./endpoints/getByID";
@ -130,12 +128,4 @@ export const Videos = buildCollectionConfig({
], ],
}), }),
], ],
custom: {
getBackPropagatedRelationships: ({ platform, platformEnabled }: Video) => {
if (!platform || !platformEnabled) {
return [];
}
return [isPayloadType(platform.channel) ? platform.channel.id : platform.channel];
},
},
}); });

View File

@ -15,6 +15,7 @@ import {
convertCreditsToEndpointCredits, convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage, convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC, convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId, getLanguageId,
} from "../../../utils/endpoints"; } from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants"; import { Collections } from "../../../shared/payload/constants";
@ -23,6 +24,7 @@ import {
EndpointVideoPreview, EndpointVideoPreview,
EndpointVideo, EndpointVideo,
} from "../../../shared/payload/endpoint-types"; } from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = { export const getByID: CollectionEndpoint = {
method: "get", method: "get",
@ -52,7 +54,7 @@ export const getByID: CollectionEndpoint = {
return res.sendStatus(404); return res.sendStatus(404);
} }
return res.status(200).json(convertVideoToEndpointVideo(result)); return res.status(200).json(await convertVideoToEndpointVideo(result));
} catch { } catch {
return res.sendStatus(404); return res.sendStatus(404);
} }
@ -98,8 +100,8 @@ export const convertVideoToEndpointVideoPreview = ({
}) ?? [], }) ?? [],
}); });
const convertVideoToEndpointVideo = (video: Video & PayloadMedia): EndpointVideo => { const convertVideoToEndpointVideo = async (video: Video & PayloadMedia): Promise<EndpointVideo> => {
const { translations, createdAt, updatedAt, filesize, platform, platformEnabled, credits } = const { translations, createdAt, updatedAt, filesize, platform, platformEnabled, credits, id } =
video; video;
return { return {
@ -125,5 +127,8 @@ const convertVideoToEndpointVideo = (video: Video & PayloadMedia): EndpointVideo
} }
: {}), : {}),
credits: convertCreditsToEndpointCredits(credits), credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Videos, id)
),
}; };
}; };

View File

@ -1,5 +1,4 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { rowField } from "../../fields/rowField/rowField"; import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups } from "../../shared/payload/constants"; import { Collections, CollectionGroups } from "../../shared/payload/constants";
@ -31,11 +30,5 @@ export const VideosChannels: CollectionConfig = buildCollectionConfig({
{ name: fields.title, type: "text", required: true }, { name: fields.title, type: "text", required: true },
{ name: fields.subscribers, type: "number", required: true }, { name: fields.subscribers, type: "number", required: true },
]), ]),
backPropagationField({
name: fields.videos,
relationTo: Collections.Videos,
hasMany: true,
where: ({ id }) => ({ "platform.channel": { equals: id } }),
}),
], ],
}); });

View File

@ -2,7 +2,6 @@ import { GlobalConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField"; import { rowField } from "../../fields/rowField/rowField";
import { globalAfterChangeWebhook } from "../../hooks/afterOperationWebhook";
import { getConfigEndpoint } from "./endpoints/getConfigEndpoint"; import { getConfigEndpoint } from "./endpoints/getConfigEndpoint";
import { Collections, CollectionGroups } from "../../shared/payload/constants"; import { Collections, CollectionGroups } from "../../shared/payload/constants";
@ -31,9 +30,6 @@ export const WebsiteConfig: GlobalConfig = {
}, },
access: { update: mustBeAdmin, read: mustBeAdmin }, access: { update: mustBeAdmin, read: mustBeAdmin },
endpoints: [getConfigEndpoint], endpoints: [getConfigEndpoint],
hooks: {
afterChange: [globalAfterChangeWebhook],
},
fields: [ fields: [
rowField([ rowField([
{ {

View File

@ -124,7 +124,7 @@ export const getAllSDKUrlsEndpoint: Endpoint = {
}, },
}; };
export const getSDKUrlsForDocument = (collection: Collections, doc: any): string[] => { const getSDKUrlsForDocument = (collection: Collections, doc: any): string[] => {
switch (collection) { switch (collection) {
case Collections.WebsiteConfig: case Collections.WebsiteConfig:
return [getSDKEndpoint.getConfigEndpoint()]; return [getSDKEndpoint.getConfigEndpoint()];

View File

@ -1,51 +0,0 @@
import payload, { GeneratedTypes } from "payload";
import { FieldBase, SingleRelationshipField } from "payload/dist/fields/config/types";
import { Where } from "payload/types";
import { isEmpty } from "../../utils/asserts";
type BackPropagationField = FieldBase & {
where: (data: any) => Where;
relationTo: keyof GeneratedTypes["collections"];
hasMany?: boolean;
};
export const backPropagationField = ({
admin,
hooks: { beforeChange = [], afterRead = [], ...otherHooks } = {},
where,
hasMany = false,
...params
}: BackPropagationField): SingleRelationshipField => ({
...params,
type: "relationship",
hasMany: hasMany,
admin: { ...admin, readOnly: true },
hooks: {
...otherHooks,
beforeChange: [
...beforeChange,
({ siblingData }) => {
delete siblingData[params.name];
},
],
afterRead: [
...afterRead,
async ({ data, context }) => {
if (isEmpty(data?.id) || context.stopPropagation) {
return hasMany ? [] : undefined;
}
const result = await payload.find({
collection: params.relationTo,
where: where(data),
limit: 100,
depth: 0,
context: { stopPropagation: true },
});
if (hasMany) {
return result.docs.map((doc) => doc.id);
} else {
return result.docs[0]?.id;
}
},
],
},
});

View File

@ -1,47 +0,0 @@
import payload, { GeneratedTypes } from "payload";
import { SanitizedCollectionConfig, SanitizedGlobalConfig } from "payload/types";
export const getAddedBackPropagationRelationships = async (
config: SanitizedCollectionConfig | SanitizedGlobalConfig,
doc: any,
previousDoc?: any
): Promise<string[]> => {
if (!("getBackPropagatedRelationships" in config.custom)) {
return [];
}
const getBackPropagatedRelationships: (doc: any) => string[] =
config.custom.getBackPropagatedRelationships;
if (!previousDoc) {
return getBackPropagatedRelationships(doc);
}
let currentIds: string[];
let previousIds: string[];
if (config.versions.drafts) {
const versions = await payload.findVersions({
collection: config.slug as keyof GeneratedTypes["collections"],
sort: "-updatedAt",
limit: 2,
where: {
and: [{ parent: { equals: doc.id } }, { "version._status": { equals: "published" } }],
},
});
const currentVersion = versions.docs[0]?.version;
const previousVersion = versions.docs[1]?.version;
if (!currentVersion) return [];
if (!previousVersion) return getBackPropagatedRelationships(currentVersion);
currentIds = getBackPropagatedRelationships(currentVersion);
previousIds = getBackPropagatedRelationships(previousVersion);
} else {
currentIds = getBackPropagatedRelationships(doc);
previousIds = getBackPropagatedRelationships(previousDoc);
}
return currentIds.filter((id) => !previousIds.includes(id));
};

View File

@ -1,99 +0,0 @@
import {
AfterDeleteHook,
AfterChangeHook as CollectionAfterChangeHook,
} from "payload/dist/collections/config/types";
import { AfterChangeHook as GlobalAfterChangeHook } from "payload/dist/globals/config/types";
import { getSDKUrlsForDocument } from "../endpoints/getAllSDKUrlsEndpoint";
import { getAddedBackPropagationRelationships } from "../fields/backPropagationField/backPropagationUtils";
import { Collections } from "../shared/payload/constants";
import { AfterOperationWebHookMessage } from "../shared/payload/webhooks";
export const globalAfterChangeWebhook: GlobalAfterChangeHook = async ({
global,
doc,
previousDoc,
}) => {
const collection = global.slug as Collections;
await sendWebhookMessage({
collection,
addedDependantIds: await getAddedBackPropagationRelationships(global, doc, previousDoc),
urls: getSDKUrlsForDocument(collection, doc),
});
return doc;
};
export const collectionAfterChangeWebhook: CollectionAfterChangeHook = async ({
collection,
doc,
previousDoc,
operation,
}) => {
const collectionSlug = collection.slug as Collections;
console.log("afterChange", operation, collectionSlug, doc.id);
if ("_status" in doc && doc._status === "draft") {
return doc;
}
if (!("id" in doc)) {
return doc;
}
await sendWebhookMessage({
collection: collectionSlug,
id: doc.id,
addedDependantIds: await getAddedBackPropagationRelationships(collection, doc, previousDoc),
urls: getSDKUrlsForDocument(collectionSlug, doc),
});
return doc;
};
export const afterDeleteWebhook: AfterDeleteHook = async ({ collection, doc }) => {
const collectionSlug = collection.slug as Collections;
console.log("afterDelete", collection.slug, doc.id);
if (!("id" in doc)) {
return doc;
}
await sendWebhookMessage({
collection: collectionSlug,
id: doc.id,
addedDependantIds: [],
urls: getSDKUrlsForDocument(collectionSlug, doc),
});
return doc;
};
const webhookTargets: { url: string; token: string }[] = [
{
url: process.env.WEB_SERVER_HOOK_URL ?? "",
token: process.env.WEB_SERVER_HOOK_TOKEN ?? "",
},
{
url: process.env.MEILISEARCH_HOOK_URL ?? "",
token: process.env.MEILISEARCH_HOOK_TOKEN ?? "",
},
];
const sendWebhookMessage = async (message: AfterOperationWebHookMessage) => {
try {
await Promise.all(
webhookTargets.flatMap(({ url, token }) => {
if (!url) return;
return fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(message),
method: "POST",
});
})
);
} catch (e) {
console.warn("Error while sending webhook", e);
}
};

View File

@ -31,6 +31,9 @@ import { getAllIds } from "./endpoints/getAllIdsEndpoint";
import { getAllSDKUrlsEndpoint } from "./endpoints/getAllSDKUrlsEndpoint"; import { getAllSDKUrlsEndpoint } from "./endpoints/getAllSDKUrlsEndpoint";
import { createEditor } from "./utils/editor"; import { createEditor } from "./utils/editor";
import { Collections } from "./shared/payload/constants"; import { Collections } from "./shared/payload/constants";
import { relationshipsPlugin } from "payloadcms-relationships";
import { shownOnlyToAdmin } from "./accesses/collections/shownOnlyToAdmin";
import { mustBeAdmin } from "./accesses/fields/mustBeAdmin";
const configuredSftpAdapter = sftpAdapter({ const configuredSftpAdapter = sftpAdapter({
connectOptions: { connectOptions: {
@ -96,6 +99,19 @@ export default buildConfig({
skip: () => true, skip: () => true,
}, },
plugins: [ plugins: [
relationshipsPlugin({
collectionConfig: {
admin: {
hidden: shownOnlyToAdmin,
},
access: {
update: mustBeAdmin,
create: mustBeAdmin,
delete: mustBeAdmin,
},
},
}),
cloudStorage({ cloudStorage({
collections: { collections: {
[Collections.Videos]: { [Collections.Videos]: {

View File

@ -2,7 +2,7 @@ import "dotenv/config";
import express from "express"; import express from "express";
import path from "path"; import path from "path";
import payload from "payload"; import payload from "payload";
import { isDefined, isUndefined } from "./utils/asserts"; import { isUndefined } from "./utils/asserts";
import { Collections, RecordersRoles } from "./shared/payload/constants"; import { Collections, RecordersRoles } from "./shared/payload/constants";
const app = express(); const app = express();
@ -28,29 +28,30 @@ const start = async () => {
express: app, express: app,
onInit: async () => { onInit: async () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`); payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
const recorders = await payload.find({ collection: Collections.Recorders });
// If no recorders, we seed some initial data const seedFirstUser = async () => {
if ( const recorders = await payload.find({ collection: Collections.Recorders });
isDefined(process.env.SEEDING_ADMIN_EMAIL) &&
isDefined(process.env.SEEDING_ADMIN_PASSWORD) &&
isDefined(process.env.SEEDING_ADMIN_USERNAME)
) {
if (recorders.docs.length === 0) {
payload.logger.info("Seeding some initial data");
await payload.create({ if (recorders.docs.length > 0) return;
collection: Collections.Recorders, if (isUndefined(process.env.SEEDING_ADMIN_EMAIL)) return;
data: { if (isUndefined(process.env.SEEDING_ADMIN_PASSWORD)) return;
email: process.env.SEEDING_ADMIN_EMAIL, if (isUndefined(process.env.SEEDING_ADMIN_USERNAME)) return;
password: process.env.SEEDING_ADMIN_PASSWORD,
username: process.env.SEEDING_ADMIN_USERNAME, payload.logger.info("Seeding your first user");
role: [RecordersRoles.Admin, RecordersRoles.Api],
anonymize: false, await payload.create({
}, collection: Collections.Recorders,
}); data: {
} email: process.env.SEEDING_ADMIN_EMAIL,
} password: process.env.SEEDING_ADMIN_PASSWORD,
username: process.env.SEEDING_ADMIN_USERNAME,
role: [RecordersRoles.Admin, RecordersRoles.Api],
anonymize: false,
},
});
};
await seedFirstUser();
}, },
}); });

@ -1 +1 @@
Subproject commit 824f7085f9e0787f97ef9afd11cb5320a2cd6208 Subproject commit 7d6f5ffb704f4ecddfb0e0982ce9eb79d39d450d

View File

@ -40,6 +40,7 @@ export interface Config {
currencies: Currency; currencies: Currency;
wordings: Wording; wordings: Wording;
"generic-contents": GenericContent; "generic-contents": GenericContent;
relationships: Relationship;
"payload-preferences": PayloadPreference; "payload-preferences": PayloadPreference;
"payload-migrations": PayloadMigration; "payload-migrations": PayloadMigration;
}; };
@ -96,8 +97,6 @@ export interface Page {
credits?: Credits; credits?: Credits;
id?: string | null; id?: string | null;
}[]; }[];
folders?: (string | Folder)[] | null;
collectibles?: (string | Collectible)[] | null;
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -352,79 +351,6 @@ export interface Recorder {
lockUntil?: string | null; lockUntil?: string | null;
password?: string | null; password?: string | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "folders".
*/
export interface Folder {
id: string;
slug: string;
icon?: string | null;
parentFolders?: (string | Folder)[] | null;
translations: {
language: string | Language;
name: string;
description?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ("ltr" | "rtl") | null;
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[];
sections?:
| {
translations?:
| {
language: string | Language;
name: string;
id?: string | null;
}[]
| null;
subfolders?: (string | Folder)[] | null;
id?: string | null;
}[]
| null;
files?:
| (
| {
relationTo: "collectibles";
value: string | Collectible;
}
| {
relationTo: "pages";
value: string | Page;
}
| {
relationTo: "videos";
value: string | Video;
}
| {
relationTo: "images";
value: string | Image;
}
| {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "files";
value: string | File;
}
)[]
| null;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collectibles". * via the `definition` "collectibles".
@ -608,8 +534,6 @@ export interface Collectible {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
folders?: (string | Folder)[] | null;
parentItems?: (string | Collectible)[] | null;
updatedBy: string | Recorder; updatedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -951,7 +875,78 @@ export interface VideosChannel {
url: string; url: string;
title: string; title: string;
subscribers: number; subscribers: number;
videos?: (string | Video)[] | null; }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "folders".
*/
export interface Folder {
id: string;
slug: string;
icon?: string | null;
translations: {
language: string | Language;
name: string;
description?: {
root: {
type: string;
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ("ltr" | "rtl") | null;
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[];
sections?:
| {
translations?:
| {
language: string | Language;
name: string;
id?: string | null;
}[]
| null;
subfolders?: (string | Folder)[] | null;
id?: string | null;
}[]
| null;
files?:
| (
| {
relationTo: "collectibles";
value: string | Collectible;
}
| {
relationTo: "pages";
value: string | Page;
}
| {
relationTo: "videos";
value: string | Video;
}
| {
relationTo: "images";
value: string | Image;
}
| {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "files";
value: string | File;
}
)[]
| null;
updatedAt: string;
createdAt: string;
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@ -1082,6 +1077,260 @@ export interface Wording {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "relationships".
*/
export interface Relationship {
id: string;
document:
| {
relationTo: "pages";
value: string | Page;
}
| {
relationTo: "collectibles";
value: string | Collectible;
}
| {
relationTo: "folders";
value: string | Folder;
}
| {
relationTo: "chronology-events";
value: string | ChronologyEvent;
}
| {
relationTo: "images";
value: string | Image;
}
| {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "media-thumbnails";
value: string | MediaThumbnail;
}
| {
relationTo: "videos";
value: string | Video;
}
| {
relationTo: "videos-subtitles";
value: string | VideoSubtitle;
}
| {
relationTo: "videos-channels";
value: string | VideosChannel;
}
| {
relationTo: "files";
value: string | File;
}
| {
relationTo: "scans";
value: string | Scan;
}
| {
relationTo: "tags";
value: string | Tag;
}
| {
relationTo: "attributes";
value: string | Attribute;
}
| {
relationTo: "credits-roles";
value: string | CreditsRole;
}
| {
relationTo: "recorders";
value: string | Recorder;
}
| {
relationTo: "languages";
value: string | Language;
}
| {
relationTo: "currencies";
value: string | Currency;
}
| {
relationTo: "wordings";
value: string | Wording;
}
| {
relationTo: "generic-contents";
value: string | GenericContent;
};
incomingRelations?:
| (
| {
relationTo: "pages";
value: string | Page;
}
| {
relationTo: "collectibles";
value: string | Collectible;
}
| {
relationTo: "folders";
value: string | Folder;
}
| {
relationTo: "chronology-events";
value: string | ChronologyEvent;
}
| {
relationTo: "images";
value: string | Image;
}
| {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "media-thumbnails";
value: string | MediaThumbnail;
}
| {
relationTo: "videos";
value: string | Video;
}
| {
relationTo: "videos-subtitles";
value: string | VideoSubtitle;
}
| {
relationTo: "videos-channels";
value: string | VideosChannel;
}
| {
relationTo: "files";
value: string | File;
}
| {
relationTo: "scans";
value: string | Scan;
}
| {
relationTo: "tags";
value: string | Tag;
}
| {
relationTo: "attributes";
value: string | Attribute;
}
| {
relationTo: "credits-roles";
value: string | CreditsRole;
}
| {
relationTo: "recorders";
value: string | Recorder;
}
| {
relationTo: "languages";
value: string | Language;
}
| {
relationTo: "currencies";
value: string | Currency;
}
| {
relationTo: "wordings";
value: string | Wording;
}
| {
relationTo: "generic-contents";
value: string | GenericContent;
}
)[]
| null;
outgoingRelations: (
| {
relationTo: "pages";
value: string | Page;
}
| {
relationTo: "collectibles";
value: string | Collectible;
}
| {
relationTo: "folders";
value: string | Folder;
}
| {
relationTo: "chronology-events";
value: string | ChronologyEvent;
}
| {
relationTo: "images";
value: string | Image;
}
| {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "media-thumbnails";
value: string | MediaThumbnail;
}
| {
relationTo: "videos";
value: string | Video;
}
| {
relationTo: "videos-subtitles";
value: string | VideoSubtitle;
}
| {
relationTo: "videos-channels";
value: string | VideosChannel;
}
| {
relationTo: "files";
value: string | File;
}
| {
relationTo: "scans";
value: string | Scan;
}
| {
relationTo: "tags";
value: string | Tag;
}
| {
relationTo: "attributes";
value: string | Attribute;
}
| {
relationTo: "credits-roles";
value: string | CreditsRole;
}
| {
relationTo: "recorders";
value: string | Recorder;
}
| {
relationTo: "languages";
value: string | Language;
}
| {
relationTo: "currencies";
value: string | Currency;
}
| {
relationTo: "wordings";
value: string | Wording;
}
| {
relationTo: "generic-contents";
value: string | GenericContent;
}
)[];
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".

View File

@ -1,6 +1,5 @@
import { GeneratedTypes } from "payload"; import { GeneratedTypes } from "payload";
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { afterDeleteWebhook, collectionAfterChangeWebhook } from "../hooks/afterOperationWebhook";
import { formatToPascalCase } from "./string"; import { formatToPascalCase } from "./string";
type CollectionConfigWithPlugins = CollectionConfig; type CollectionConfigWithPlugins = CollectionConfig;
@ -11,18 +10,9 @@ export type BuildCollectionConfig = Omit<
> & { > & {
slug: keyof GeneratedTypes["collections"]; slug: keyof GeneratedTypes["collections"];
labels: { singular: string; plural: string }; labels: { singular: string; plural: string };
custom?: {
getBackPropagatedRelationships?: (object: any) => string[];
[key: string]: unknown;
};
}; };
export const buildCollectionConfig = (config: BuildCollectionConfig): CollectionConfig => ({ export const buildCollectionConfig = (config: BuildCollectionConfig): CollectionConfig => ({
...config, ...config,
typescript: { interface: formatToPascalCase(config.labels.singular) }, typescript: { interface: formatToPascalCase(config.labels.singular) },
hooks: {
...config.hooks,
afterChange: [...(config.hooks?.afterChange ?? []), collectionAfterChangeWebhook],
afterDelete: [...(config.hooks?.afterDelete ?? []), afterDeleteWebhook],
},
}); });

View File

@ -1,18 +1,22 @@
import { convertAudioToEndpointAudioPreview } from "../collections/Audios/endpoints/getByID"; import { convertAudioToEndpointAudioPreview } from "../collections/Audios/endpoints/getByID";
import { convertEventToEndpointEvent } from "../collections/ChronologyEvents/endpoints/getAllEndpoint";
import { convertCollectibleToEndpointCollectiblePreview } from "../collections/Collectibles/endpoints/getBySlugEndpoint";
import { convertFileToEndpointFilePreview } from "../collections/Files/endpoints/getByID";
import { convertFolderToEndpointFolderPreview } from "../collections/Folders/endpoints/getBySlugEndpoint";
import { convertImageToEndpointImagePreview } from "../collections/Images/endpoints/getByID"; import { convertImageToEndpointImagePreview } from "../collections/Images/endpoints/getByID";
import { convertPageToEndpointPagePreview } from "../collections/Pages/endpoints/getBySlugEndpoint";
import { convertRecorderToEndpointRecorderPreview } from "../collections/Recorders/endpoints/getByID"; import { convertRecorderToEndpointRecorderPreview } from "../collections/Recorders/endpoints/getByID";
import { convertVideoToEndpointVideoPreview } from "../collections/Videos/endpoints/getByID"; import { convertVideoToEndpointVideoPreview } from "../collections/Videos/endpoints/getByID";
import { AttributeTypes } from "../shared/payload/constants"; import { AttributeTypes, Collections } from "../shared/payload/constants";
import { import {
EndpointTag, EndpointTag,
EndpointSource,
EndpointSourcePreview,
EndpointRole, EndpointRole,
EndpointCredit, EndpointCredit,
EndpointAttribute, EndpointAttribute,
PayloadImage, PayloadImage,
EndpointScanImage, EndpointScanImage,
EndpointPayloadImage, EndpointPayloadImage,
EndpointRelation,
} from "../shared/payload/endpoint-types"; } from "../shared/payload/endpoint-types";
import { import {
RichTextContent, RichTextContent,
@ -29,14 +33,13 @@ import {
} from "../shared/payload/rich-text"; } from "../shared/payload/rich-text";
import { import {
Audio, Audio,
Collectible,
Credits, Credits,
CreditsRole, CreditsRole,
Folder,
Image, Image,
Language, Language,
MediaThumbnail, MediaThumbnail,
NumberBlock, NumberBlock,
Relationship,
Scan, Scan,
Tag, Tag,
TagsBlock, TagsBlock,
@ -47,12 +50,11 @@ import {
isAudio, isAudio,
isDefined, isDefined,
isEmpty, isEmpty,
isFile,
isImage, isImage,
isNotEmpty,
isPayloadArrayType, isPayloadArrayType,
isPayloadImage, isPayloadImage,
isPayloadType, isPayloadType,
isPublished,
isVideo, isVideo,
} from "./asserts"; } from "./asserts";
@ -139,86 +141,64 @@ export const convertRTCToEndpointRTC = (
}; };
}; };
// TODO: Handle URL sources export const convertRelationshipsToEndpointRelations = (
export const convertSourceToEndpointSource = ({ relationships: Relationship["incomingRelations"] | Relationship["outgoingRelations"]
collectibles, ): EndpointRelation[] =>
folders, relationships?.flatMap<EndpointRelation>(({ relationTo, value }) => {
gallery, if (!isPayloadType(value)) return [];
scans, switch (relationTo) {
}: { case Collections.Folders:
collectibles?: (string | Collectible)[] | null | undefined; return { type: Collections.Folders, value: convertFolderToEndpointFolderPreview(value) };
scans?: (string | Collectible)[] | null | undefined;
gallery?: (string | Collectible)[] | null | undefined;
folders?: (string | Folder)[] | null | undefined;
}): EndpointSource[] => {
const result: EndpointSource[] = [];
const convertFolderToEndpointSourcePreview = ({ case Collections.Pages:
id, return { type: Collections.Pages, value: convertPageToEndpointPagePreview(value) };
slug,
translations,
}: Folder): EndpointSourcePreview => ({
id,
slug,
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
title: name,
})),
});
const convertCollectibleToEndpointSourcePreview = ({ case Collections.Collectibles:
id, return {
slug, type: Collections.Collectibles,
translations, value: convertCollectibleToEndpointCollectiblePreview(value),
}: Collectible): EndpointSourcePreview => ({ };
id,
slug,
translations: translations.map(({ language, title, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language,
title,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
...(isNotEmpty(subtitle) ? { subtitle } : {}),
})),
});
if (collectibles && isPayloadArrayType(collectibles)) { case Collections.Images:
collectibles.filter(isPublished).forEach((collectible) => { if (!isImage(value)) return [];
result.push({ return { type: Collections.Images, value: convertImageToEndpointImagePreview(value) };
type: "collectible",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (scans && isPayloadArrayType(scans)) { case Collections.Videos:
scans.filter(isPublished).forEach((collectible) => { if (!isVideo(value)) return [];
result.push({ return { type: Collections.Videos, value: convertVideoToEndpointVideoPreview(value) };
type: "scans",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (gallery && isPayloadArrayType(gallery)) { case Collections.Audios:
gallery.filter(isPublished).forEach((collectible) => { if (!isAudio(value)) return [];
result.push({ return { type: Collections.Audios, value: convertAudioToEndpointAudioPreview(value) };
type: "gallery",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (folders && isPayloadArrayType(folders)) { case Collections.Files:
folders.forEach((folder) => { if (!isFile(value)) return [];
result.push({ return { type: Collections.Files, value: convertFileToEndpointFilePreview(value) };
type: "folder",
folder: convertFolderToEndpointSourcePreview(folder),
});
});
}
return result; case Collections.Recorders:
}; return {
type: Collections.Recorders,
value: convertRecorderToEndpointRecorderPreview(value),
};
case Collections.ChronologyEvents:
return { type: Collections.ChronologyEvents, value: convertEventToEndpointEvent(value) };
case Collections.MediaThumbnails:
case Collections.VideosSubtitles:
case Collections.VideosChannels:
case Collections.Scans:
case Collections.Tags:
case Collections.Attributes:
case Collections.CreditsRole:
case Collections.Languages:
case Collections.Currencies:
case Collections.Wordings:
case Collections.GenericContents:
default:
return [];
}
}) ?? [];
export const getDomainFromUrl = (url: string): string => { export const getDomainFromUrl = (url: string): string => {
const urlObject = new URL(url); const urlObject = new URL(url);