From 5acaf65aded2b1746bcc0d0d5677197af04f7073 Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Sun, 21 Jul 2024 10:24:14 +0200 Subject: [PATCH] Move from using parentPages to backlinks --- package-lock.json | 24 +- package.json | 5 +- src/collections/Audios/endpoints/getByID.ts | 11 +- .../endpoints/getAllEndpoint.ts | 26 +- .../ChronologyEvents/endpoints/getByID.ts | 4 +- src/collections/Collectibles/Collectibles.ts | 37 -- .../endpoints/getBySlugEndpoint.ts | 16 +- .../endpoints/getBySlugEndpointGallery.ts | 13 +- .../getBySlugEndpointGalleryImage.ts | 11 +- .../endpoints/getBySlugEndpointScanPage.ts | 13 +- .../endpoints/getBySlugEndpointScans.ts | 9 +- src/collections/Files/endpoints/getByID.ts | 11 +- src/collections/Folders/Folders.ts | 31 +- .../Folders/endpoints/getBySlugEndpoint.ts | 11 +- src/collections/Images/endpoints/getByID.ts | 13 +- src/collections/Pages/Pages.ts | 26 -- .../Pages/endpoints/getBySlugEndpoint.ts | 11 +- src/collections/Videos/Videos.ts | 10 - src/collections/Videos/endpoints/getByID.ts | 11 +- .../VideosChannels/VideosChannels.ts | 7 - .../WebsiteConfig/WebsiteConfig.ts | 4 - src/endpoints/getAllSDKUrlsEndpoint.ts | 2 +- .../backPropagationField.ts | 51 --- .../backPropagationUtils.ts | 47 -- src/hooks/afterOperationWebhook.ts | 99 ----- src/payload.config.ts | 16 + src/server.ts | 45 +- src/shared | 2 +- src/types/collections.ts | 405 ++++++++++++++---- src/utils/collectionConfig.ts | 10 - src/utils/endpoints.ts | 140 +++--- 31 files changed, 555 insertions(+), 566 deletions(-) delete mode 100644 src/fields/backPropagationField/backPropagationField.ts delete mode 100644 src/fields/backPropagationField/backPropagationUtils.ts delete mode 100644 src/hooks/afterOperationWebhook.ts diff --git a/package-lock.json b/package-lock.json index 6d0c212..66ecbf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@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/db-mongodb": "1.6.0", "@payloadcms/richtext-lexical": "0.11.2", @@ -19,9 +19,10 @@ "language-tags": "1.0.9", "luxon": "3.4.4", "payload": "2.24.0", + "payloadcms-relationships": "github:DrMint/payloadcms-relationships", "payloadcms-sftp-storage": "1.0.1", "sharp": "0.33.4", - "styled-components": "6.1.11" + "styled-components": "6.1.12" }, "devDependencies": { "@types/express": "4.17.21", @@ -1962,9 +1963,9 @@ } }, "node_modules/@iconify-json/material-symbols": { - "version": "1.1.84", - "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.1.84.tgz", - "integrity": "sha512-Vio0Ns2rzdTsushHGBiyAcIIbGNWXI6nTNsrVqcq1EEPkfNkuchufBMvErs9vdc1vN1Fj7Ul3uWA3r988TIvfQ==", + "version": "1.1.85", + "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.1.85.tgz", + "integrity": "sha512-GJXTScAIdaxxMPcp6GCd4qbntvHpG9UrF/2V03PMUuD7+1fMU5vHG5w0IGDdvqOnI9HpEcUFa7CFDVQHOpBeDA==", "dependencies": { "@iconify/types": "*" } @@ -9055,6 +9056,13 @@ "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/payloadcms-sftp-storage/-/payloadcms-sftp-storage-1.0.1.tgz", @@ -11545,9 +11553,9 @@ } }, "node_modules/styled-components": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", - "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", diff --git a/package.json b/package.json index 87e3503..342e2ae 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@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/db-mongodb": "1.6.0", "@payloadcms/richtext-lexical": "0.11.2", @@ -31,9 +31,10 @@ "language-tags": "1.0.9", "luxon": "3.4.4", "payload": "2.24.0", + "payloadcms-relationships": "github:DrMint/payloadcms-relationships", "payloadcms-sftp-storage": "1.0.1", "sharp": "0.33.4", - "styled-components": "6.1.11" + "styled-components": "6.1.12" }, "devDependencies": { "@types/express": "4.17.21", diff --git a/src/collections/Audios/endpoints/getByID.ts b/src/collections/Audios/endpoints/getByID.ts index 232dd8c..56d8613 100644 --- a/src/collections/Audios/endpoints/getByID.ts +++ b/src/collections/Audios/endpoints/getByID.ts @@ -7,6 +7,7 @@ import { convertCreditsToEndpointCredits, convertMediaThumbnailToEndpointPayloadImage, convertRTCToEndpointRTC, + convertRelationshipsToEndpointRelations, getLanguageId, } from "../../../utils/endpoints"; import { Collections } from "../../../shared/payload/constants"; @@ -15,6 +16,7 @@ import { EndpointAudioPreview, EndpointAudio, } from "../../../shared/payload/endpoint-types"; +import { findIncomingRelationships } from "payloadcms-relationships"; export const getByID: CollectionEndpoint = { method: "get", @@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = { return res.sendStatus(404); } - return res.status(200).json(convertAudioToEndpointAudio(result)); + return res.status(200).json(await convertAudioToEndpointAudio(result)); } catch { return res.sendStatus(404); } @@ -79,8 +81,8 @@ export const convertAudioToEndpointAudioPreview = ({ : {}), }); -const convertAudioToEndpointAudio = (audio: Audio & PayloadMedia): EndpointAudio => { - const { translations, createdAt, updatedAt, filesize, credits } = audio; +const convertAudioToEndpointAudio = async (audio: Audio & PayloadMedia): Promise => { + const { translations, createdAt, updatedAt, filesize, credits, id } = audio; return { ...convertAudioToEndpointAudioPreview(audio), createdAt, @@ -95,5 +97,8 @@ const convertAudioToEndpointAudio = (audio: Audio & PayloadMedia): EndpointAudio ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), })) ?? [], credits: convertCreditsToEndpointCredits(credits), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Audios, id) + ), }; }; diff --git a/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts b/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts index f870cbd..064de8a 100644 --- a/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts +++ b/src/collections/ChronologyEvents/endpoints/getAllEndpoint.ts @@ -6,7 +6,11 @@ import { convertCreditsToEndpointCredits, getDomainFromUrl } from "../../../util import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint"; import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint"; 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 = { method: "get", @@ -48,13 +52,13 @@ export const getAllEndpoint: CollectionEndpoint = { if (aDay !== bDay) return aDay - bDay; return 0; }) - .map(eventToEndpointEvent); + .map(convertEventToEndpointEvent); res.status(200).json(events); }, }; -export const eventToEndpointEvent = ({ +export const convertEventToEndpointEvent = ({ date: { year, day, month }, events, 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 ( - sources?.flatMap((source) => { + sources?.flatMap((source) => { switch (source.blockType) { case "collectibleBlock": const range = handleRange(source.range); if (!isPayloadType(source.collectible)) return []; return { - type: "collectible", - collectible: convertCollectibleToEndpointCollectiblePreview(source.collectible), + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(source.collectible), ...(isDefined(range) ? { range } : {}), }; case "pageBlock": if (!isPayloadType(source.page)) return []; return { - type: "page", - page: convertPageToEndpointPagePreview(source.page), + type: Collections.Pages, + value: convertPageToEndpointPagePreview(source.page), }; case "urlBlock": @@ -113,7 +119,7 @@ const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): E const handleRange = ( rawRange: CollectibleBlock["range"] -): Extract["range"] => { +): EndpointCollectibleRelationRange | undefined => { const range = rawRange?.[0]; switch (range?.blockType) { diff --git a/src/collections/ChronologyEvents/endpoints/getByID.ts b/src/collections/ChronologyEvents/endpoints/getByID.ts index 2cd73db..780e56b 100644 --- a/src/collections/ChronologyEvents/endpoints/getByID.ts +++ b/src/collections/ChronologyEvents/endpoints/getByID.ts @@ -1,6 +1,6 @@ import payload from "payload"; import { CollectionEndpoint } from "../../../types/payload"; -import { eventToEndpointEvent } from "./getAllEndpoint"; +import { convertEventToEndpointEvent } from "./getAllEndpoint"; import { Collections } from "../../../shared/payload/constants"; export const getByID: CollectionEndpoint = { @@ -27,7 +27,7 @@ export const getByID: CollectionEndpoint = { id: req.params.id, }); - return res.status(200).json(eventToEndpointEvent(result)); + return res.status(200).json(convertEventToEndpointEvent(result)); } catch { return res.sendStatus(404); } diff --git a/src/collections/Collectibles/Collectibles.ts b/src/collections/Collectibles/Collectibles.ts index c0b5956..16ae95a 100644 --- a/src/collections/Collectibles/Collectibles.ts +++ b/src/collections/Collectibles/Collectibles.ts @@ -1,7 +1,5 @@ import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types"; -import { Where } from "payload/types"; import { attributesField } from "../../fields/attributesField/attributesField"; -import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { componentField } from "../../fields/componentField/componentField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { imageField } from "../../fields/imageField/imageField"; @@ -11,8 +9,6 @@ import { translatedFields } from "../../fields/translatedFields/translatedFields import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping"; import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; -import { Collectible } from "../../types/collections"; -import { isPayloadType } from "../../utils/asserts"; import { createEditor } from "../../utils/editor"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; 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; - }, - }, }); diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts index bf409cf..36290cc 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpoint.ts @@ -20,8 +20,8 @@ import { import { convertAttributesToEndpointAttributes, convertImageToEndpointPayloadImage, + convertRelationshipsToEndpointRelations, convertScanToEndpointScanImage, - convertSourceToEndpointSource, getDomainFromUrl, } from "../../../utils/endpoints"; import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID"; @@ -29,12 +29,13 @@ import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID" import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint"; import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID"; import { convertVideoToEndpointVideoPreview } from "../../Videos/endpoints/getByID"; +import { findIncomingRelationships } from "payloadcms-relationships"; export const getBySlugEndpoint = createGetByEndpoint({ collection: Collections.Collectibles, attribute: "slug", depth: 3, - handler: (collectible) => convertCollectibleToEndpointCollectible(collectible), + handler: async (collectible) => await convertCollectibleToEndpointCollectible(collectible), }); export const convertCollectibleToEndpointCollectiblePreview = ({ @@ -64,8 +65,11 @@ export const convertCollectibleToEndpointCollectiblePreview = ({ ...handlePrice(price, priceEnabled), }); -const convertCollectibleToEndpointCollectible = (collectible: Collectible): EndpointCollectible => { +const convertCollectibleToEndpointCollectible = async ( + collectible: Collectible +): Promise => { const { + id, nature, urls, subitems, @@ -80,8 +84,6 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp weightEnabled, pageInfo, pageInfoEnabled, - parentItems, - folders, backgroundImage, translations, scans: rawScans, @@ -128,7 +130,9 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp ...(isPayloadType(updatedBy) ? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) } : {}), - parentPages: convertSourceToEndpointSource({ collectibles: parentItems, folders }), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Collectibles, id) + ), }; }; diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpointGallery.ts b/src/collections/Collectibles/endpoints/getBySlugEndpointGallery.ts index f472f6b..6965005 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpointGallery.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpointGallery.ts @@ -2,10 +2,8 @@ import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; import { Collections } from "../../../shared/payload/constants"; import { EndpointCollectibleGallery } from "../../../shared/payload/endpoint-types"; import { isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts"; -import { - convertImageToEndpointPayloadImage, - convertSourceToEndpointSource, -} from "../../../utils/endpoints"; +import { convertImageToEndpointPayloadImage } from "../../../utils/endpoints"; +import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint"; export const getBySlugEndpointGallery = createGetByEndpoint({ collection: Collections.Collectibles, @@ -29,7 +27,12 @@ export const getBySlugEndpointGallery = createGetByEndpoint({ gallery?.flatMap(({ image }) => isImage(image) ? convertImageToEndpointPayloadImage(image) : [] ) ?? [], - parentPages: convertSourceToEndpointSource({ collectibles: [collectible] }), + backlinks: [ + { + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(collectible), + }, + ], }; }, }); diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpointGalleryImage.ts b/src/collections/Collectibles/endpoints/getBySlugEndpointGalleryImage.ts index 36ec12c..aeb71bb 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpointGalleryImage.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpointGalleryImage.ts @@ -2,10 +2,10 @@ import payload from "payload"; import { Collectible, Image } from "../../../types/collections"; import { CollectionEndpoint } from "../../../types/payload"; import { isDefined, isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts"; -import { convertSourceToEndpointSource } from "../../../utils/endpoints"; import { convertImageToEndpointImage } from "../../Images/endpoints/getByID"; import { Collections } from "../../../shared/payload/constants"; import { EndpointCollectibleGalleryImage } from "../../../shared/payload/endpoint-types"; +import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint"; export const getBySlugEndpointGalleryImage: CollectionEndpoint = { path: "/slug/:slug/gallery/:index", @@ -51,8 +51,7 @@ export const getBySlugEndpointGalleryImage: CollectionEndpoint = { const nextIndex = getNextIndex(index, collectible.gallery); const scanPage: EndpointCollectibleGalleryImage = { - image: convertImageToEndpointImage(image), - parentPages: convertSourceToEndpointSource({ gallery: [collectible] }), + image: await convertImageToEndpointImage(image), slug, translations: collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({ @@ -64,6 +63,12 @@ export const getBySlugEndpointGalleryImage: CollectionEndpoint = { })) ?? [], ...(isDefined(previousIndex) ? { previousIndex } : {}), ...(isDefined(nextIndex) ? { nextIndex } : {}), + backlinks: [ + { + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(collectible), + }, + ], }; res.status(200).send(scanPage); diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpointScanPage.ts b/src/collections/Collectibles/endpoints/getBySlugEndpointScanPage.ts index 1f93359..04351ae 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpointScanPage.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpointScanPage.ts @@ -2,12 +2,10 @@ import payload from "payload"; import { Collectible, Scan } from "../../../types/collections"; import { CollectionEndpoint } from "../../../types/payload"; import { isDefined, isNotEmpty, isPayloadType, isScan } from "../../../utils/asserts"; -import { - convertScanToEndpointScanImage, - convertSourceToEndpointSource, -} from "../../../utils/endpoints"; +import { convertScanToEndpointScanImage } from "../../../utils/endpoints"; import { Collections } from "../../../shared/payload/constants"; import { EndpointCollectibleScanPage } from "../../../shared/payload/endpoint-types"; +import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint"; export const getBySlugEndpointScanPage: CollectionEndpoint = { path: "/slug/:slug/scans/:index", @@ -54,7 +52,6 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = { const scanPage: EndpointCollectibleScanPage = { image: convertScanToEndpointScanImage(scan, index), - parentPages: convertSourceToEndpointSource({ scans: [collectible] }), slug, translations: collectible.translations?.map(({ language, title, description, pretitle, subtitle }) => ({ @@ -66,6 +63,12 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = { })) ?? [], ...(isDefined(previousIndex) ? { previousIndex } : {}), ...(isDefined(nextIndex) ? { nextIndex } : {}), + backlinks: [ + { + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(collectible), + }, + ], }; res.status(200).send(scanPage); diff --git a/src/collections/Collectibles/endpoints/getBySlugEndpointScans.ts b/src/collections/Collectibles/endpoints/getBySlugEndpointScans.ts index ced5cd9..23d3a96 100644 --- a/src/collections/Collectibles/endpoints/getBySlugEndpointScans.ts +++ b/src/collections/Collectibles/endpoints/getBySlugEndpointScans.ts @@ -7,8 +7,8 @@ import { convertCreditsToEndpointCredits, convertImageToEndpointPayloadImage, convertScanToEndpointScanImage, - convertSourceToEndpointSource, } from "../../../utils/endpoints"; +import { convertCollectibleToEndpointCollectiblePreview } from "./getBySlugEndpoint"; export const getBySlugEndpointScans = createGetByEndpoint({ collection: Collections.Collectibles, @@ -29,7 +29,12 @@ export const getBySlugEndpointScans = createGetByEndpoint({ })) ?? [], ...(isImage(thumbnail) ? { thumbnail: convertImageToEndpointPayloadImage(thumbnail) } : {}), ...(scansEnabled && scans ? handleScans(scans) : { credits: [], pages: [] }), - parentPages: convertSourceToEndpointSource({ collectibles: [collectible] }), + backlinks: [ + { + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(collectible), + }, + ], }; }, }); diff --git a/src/collections/Files/endpoints/getByID.ts b/src/collections/Files/endpoints/getByID.ts index 46d7744..afefdd8 100644 --- a/src/collections/Files/endpoints/getByID.ts +++ b/src/collections/Files/endpoints/getByID.ts @@ -7,6 +7,7 @@ import { convertCreditsToEndpointCredits, convertMediaThumbnailToEndpointPayloadImage, convertRTCToEndpointRTC, + convertRelationshipsToEndpointRelations, getLanguageId, } from "../../../utils/endpoints"; import { Collections } from "../../../shared/payload/constants"; @@ -15,6 +16,7 @@ import { EndpointFilePreview, EndpointFile, } from "../../../shared/payload/endpoint-types"; +import { findIncomingRelationships } from "payloadcms-relationships"; export const getByID: CollectionEndpoint = { method: "get", @@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = { return res.sendStatus(404); } - return res.status(200).json(convertFileToEndpointFile(result)); + return res.status(200).json(await convertFileToEndpointFile(result)); } catch { return res.sendStatus(404); } @@ -79,8 +81,8 @@ export const convertFileToEndpointFilePreview = ({ : {}), }); -const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => { - const { translations, createdAt, updatedAt, filesize, credits } = file; +const convertFileToEndpointFile = async (file: File & PayloadMedia): Promise => { + const { translations, createdAt, updatedAt, filesize, credits, id } = file; return { ...convertFileToEndpointFilePreview(file), @@ -96,5 +98,8 @@ const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => { ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), })) ?? [], credits: convertCreditsToEndpointCredits(credits), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Files, id) + ), }; }; diff --git a/src/collections/Folders/Folders.ts b/src/collections/Folders/Folders.ts index f650dec..256be6d 100644 --- a/src/collections/Folders/Folders.ts +++ b/src/collections/Folders/Folders.ts @@ -1,11 +1,8 @@ -import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { iconField } from "../../fields/iconField/iconField"; import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { Collections, CollectionGroups } from "../../shared/payload/constants"; -import { Folder } from "../../types/collections"; -import { isPayloadType } from "../../utils/asserts"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { createEditor } from "../../utils/editor"; import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint"; @@ -44,16 +41,7 @@ export const Folders = buildCollectionConfig({ }, endpoints: [getBySlugEndpoint], fields: [ - rowField([ - slugField({ name: fields.slug }), - iconField({ name: fields.icon }), - backPropagationField({ - name: fields.parentFolders, - relationTo: Collections.Folders, - hasMany: true, - where: ({ id }) => ({ "sections.subfolders": { equals: id } }), - }), - ]), + rowField([slugField({ name: fields.slug }), iconField({ name: fields.icon })]), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.translationsName }, @@ -112,21 +100,4 @@ export const Folders = buildCollectionConfig({ 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; - }, - }, }); diff --git a/src/collections/Folders/endpoints/getBySlugEndpoint.ts b/src/collections/Folders/endpoints/getBySlugEndpoint.ts index 0c7bbec..5df0690 100644 --- a/src/collections/Folders/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Folders/endpoints/getBySlugEndpoint.ts @@ -1,4 +1,5 @@ import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; +import { findIncomingRelationships } from "payloadcms-relationships"; import { Collections } from "../../../shared/payload/constants"; import { EndpointFolderPreview, EndpointFolder } from "../../../shared/payload/endpoint-types"; import { Folder, Language } from "../../../types/collections"; @@ -12,7 +13,7 @@ import { isPublished, isVideo, } from "../../../utils/asserts"; -import { convertSourceToEndpointSource, getLanguageId } from "../../../utils/endpoints"; +import { convertRelationshipsToEndpointRelations, getLanguageId } from "../../../utils/endpoints"; import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID"; import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint"; import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID"; @@ -43,8 +44,8 @@ export const convertFolderToEndpointFolderPreview = ({ })) ?? [], }); -const convertFolderToEndpointFolder = (folder: Folder): EndpointFolder => { - const { translations, sections, files, parentFolders } = folder; +const convertFolderToEndpointFolder = async (folder: Folder): Promise => { + const { translations, sections, files, id } = folder; return { ...convertFolderToEndpointFolderPreview(folder), @@ -116,7 +117,9 @@ const convertFolderToEndpointFolder = (folder: Folder): EndpointFolder => { return []; } }) ?? [], - parentPages: convertSourceToEndpointSource({ folders: parentFolders }), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Folders, id) + ), }; }; diff --git a/src/collections/Images/endpoints/getByID.ts b/src/collections/Images/endpoints/getByID.ts index 39da5d4..020800f 100644 --- a/src/collections/Images/endpoints/getByID.ts +++ b/src/collections/Images/endpoints/getByID.ts @@ -6,6 +6,7 @@ import { convertAttributesToEndpointAttributes, convertCreditsToEndpointCredits, convertRTCToEndpointRTC, + convertRelationshipsToEndpointRelations, convertSizesToPayloadImages, getLanguageId, } from "../../../utils/endpoints"; @@ -15,6 +16,7 @@ import { EndpointImagePreview, EndpointImage, } from "../../../shared/payload/endpoint-types"; +import { findIncomingRelationships } from "payloadcms-relationships"; export const getByID: CollectionEndpoint = { method: "get", @@ -44,7 +46,7 @@ export const getByID: CollectionEndpoint = { return res.sendStatus(404); } - return res.status(200).json(convertImageToEndpointImage(result)); + return res.status(200).json(await convertImageToEndpointImage(result)); } catch { return res.sendStatus(404); } @@ -93,8 +95,10 @@ export const convertImageToEndpointImagePreview = ({ ...(isPayloadImage(sizes?.og) ? { openGraph: sizes.og } : {}), }); -export const convertImageToEndpointImage = (image: Image & PayloadImage): EndpointImage => { - const { translations, createdAt, updatedAt, filesize, credits } = image; +export const convertImageToEndpointImage = async ( + image: Image & PayloadImage +): Promise => { + const { translations, createdAt, updatedAt, filesize, credits, id } = image; return { ...convertImageToEndpointImagePreview(image), createdAt, @@ -109,5 +113,8 @@ export const convertImageToEndpointImage = (image: Image & PayloadImage): Endpoi ...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}), })) ?? [], credits: convertCreditsToEndpointCredits(credits), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Images, id) + ), }; }; diff --git a/src/collections/Pages/Pages.ts b/src/collections/Pages/Pages.ts index c514b79..de33984 100644 --- a/src/collections/Pages/Pages.ts +++ b/src/collections/Pages/Pages.ts @@ -1,10 +1,8 @@ -import { Where } from "payload/types"; import { breakBlock } from "../../blocks/breakBlock"; import { sectionBlock } from "../../blocks/sectionBlock"; import { transcriptBlock } from "../../blocks/transcriptBlock"; import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters"; import { attributesField } from "../../fields/attributesField/attributesField"; -import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { creditsField } from "../../fields/creditsField/creditsField"; import { imageField } from "../../fields/imageField/imageField"; import { rowField } from "../../fields/rowField/rowField"; @@ -125,29 +123,5 @@ export const Pages = buildVersionedCollectionConfig({ 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[], - }), - }), - ]), ], }); diff --git a/src/collections/Pages/endpoints/getBySlugEndpoint.ts b/src/collections/Pages/endpoints/getBySlugEndpoint.ts index fa4a743..747444d 100644 --- a/src/collections/Pages/endpoints/getBySlugEndpoint.ts +++ b/src/collections/Pages/endpoints/getBySlugEndpoint.ts @@ -1,4 +1,5 @@ import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint"; +import { findIncomingRelationships } from "payloadcms-relationships"; import { Collections, BreakBlockType } from "../../../shared/payload/constants"; import { EndpointPagePreview, @@ -18,7 +19,7 @@ import { convertCreditsToEndpointCredits, convertImageToEndpointPayloadImage, convertRTCToEndpointRTC, - convertSourceToEndpointSource, + convertRelationshipsToEndpointRelations, } from "../../../utils/endpoints"; import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID"; @@ -49,8 +50,8 @@ export const convertPageToEndpointPagePreview = ({ updatedAt, }); -const convertPageToEndpointPage = (page: Page): EndpointPage => { - const { translations, collectibles, folders, backgroundImage, createdAt, updatedBy } = page; +const convertPageToEndpointPage = async (page: Page): Promise => { + const { translations, backgroundImage, createdAt, updatedBy, id } = page; return { ...convertPageToEndpointPagePreview(page), @@ -74,7 +75,9 @@ const convertPageToEndpointPage = (page: Page): EndpointPage => { ...(isPayloadType(updatedBy) ? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) } : {}), - parentPages: convertSourceToEndpointSource({ collectibles, folders }), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Pages, id) + ), }; }; diff --git a/src/collections/Videos/Videos.ts b/src/collections/Videos/Videos.ts index bb39b9f..7468eb0 100644 --- a/src/collections/Videos/Videos.ts +++ b/src/collections/Videos/Videos.ts @@ -5,8 +5,6 @@ import { imageField } from "../../fields/imageField/imageField"; import { rowField } from "../../fields/rowField/rowField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { Collections, CollectionGroups } from "../../shared/payload/constants"; -import { Video } from "../../types/collections"; -import { isPayloadType } from "../../utils/asserts"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { createEditor } from "../../utils/editor"; 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]; - }, - }, }); diff --git a/src/collections/Videos/endpoints/getByID.ts b/src/collections/Videos/endpoints/getByID.ts index 937dad5..112c9b9 100644 --- a/src/collections/Videos/endpoints/getByID.ts +++ b/src/collections/Videos/endpoints/getByID.ts @@ -15,6 +15,7 @@ import { convertCreditsToEndpointCredits, convertMediaThumbnailToEndpointPayloadImage, convertRTCToEndpointRTC, + convertRelationshipsToEndpointRelations, getLanguageId, } from "../../../utils/endpoints"; import { Collections } from "../../../shared/payload/constants"; @@ -23,6 +24,7 @@ import { EndpointVideoPreview, EndpointVideo, } from "../../../shared/payload/endpoint-types"; +import { findIncomingRelationships } from "payloadcms-relationships"; export const getByID: CollectionEndpoint = { method: "get", @@ -52,7 +54,7 @@ export const getByID: CollectionEndpoint = { return res.sendStatus(404); } - return res.status(200).json(convertVideoToEndpointVideo(result)); + return res.status(200).json(await convertVideoToEndpointVideo(result)); } catch { return res.sendStatus(404); } @@ -98,8 +100,8 @@ export const convertVideoToEndpointVideoPreview = ({ }) ?? [], }); -const convertVideoToEndpointVideo = (video: Video & PayloadMedia): EndpointVideo => { - const { translations, createdAt, updatedAt, filesize, platform, platformEnabled, credits } = +const convertVideoToEndpointVideo = async (video: Video & PayloadMedia): Promise => { + const { translations, createdAt, updatedAt, filesize, platform, platformEnabled, credits, id } = video; return { @@ -125,5 +127,8 @@ const convertVideoToEndpointVideo = (video: Video & PayloadMedia): EndpointVideo } : {}), credits: convertCreditsToEndpointCredits(credits), + backlinks: convertRelationshipsToEndpointRelations( + await findIncomingRelationships(Collections.Videos, id) + ), }; }; diff --git a/src/collections/VideosChannels/VideosChannels.ts b/src/collections/VideosChannels/VideosChannels.ts index 243865a..81fc575 100644 --- a/src/collections/VideosChannels/VideosChannels.ts +++ b/src/collections/VideosChannels/VideosChannels.ts @@ -1,5 +1,4 @@ import { CollectionConfig } from "payload/types"; -import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { rowField } from "../../fields/rowField/rowField"; import { buildCollectionConfig } from "../../utils/collectionConfig"; 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.subscribers, type: "number", required: true }, ]), - backPropagationField({ - name: fields.videos, - relationTo: Collections.Videos, - hasMany: true, - where: ({ id }) => ({ "platform.channel": { equals: id } }), - }), ], }); diff --git a/src/collections/WebsiteConfig/WebsiteConfig.ts b/src/collections/WebsiteConfig/WebsiteConfig.ts index fa48563..c8ef867 100644 --- a/src/collections/WebsiteConfig/WebsiteConfig.ts +++ b/src/collections/WebsiteConfig/WebsiteConfig.ts @@ -2,7 +2,6 @@ import { GlobalConfig } from "payload/types"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { imageField } from "../../fields/imageField/imageField"; import { rowField } from "../../fields/rowField/rowField"; -import { globalAfterChangeWebhook } from "../../hooks/afterOperationWebhook"; import { getConfigEndpoint } from "./endpoints/getConfigEndpoint"; import { Collections, CollectionGroups } from "../../shared/payload/constants"; @@ -31,9 +30,6 @@ export const WebsiteConfig: GlobalConfig = { }, access: { update: mustBeAdmin, read: mustBeAdmin }, endpoints: [getConfigEndpoint], - hooks: { - afterChange: [globalAfterChangeWebhook], - }, fields: [ rowField([ { diff --git a/src/endpoints/getAllSDKUrlsEndpoint.ts b/src/endpoints/getAllSDKUrlsEndpoint.ts index e96ed12..5f193e6 100644 --- a/src/endpoints/getAllSDKUrlsEndpoint.ts +++ b/src/endpoints/getAllSDKUrlsEndpoint.ts @@ -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) { case Collections.WebsiteConfig: return [getSDKEndpoint.getConfigEndpoint()]; diff --git a/src/fields/backPropagationField/backPropagationField.ts b/src/fields/backPropagationField/backPropagationField.ts deleted file mode 100644 index 08e190f..0000000 --- a/src/fields/backPropagationField/backPropagationField.ts +++ /dev/null @@ -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; - } - }, - ], - }, -}); diff --git a/src/fields/backPropagationField/backPropagationUtils.ts b/src/fields/backPropagationField/backPropagationUtils.ts deleted file mode 100644 index 56f5f58..0000000 --- a/src/fields/backPropagationField/backPropagationUtils.ts +++ /dev/null @@ -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 => { - 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)); -}; diff --git a/src/hooks/afterOperationWebhook.ts b/src/hooks/afterOperationWebhook.ts deleted file mode 100644 index 8d7a11b..0000000 --- a/src/hooks/afterOperationWebhook.ts +++ /dev/null @@ -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); - } -}; diff --git a/src/payload.config.ts b/src/payload.config.ts index 2ed2915..c5b26ee 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -31,6 +31,9 @@ import { getAllIds } from "./endpoints/getAllIdsEndpoint"; import { getAllSDKUrlsEndpoint } from "./endpoints/getAllSDKUrlsEndpoint"; import { createEditor } from "./utils/editor"; 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({ connectOptions: { @@ -96,6 +99,19 @@ export default buildConfig({ skip: () => true, }, plugins: [ + relationshipsPlugin({ + collectionConfig: { + admin: { + hidden: shownOnlyToAdmin, + }, + access: { + update: mustBeAdmin, + create: mustBeAdmin, + delete: mustBeAdmin, + }, + }, + }), + cloudStorage({ collections: { [Collections.Videos]: { diff --git a/src/server.ts b/src/server.ts index 640e2ae..02fc773 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import express from "express"; import path from "path"; import payload from "payload"; -import { isDefined, isUndefined } from "./utils/asserts"; +import { isUndefined } from "./utils/asserts"; import { Collections, RecordersRoles } from "./shared/payload/constants"; const app = express(); @@ -28,29 +28,30 @@ const start = async () => { express: app, onInit: async () => { payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`); - const recorders = await payload.find({ collection: Collections.Recorders }); - // If no recorders, we seed some initial data - if ( - 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"); + const seedFirstUser = async () => { + const recorders = await payload.find({ collection: Collections.Recorders }); - 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, - }, - }); - } - } + if (recorders.docs.length > 0) return; + if (isUndefined(process.env.SEEDING_ADMIN_EMAIL)) return; + if (isUndefined(process.env.SEEDING_ADMIN_PASSWORD)) return; + if (isUndefined(process.env.SEEDING_ADMIN_USERNAME)) return; + + payload.logger.info("Seeding your first user"); + + 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(); }, }); diff --git a/src/shared b/src/shared index 824f708..7d6f5ff 160000 --- a/src/shared +++ b/src/shared @@ -1 +1 @@ -Subproject commit 824f7085f9e0787f97ef9afd11cb5320a2cd6208 +Subproject commit 7d6f5ffb704f4ecddfb0e0982ce9eb79d39d450d diff --git a/src/types/collections.ts b/src/types/collections.ts index fa552dc..ddef918 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -40,6 +40,7 @@ export interface Config { currencies: Currency; wordings: Wording; "generic-contents": GenericContent; + relationships: Relationship; "payload-preferences": PayloadPreference; "payload-migrations": PayloadMigration; }; @@ -96,8 +97,6 @@ export interface Page { credits?: Credits; id?: string | null; }[]; - folders?: (string | Folder)[] | null; - collectibles?: (string | Collectible)[] | null; updatedBy: string | Recorder; updatedAt: string; createdAt: string; @@ -352,79 +351,6 @@ export interface Recorder { lockUntil?: 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 * via the `definition` "collectibles". @@ -608,8 +534,6 @@ export interface Collectible { id?: string | null; }[] | null; - folders?: (string | Folder)[] | null; - parentItems?: (string | Collectible)[] | null; updatedBy: string | Recorder; updatedAt: string; createdAt: string; @@ -951,7 +875,78 @@ export interface VideosChannel { url: string; title: string; 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 @@ -1082,6 +1077,260 @@ export interface Wording { updatedAt: 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 * via the `definition` "payload-preferences". diff --git a/src/utils/collectionConfig.ts b/src/utils/collectionConfig.ts index 4180246..c762bad 100644 --- a/src/utils/collectionConfig.ts +++ b/src/utils/collectionConfig.ts @@ -1,6 +1,5 @@ import { GeneratedTypes } from "payload"; import { CollectionConfig } from "payload/types"; -import { afterDeleteWebhook, collectionAfterChangeWebhook } from "../hooks/afterOperationWebhook"; import { formatToPascalCase } from "./string"; type CollectionConfigWithPlugins = CollectionConfig; @@ -11,18 +10,9 @@ export type BuildCollectionConfig = Omit< > & { slug: keyof GeneratedTypes["collections"]; labels: { singular: string; plural: string }; - custom?: { - getBackPropagatedRelationships?: (object: any) => string[]; - [key: string]: unknown; - }; }; export const buildCollectionConfig = (config: BuildCollectionConfig): CollectionConfig => ({ ...config, typescript: { interface: formatToPascalCase(config.labels.singular) }, - hooks: { - ...config.hooks, - afterChange: [...(config.hooks?.afterChange ?? []), collectionAfterChangeWebhook], - afterDelete: [...(config.hooks?.afterDelete ?? []), afterDeleteWebhook], - }, }); diff --git a/src/utils/endpoints.ts b/src/utils/endpoints.ts index e47a364..a3aacd0 100644 --- a/src/utils/endpoints.ts +++ b/src/utils/endpoints.ts @@ -1,18 +1,22 @@ 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 { convertPageToEndpointPagePreview } from "../collections/Pages/endpoints/getBySlugEndpoint"; import { convertRecorderToEndpointRecorderPreview } from "../collections/Recorders/endpoints/getByID"; import { convertVideoToEndpointVideoPreview } from "../collections/Videos/endpoints/getByID"; -import { AttributeTypes } from "../shared/payload/constants"; +import { AttributeTypes, Collections } from "../shared/payload/constants"; import { EndpointTag, - EndpointSource, - EndpointSourcePreview, EndpointRole, EndpointCredit, EndpointAttribute, PayloadImage, EndpointScanImage, EndpointPayloadImage, + EndpointRelation, } from "../shared/payload/endpoint-types"; import { RichTextContent, @@ -29,14 +33,13 @@ import { } from "../shared/payload/rich-text"; import { Audio, - Collectible, Credits, CreditsRole, - Folder, Image, Language, MediaThumbnail, NumberBlock, + Relationship, Scan, Tag, TagsBlock, @@ -47,12 +50,11 @@ import { isAudio, isDefined, isEmpty, + isFile, isImage, - isNotEmpty, isPayloadArrayType, isPayloadImage, isPayloadType, - isPublished, isVideo, } from "./asserts"; @@ -139,86 +141,64 @@ export const convertRTCToEndpointRTC = ( }; }; -// TODO: Handle URL sources -export const convertSourceToEndpointSource = ({ - collectibles, - folders, - gallery, - scans, -}: { - collectibles?: (string | Collectible)[] | null | undefined; - scans?: (string | Collectible)[] | null | undefined; - gallery?: (string | Collectible)[] | null | undefined; - folders?: (string | Folder)[] | null | undefined; -}): EndpointSource[] => { - const result: EndpointSource[] = []; +export const convertRelationshipsToEndpointRelations = ( + relationships: Relationship["incomingRelations"] | Relationship["outgoingRelations"] +): EndpointRelation[] => + relationships?.flatMap(({ relationTo, value }) => { + if (!isPayloadType(value)) return []; + switch (relationTo) { + case Collections.Folders: + return { type: Collections.Folders, value: convertFolderToEndpointFolderPreview(value) }; - const convertFolderToEndpointSourcePreview = ({ - id, - slug, - translations, - }: Folder): EndpointSourcePreview => ({ - id, - slug, - translations: translations.map(({ language, name }) => ({ - language: isPayloadType(language) ? language.id : language, - title: name, - })), - }); + case Collections.Pages: + return { type: Collections.Pages, value: convertPageToEndpointPagePreview(value) }; - const convertCollectibleToEndpointSourcePreview = ({ - id, - slug, - translations, - }: Collectible): EndpointSourcePreview => ({ - id, - slug, - translations: translations.map(({ language, title, pretitle, subtitle }) => ({ - language: isPayloadType(language) ? language.id : language, - title, - ...(isNotEmpty(pretitle) ? { pretitle } : {}), - ...(isNotEmpty(subtitle) ? { subtitle } : {}), - })), - }); + case Collections.Collectibles: + return { + type: Collections.Collectibles, + value: convertCollectibleToEndpointCollectiblePreview(value), + }; - if (collectibles && isPayloadArrayType(collectibles)) { - collectibles.filter(isPublished).forEach((collectible) => { - result.push({ - type: "collectible", - collectible: convertCollectibleToEndpointSourcePreview(collectible), - }); - }); - } + case Collections.Images: + if (!isImage(value)) return []; + return { type: Collections.Images, value: convertImageToEndpointImagePreview(value) }; - if (scans && isPayloadArrayType(scans)) { - scans.filter(isPublished).forEach((collectible) => { - result.push({ - type: "scans", - collectible: convertCollectibleToEndpointSourcePreview(collectible), - }); - }); - } + case Collections.Videos: + if (!isVideo(value)) return []; + return { type: Collections.Videos, value: convertVideoToEndpointVideoPreview(value) }; - if (gallery && isPayloadArrayType(gallery)) { - gallery.filter(isPublished).forEach((collectible) => { - result.push({ - type: "gallery", - collectible: convertCollectibleToEndpointSourcePreview(collectible), - }); - }); - } + case Collections.Audios: + if (!isAudio(value)) return []; + return { type: Collections.Audios, value: convertAudioToEndpointAudioPreview(value) }; - if (folders && isPayloadArrayType(folders)) { - folders.forEach((folder) => { - result.push({ - type: "folder", - folder: convertFolderToEndpointSourcePreview(folder), - }); - }); - } + case Collections.Files: + if (!isFile(value)) return []; + return { type: Collections.Files, value: convertFileToEndpointFilePreview(value) }; - 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 => { const urlObject = new URL(url);