Compare commits

...

20 Commits

Author SHA1 Message Date
DrMint 7dc5ca6020 Disable mime check on video subs 2024-08-17 12:08:56 +02:00
DrMint 0806c74f8c Added source urls to page 2024-08-16 14:52:00 +02:00
DrMint 5c059d0410 Updated deps 2024-08-16 14:47:17 +02:00
DrMint c9cc673419 Updated collectible's subpages 2024-08-15 16:28:25 +02:00
DrMint 935b130075 Detect more changes by actually comparing previous state and current state 2024-07-27 17:34:05 +02:00
DrMint 682e1b1cfb Fixed missing endpoint for collectibles 2024-07-27 14:49:11 +02:00
DrMint 47dd0557d5 Fixed getting changes after deletion 2024-07-27 07:33:09 +02:00
DrMint 1fb10535a6 Fixed bug with collectibles scans endpointchanges 2024-07-26 08:40:14 +02:00
DrMint 9e7d020ad7 Removed unused endpoints and use EndpointChanges for getAll 2024-07-26 08:13:43 +02:00
DrMint 4c467089dc Updated deps 2024-07-26 08:08:38 +02:00
DrMint 8ab2e8088a Setup webhooks 2024-07-26 06:15:28 +02:00
DrMint 5acaf65ade Move from using parentPages to backlinks 2024-07-21 10:24:14 +02:00
DrMint 8cd8a67778 Updated deps 2024-07-17 23:59:39 +02:00
DrMint 1092c97f5d Use filename as title for media 2024-07-16 08:15:30 +02:00
DrMint b0416b61db Payload being an ass whenever the code runs on the server or client 2024-07-14 18:16:25 +02:00
DrMint 8fe41b3989 Handle multiple urls (and token) for webhooks 2024-07-14 18:09:58 +02:00
DrMint e2cd4a9388 Now sending webhooks to both the webserver and meili 2024-07-14 09:13:28 +02:00
DrMint dfbf3c9840 Remove precommit from prod script 2024-07-13 19:23:10 +02:00
DrMint 6f6c3f5c68 Updated deps 2024-07-13 19:09:47 +02:00
DrMint faeb61e1f4 Using shared library 2024-07-13 19:03:21 +02:00
82 changed files with 1741 additions and 2221 deletions

View File

@ -13,8 +13,11 @@ SEEDING_ADMIN_USERNAME=admin_name
SEEDING_ADMIN_EMAIL=email@domain.com
SEEDING_ADMIN_PASSWORD=somepassword
WEB_HOOK_TOKEN=webhooktoken5e6ea45ef4e66eaa151612bdcb599df
WEB_HOOK_URI=https://accords-library.com/some/path
WEB_SERVER_HOOK_TOKEN=webhooktoken5e6ea45ef4e66eaa151612bdcb599df
WEB_SERVER_HOOK_URL=https://accords-library.com/some/path
MEILISEARCH_HOOK_TOKEN=webhooktoken5e6ea45ef4e66eaa151612bdcb599df
MEILISEARCH_HOOK_URL=https://meili.domain.com
SFTP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nxxxxxxxxxx..."
SFTP_USERNAME=someuser

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/shared"]
path = src/shared
url = https://github.com/Accords-Library/shared-library.git

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
src/shared/*
dist

12
.vscode/settings.json vendored
View File

@ -1,9 +1,11 @@
{
"css.lint.unknownAtRules": "ignore",
"editor.rulers": [100],
"typescript.preferences.importModuleSpecifier": "non-relative",
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
"editor.tabSize": 2,
"typescript.preferences.importModuleSpecifier": "relative",
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"package.json": ".git*, package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, .ncurc.*, .nvmrc, *.config.cjs, *.config.js, *.config.ts, *config.json, .*ignore",
".env": ".env.*",
"README.md": "*.md"
}
}

754
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,29 +10,31 @@
"build:server": "tsc",
"build": "npm run copyfiles && npm run build:payload && npm run build:server",
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/ && copyfiles -u 1 \"src/sdk.ts\" dist/ && copyfiles -u 1 \"src/constants.ts\" dist/ && copyfiles -u 1 \"src/types/collections.ts\" dist/",
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport='src/payload.config.ts;src/constants.ts;src/sdk.ts;src/types/collections.ts'",
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport='src/payload.config.ts;src/types/collections.ts;src/shared/*'",
"prettier": "prettier --list-different --end-of-line auto --write src",
"tsc": "tsc --noEmit",
"precommit": "npm run generate:types && npm run prettier && npm run unused-exports && npm run tsc",
"fetch-submodules": "cd src/shared && git pull && cd ../..",
"precommit": "npm run fetch-submodules && npm run generate:types && npm run prettier && npm run unused-exports && npm run tsc",
"upgrade": "ncu",
"prod": "rm -rf build && rm -rf dist && npm ci && npm run precommit && npm run build && npm run serve"
"prod": "rm -rf build && rm -rf dist && npm ci && npm run build && npm run serve"
},
"dependencies": {
"@fontsource/vollkorn": "5.0.20",
"@iconify-json/material-symbols": "^1.1.83",
"@iconify-json/material-symbols": "^1.1.87",
"@payloadcms/bundler-webpack": "1.0.7",
"@payloadcms/db-mongodb": "1.5.2",
"@payloadcms/richtext-lexical": "0.11.2",
"@payloadcms/db-mongodb": "1.7.2",
"@payloadcms/richtext-lexical": "0.11.3",
"cross-env": "7.0.3",
"dotenv": "^16.4.5",
"language-tags": "1.0.9",
"luxon": "3.4.4",
"payload": "2.23.1",
"luxon": "3.5.0",
"payload": "2.26.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",
@ -43,7 +45,7 @@
"@types/styled-components": "5.1.34",
"copyfiles": "2.4.1",
"nodemon": "3.1.4",
"prettier": "3.3.2",
"prettier": "3.3.3",
"ts-node": "10.9.2",
"ts-unused-exports": "10.1.0",
"typescript": "5.4.5"

View File

@ -1,7 +1,7 @@
import { Access } from "payload/config";
import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { isDefined, isUndefined } from "../../utils/asserts";
import { RecordersRoles } from "../../shared/payload/constants";
export const mustBeAdmin: Access<unknown, Recorder> = ({ req: { user } }): boolean => {
if (isUndefined(user)) return false;

View File

@ -1,7 +1,7 @@
import { Access } from "payload/config";
import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { isUndefined } from "../../utils/asserts";
import { RecordersRoles } from "../../shared/payload/constants";
export const mustBeAdminOrSelf: Access<unknown, Recorder> = ({ req: { user } }) => {
if (isUndefined(user)) return false;

View File

@ -1,7 +1,7 @@
import { User } from "payload/auth";
import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { isDefined, isUndefined } from "../../utils/asserts";
import { RecordersRoles } from "../../shared/payload/constants";
export const shownOnlyToAdmin = ({ user }: { user: User }): boolean => {
if (isUndefined(user)) return false;

View File

@ -1,7 +1,7 @@
import { FieldAccess } from "payload/types";
import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { isDefined, isUndefined } from "../../utils/asserts";
import { RecordersRoles } from "../../shared/payload/constants";
export const mustBeAdmin: FieldAccess<any, any, Recorder> = ({ req: { user } }): boolean => {
if (isUndefined(user)) return false;

View File

@ -1,6 +1,6 @@
import { Block } from "payload/types";
import { AttributeTypes, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { AttributeTypes, Collections } from "../../shared/payload/constants";
export const numberBlock: Block = {
slug: "numberBlock",

View File

@ -1,6 +1,6 @@
import { Block } from "payload/types";
import { AttributeTypes, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { Collections, AttributeTypes } from "../../shared/payload/constants";
export const tagsBlock: Block = {
slug: "tagsBlock",

View File

@ -1,6 +1,6 @@
import { Block } from "payload/types";
import { AttributeTypes, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { AttributeTypes, Collections } from "../../shared/payload/constants";
export const textBlock: Block = {
slug: "textBlock",

View File

@ -1,5 +1,5 @@
import { Block } from "payload/types";
import { BreakBlockType } from "../constants";
import { BreakBlockType } from "../shared/payload/constants";
export const breakBlock: Block = {
slug: "breakBlock",

View File

@ -1,11 +1,11 @@
import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/fields/mustBeAdmin";
import { AttributeTypes, CollectionGroups, Collections } from "../../constants";
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 { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups, AttributeTypes } from "../../shared/payload/constants";
const fields = {
slug: "slug",

View File

@ -1,9 +1,9 @@
import { CollectionGroups, Collections } from "../../constants";
import { attributesField } from "../../fields/attributesField/attributesField";
import { creditsField } from "../../fields/creditsField/creditsField";
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 { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";
@ -31,6 +31,7 @@ export const Audios = buildCollectionConfig({
admin: {
group: CollectionGroups.Media,
preview: ({ id }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/audios/${id}`,
useAsTitle: fields.filename,
defaultColumns: [
fields.filename,
fields.thumbnail,

View File

@ -1,6 +1,4 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointAudio, EndpointAudioPreview, PayloadMedia } from "../../../sdk";
import { Audio } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isAudio, isMediaThumbnail, isNotEmpty } from "../../../utils/asserts";
@ -9,8 +7,16 @@ import {
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants";
import {
PayloadMedia,
EndpointAudioPreview,
EndpointAudio,
} from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = {
method: "get",
@ -40,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);
}
@ -75,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<EndpointAudio> => {
const { translations, createdAt, updatedAt, filesize, credits, id } = audio;
return {
...convertAudioToEndpointAudioPreview(audio),
createdAt,
@ -91,5 +97,8 @@ const convertAudioToEndpointAudio = (audio: Audio & PayloadMedia): EndpointAudio
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Audios, id)
),
};
};

View File

@ -4,7 +4,6 @@ import {
languageBasedFilters,
publishStatusFilters,
} from "../../components/QuickFilters";
import { CollectionGroups, Collections } from "../../constants";
import { creditsField } from "../../fields/creditsField/creditsField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
@ -20,6 +19,7 @@ import { beforeValidatePopulateNameField } from "./hooks/beforeValidatePopulateN
import { validateDate } from "./validations/validateDate";
import { validateEventsTranslationsDescription } from "./validations/validateEventsTranslationsDescription";
import { validateEventsTranslationsTitle } from "./validations/validateEventsTranslationsTitle";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
name: "name",

View File

@ -1,6 +1,6 @@
import { Block } from "payload/types";
import { Collections } from "../../../constants";
import { translatedFields } from "../../../fields/translatedFields/translatedFields";
import { Collections } from "../../../shared/payload/constants";
export const collectibleBlock: Block = {
slug: "collectibleBlock",

View File

@ -1,5 +1,5 @@
import { Block } from "payload/types";
import { Collections } from "../../../constants";
import { Collections } from "../../../shared/payload/constants";
export const pageBlock: Block = {
slug: "pageBlock",

View File

@ -1,12 +1,16 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointChronologyEvent, EndpointSource } from "../../../sdk";
import { ChronologyEvent, CollectibleBlock } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isNotEmpty, isPayloadType } from "../../../utils/asserts";
import { convertCreditsToEndpointCredits, getDomainFromUrl } from "../../../utils/endpoints";
import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { Collections } from "../../../shared/payload/constants";
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<EndpointChronologyEvent>(eventToEndpointEvent);
.map<EndpointChronologyEvent>(convertEventToEndpointEvent);
res.status(200).json(events);
},
};
export const eventToEndpointEvent = ({
export const convertEventToEndpointEvent = ({
date: { year, day, month },
events,
id,
@ -80,31 +84,35 @@ export const eventToEndpointEvent = ({
})),
});
const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): EndpointSource[] => {
const handleSources = (
sources: ChronologyEvent["events"][number]["sources"]
): EndpointRelation[] => {
return (
sources?.flatMap<EndpointSource>((source) => {
sources?.flatMap<EndpointRelation>((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":
return {
type: "url",
url: source.url,
label: getDomainFromUrl(source.url),
value: {
url: source.url,
label: getDomainFromUrl(source.url),
},
};
}
}) ?? []
@ -113,7 +121,7 @@ const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): E
const handleRange = (
rawRange: CollectibleBlock["range"]
): Extract<EndpointSource, { type: "collectible" }>["range"] => {
): EndpointCollectibleRelationRange | undefined => {
const range = rawRange?.[0];
switch (range?.blockType) {

View File

@ -1,7 +1,7 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { CollectionEndpoint } from "../../../types/payload";
import { eventToEndpointEvent } from "./getAllEndpoint";
import { convertEventToEndpointEvent } from "./getAllEndpoint";
import { Collections } from "../../../shared/payload/constants";
export const getByID: CollectionEndpoint = {
method: "get",
@ -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);
}

View File

@ -1,5 +1,5 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Collections } from "../../../shared/payload/constants";
import { StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { plainTextToLexical } from "../../../utils/string";

View File

@ -1,14 +1,5 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { Where } from "payload/types";
import {
CollectibleBindingTypes,
CollectibleNature,
CollectiblePageOrders,
CollectionGroups,
Collections,
} from "../../constants";
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";
@ -18,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";
@ -28,6 +17,13 @@ import { getBySlugEndpointGallery } from "./endpoints/getBySlugEndpointGallery";
import { getBySlugEndpointGalleryImage } from "./endpoints/getBySlugEndpointGalleryImage";
import { getBySlugEndpointScanPage } from "./endpoints/getBySlugEndpointScanPage";
import { getBySlugEndpointScans } from "./endpoints/getBySlugEndpointScans";
import {
Collections,
CollectionGroups,
CollectibleNature,
CollectibleBindingTypes,
CollectiblePageOrders,
} from "../../shared/payload/constants";
const fields = {
status: "_status",
@ -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

@ -1,6 +1,9 @@
import { CollectibleNature, Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectible, EndpointCollectiblePreview } from "../../../sdk";
import { Collections, CollectibleNature } from "../../../shared/payload/constants";
import {
EndpointCollectiblePreview,
EndpointCollectible,
} from "../../../shared/payload/endpoint-types";
import { Collectible } from "../../../types/collections";
import {
isAudio,
@ -17,8 +20,8 @@ import {
import {
convertAttributesToEndpointAttributes,
convertImageToEndpointPayloadImage,
convertRelationshipsToEndpointRelations,
convertScanToEndpointScanImage,
convertSourceToEndpointSource,
getDomainFromUrl,
} from "../../../utils/endpoints";
import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID";
@ -26,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 = ({
@ -61,8 +65,11 @@ export const convertCollectibleToEndpointCollectiblePreview = ({
...handlePrice(price, priceEnabled),
});
const convertCollectibleToEndpointCollectible = (collectible: Collectible): EndpointCollectible => {
const convertCollectibleToEndpointCollectible = async (
collectible: Collectible
): Promise<EndpointCollectible> => {
const {
id,
nature,
urls,
subitems,
@ -77,8 +84,6 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp
weightEnabled,
pageInfo,
pageInfoEnabled,
parentItems,
folders,
backgroundImage,
translations,
scans: rawScans,
@ -125,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)
),
};
};

View File

@ -1,11 +1,9 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectibleGallery } from "../../../sdk";
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),
},
],
};
},
});

View File

@ -1,11 +1,11 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointCollectibleGalleryImage } from "../../../sdk";
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,13 @@ export const getBySlugEndpointGalleryImage: CollectionEndpoint = {
})) ?? [],
...(isDefined(previousIndex) ? { previousIndex } : {}),
...(isDefined(nextIndex) ? { nextIndex } : {}),
backlinks: [
{
type: Collections.Collectibles,
subpage: "gallery",
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
};
res.status(200).send(scanPage);

View File

@ -1,13 +1,11 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointCollectibleScanPage } from "../../../sdk";
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,13 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = {
})) ?? [],
...(isDefined(previousIndex) ? { previousIndex } : {}),
...(isDefined(nextIndex) ? { nextIndex } : {}),
backlinks: [
{
type: Collections.Collectibles,
subpage: "scans",
value: convertCollectibleToEndpointCollectiblePreview(collectible),
},
],
};
res.status(200).send(scanPage);

View File

@ -1,14 +1,14 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectibleScans } from "../../../sdk";
import { Collections } from "../../../shared/payload/constants";
import { EndpointCollectibleScans } from "../../../shared/payload/endpoint-types";
import { Collectible } from "../../../types/collections";
import { isImage, isNotEmpty, isPayloadType, isScan } from "../../../utils/asserts";
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),
},
],
};
},
});

View File

@ -1,9 +1,9 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroups, Collections } from "../../constants";
import { iconField } from "../../fields/iconField/iconField";
import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
slug: "slug",

View File

@ -1,10 +1,10 @@
import { text } from "payload/dist/fields/validations";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { importFromStrapi } from "./endpoints/importFromStrapi";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
id: "id",

View File

@ -1,7 +1,7 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { Currency } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { Collections } from "../../../shared/payload/constants";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",

View File

@ -1,5 +1,5 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Collections } from "../../../shared/payload/constants";
type StrapiLanguage = {
code: string;

View File

@ -1,9 +1,9 @@
import { CollectionGroups, Collections } from "../../constants";
import { attributesField } from "../../fields/attributesField/attributesField";
import { creditsField } from "../../fields/creditsField/creditsField";
import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { CollectionGroups, Collections } from "../../shared/payload/constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";

View File

@ -1,6 +1,4 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointFile, EndpointFilePreview, PayloadMedia } from "../../../sdk";
import { File } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isFile, isMediaThumbnail, isNotEmpty } from "../../../utils/asserts";
@ -9,8 +7,16 @@ import {
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants";
import {
PayloadMedia,
EndpointFilePreview,
EndpointFile,
} from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = {
method: "get",
@ -40,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);
}
@ -75,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<EndpointFile> => {
const { translations, createdAt, updatedAt, filesize, credits, id } = file;
return {
...convertFileToEndpointFilePreview(file),
@ -92,5 +98,8 @@ const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => {
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Files, id)
),
};
};

View File

@ -1,11 +1,8 @@
import { CollectionGroups, Collections } from "../../constants";
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 { Folder } from "../../types/collections";
import { isPayloadType } from "../../utils/asserts";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
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;
},
},
});

View File

@ -1,6 +1,7 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointFolder, EndpointFolderPreview } from "../../../sdk";
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";
import {
isAudio,
@ -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<EndpointFolder> => {
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)
),
};
};

View File

@ -1,10 +1,10 @@
import { CollectionConfig } from "payload/types";
import { QuickFilters, languageBasedFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
name: "name",

View File

@ -1,9 +1,9 @@
import { Collections } from "../../constants";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import { attributesField } from "../../fields/attributesField/attributesField";
import { creditsField } from "../../fields/creditsField/creditsField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { Collections } from "../../shared/payload/constants";
import { createEditor } from "../../utils/editor";
import {
buildImageCollectionConfig,
@ -36,6 +36,7 @@ export const Images = buildImageCollectionConfig({
admin: {
preview: ({ id }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/images/${id}`,
defaultColumns: [fields.filename, fields.posts, fields.updatedAt],
useAsTitle: fields.filename,
},
upload: {
imageSizes: [

View File

@ -1,6 +1,4 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointImage, EndpointImagePreview, PayloadImage } from "../../../sdk";
import { Image } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isImage, isNotEmpty, isPayloadImage } from "../../../utils/asserts";
@ -8,9 +6,17 @@ import {
convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits,
convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
convertSizesToPayloadImages,
getLanguageId,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants";
import {
PayloadImage,
EndpointImagePreview,
EndpointImage,
} from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = {
method: "get",
@ -40,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);
}
@ -89,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<EndpointImage> => {
const { translations, createdAt, updatedAt, filesize, credits, id } = image;
return {
...convertImageToEndpointImagePreview(image),
createdAt,
@ -105,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)
),
};
};

View File

@ -1,11 +1,11 @@
import { text } from "payload/dist/fields/validations";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { importFromStrapi } from "./endpoints/importFromStrapi";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
id: "id",

View File

@ -1,7 +1,7 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { Language } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { Collections } from "../../../shared/payload/constants";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",

View File

@ -1,5 +1,5 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Collections } from "../../../shared/payload/constants";
type StrapiLanguage = {
name: string;

View File

@ -1,6 +1,6 @@
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { Collections } from "../../constants";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import { Collections } from "../../shared/payload/constants";
import {
buildImageCollectionConfig,
generateOpenGraphSize,

View File

@ -1,11 +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 { CollectionGroups, Collections } from "../../constants";
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";
@ -17,6 +14,7 @@ import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
slug: "slug",
@ -31,8 +29,7 @@ const fields = {
summary: "summary",
content: "content",
credits: "credits",
collectibles: "collectibles",
folders: "folders",
sourceUrls: "sourceUrls",
} as const satisfies Record<string, string>;
export const Pages = buildVersionedCollectionConfig({
@ -44,13 +41,7 @@ export const Pages = buildVersionedCollectionConfig({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
defaultColumns: [
fields.slug,
fields.thumbnail,
fields.backgroundImage,
fields.translations,
fields.folders,
],
defaultColumns: [fields.slug, fields.thumbnail, fields.backgroundImage, fields.translations],
group: CollectionGroups.Collections,
preview: ({ slug }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/pages/${slug}`,
components: {
@ -123,31 +114,18 @@ export const Pages = buildVersionedCollectionConfig({
}),
},
creditsField({ name: fields.credits }),
{
name: fields.sourceUrls,
label: "Source URLs",
type: "text",
hasMany: true,
admin: {
description:
"If the content originates from an external source (e.g: fandom.com, an online interview...) you can add a link to the original page(s) here",
width: "50%",
},
},
],
}),
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,13 +1,17 @@
import {
BreakBlockType,
Collections,
RichTextContent,
isBlockNodeBreakBlock,
isBlockNodeSectionBlock,
isNodeBlockNode,
} from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointPage, EndpointPagePreview, TableOfContentEntry } from "../../../sdk";
import { findIncomingRelationships } from "payloadcms-relationships";
import { Collections, BreakBlockType } from "../../../shared/payload/constants";
import {
EndpointPagePreview,
EndpointPage,
TableOfContentEntry,
} from "../../../shared/payload/endpoint-types";
import {
RichTextContent,
isNodeBlockNode,
isBlockNodeSectionBlock,
isBlockNodeBreakBlock,
} from "../../../shared/payload/rich-text";
import { Page } from "../../../types/collections";
import { isImage, isNotEmpty, isPayloadType } from "../../../utils/asserts";
import {
@ -15,7 +19,8 @@ import {
convertCreditsToEndpointCredits,
convertImageToEndpointPayloadImage,
convertRTCToEndpointRTC,
convertSourceToEndpointSource,
convertRelationshipsToEndpointRelations,
getDomainFromUrl,
} from "../../../utils/endpoints";
import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID";
@ -46,8 +51,8 @@ export const convertPageToEndpointPagePreview = ({
updatedAt,
});
const convertPageToEndpointPage = (page: Page): EndpointPage => {
const { translations, collectibles, folders, backgroundImage, createdAt, updatedBy } = page;
const convertPageToEndpointPage = async (page: Page): Promise<EndpointPage> => {
const { translations, backgroundImage, createdAt, updatedBy, id } = page;
return {
...convertPageToEndpointPagePreview(page),
@ -55,7 +60,17 @@ const convertPageToEndpointPage = (page: Page): EndpointPage => {
? { backgroundImage: convertImageToEndpointPayloadImage(backgroundImage) }
: {}),
translations: translations.map(
({ content, language, sourceLanguage, title, pretitle, subtitle, summary, credits }) => ({
({
content,
language,
sourceLanguage,
title,
pretitle,
subtitle,
summary,
credits,
sourceUrls,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
@ -65,13 +80,20 @@ const convertPageToEndpointPage = (page: Page): EndpointPage => {
content: convertRTCToEndpointRTC(content),
toc: handleToc(content),
credits: convertCreditsToEndpointCredits(credits),
sourceUrls:
sourceUrls?.map((url) => ({
url,
label: getDomainFromUrl(url),
})) ?? [],
})
),
createdAt,
...(isPayloadType(updatedBy)
? { updatedBy: convertRecorderToEndpointRecorderPreview(updatedBy) }
: {}),
parentPages: convertSourceToEndpointSource({ collectibles, folders }),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Pages, id)
),
};
};

View File

@ -2,10 +2,10 @@ import { mustBeAdmin as mustBeAdminForCollections } from "../../accesses/collect
import { mustBeAdminOrSelf } from "../../accesses/collections/mustBeAdminOrSelf";
import { mustBeAdmin as mustBeAdminForFields } from "../../accesses/fields/mustBeAdmin";
import { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, RecordersRoles } from "../../constants";
import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { Collections, CollectionGroups, RecordersRoles } from "../../shared/payload/constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";

View File

@ -1,6 +1,4 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointRecorder, EndpointRecorderPreview } from "../../../sdk";
import { Recorder } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isImage, isPayloadType } from "../../../utils/asserts";
@ -8,6 +6,8 @@ import {
convertImageToEndpointPayloadImage,
convertRTCToEndpointRTC,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants";
import { EndpointRecorderPreview, EndpointRecorder } from "../../../shared/payload/endpoint-types";
export const getByID: CollectionEndpoint = {
method: "get",

View File

@ -1,10 +1,10 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Recorder } from "../../../types/collections";
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
import { isDefined } from "../../../utils/asserts";
import { uploadStrapiImage } from "../../../utils/localApi";
import { Collections } from "../../../shared/payload/constants";
type StrapiRecorder = {
username: string;

View File

@ -1,6 +1,6 @@
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { Collections } from "../../constants";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import { Collections } from "../../shared/payload/constants";
import { buildImageCollectionConfig, generateWebpSize } from "../../utils/imageCollectionConfig";
const fields = {
@ -17,6 +17,7 @@ export const Scans = buildImageCollectionConfig({
plural: "Scans",
},
admin: {
useAsTitle: fields.filename,
defaultColumns: [fields.filename, fields.updatedAt],
hidden: shownOnlyToAdmin,
},

View File

@ -1,10 +1,10 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
slug: "slug",

View File

@ -1,12 +1,10 @@
import { CollectionGroups, Collections } from "../../constants";
import { attributesField } from "../../fields/attributesField/attributesField";
import { componentField } from "../../fields/componentField/componentField";
import { creditsField } from "../../fields/creditsField/creditsField";
import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { Video } from "../../types/collections";
import { isPayloadType } from "../../utils/asserts";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";
@ -40,6 +38,7 @@ export const Videos = buildCollectionConfig({
labels: { singular: "Video", plural: "Videos" },
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroups.Media,
preview: ({ id }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/videos/${id}`,
defaultColumns: [
@ -129,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

@ -1,6 +1,4 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointVideo, EndpointVideoPreview, PayloadMedia } from "../../../sdk";
import { Video } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import {
@ -17,8 +15,16 @@ import {
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC,
convertRelationshipsToEndpointRelations,
getLanguageId,
} from "../../../utils/endpoints";
import { Collections } from "../../../shared/payload/constants";
import {
PayloadMedia,
EndpointVideoPreview,
EndpointVideo,
} from "../../../shared/payload/endpoint-types";
import { findIncomingRelationships } from "payloadcms-relationships";
export const getByID: CollectionEndpoint = {
method: "get",
@ -48,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);
}
@ -94,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<EndpointVideo> => {
const { translations, createdAt, updatedAt, filesize, platform, platformEnabled, credits, id } =
video;
return {
@ -121,5 +127,8 @@ const convertVideoToEndpointVideo = (video: Video & PayloadMedia): EndpointVideo
}
: {}),
credits: convertCreditsToEndpointCredits(credits),
backlinks: convertRelationshipsToEndpointRelations(
await findIncomingRelationships(Collections.Videos, id)
),
};
};

View File

@ -1,8 +1,7 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroups, Collections } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
url: "url",
@ -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 } }),
}),
],
});

View File

@ -1,13 +1,18 @@
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { CollectionGroups, Collections } from "../../constants";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
export const VideosSubtitles = buildCollectionConfig({
slug: Collections.VideosSubtitles,
labels: { singular: "Video Subtitle", plural: "Videos Subtitles" },
admin: { group: CollectionGroups.Media, disableDuplicate: true, hidden: shownOnlyToAdmin },
admin: {
useAsTitle: "filename",
group: CollectionGroups.Media,
disableDuplicate: true,
hidden: shownOnlyToAdmin,
},
upload: {
mimeTypes: ["text/*"],
// mimeTypes: ["text/*"], Disable because of a bug on Chrome windows where the MIME is detected as application/octet-stream instead of text/vtt or text/plain
disableLocalStorage: true,
},
timestamps: false,

View File

@ -1,10 +1,10 @@
import { GlobalConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants";
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";
import { globalAfterChangeSendChangesWebhook } from "../../hooks/afterOperationSendChangesWebhook";
const fields = {
homeBackgroundImage: "homeBackgroundImage",
@ -32,7 +32,7 @@ export const WebsiteConfig: GlobalConfig = {
access: { update: mustBeAdmin, read: mustBeAdmin },
endpoints: [getConfigEndpoint],
hooks: {
afterChange: [globalAfterChangeWebhook],
afterChange: [globalAfterChangeSendChangesWebhook],
},
fields: [
rowField([

View File

@ -1,10 +1,10 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointWebsiteConfig } from "../../../sdk";
import { CollectionEndpoint } from "../../../types/payload";
import { isImage, isPayloadType } from "../../../utils/asserts";
import { convertImageToEndpointPayloadImage } from "../../../utils/endpoints";
import { convertFolderToEndpointFolderPreview } from "../../Folders/endpoints/getBySlugEndpoint";
import { Collections } from "../../../shared/payload/constants";
import { EndpointWebsiteConfig } from "../../../shared/payload/endpoint-types";
export const getConfigEndpoint: CollectionEndpoint = {
method: "get",

View File

@ -1,12 +1,12 @@
import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { QuickFilters, languageBasedFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { Collections, CollectionGroups } from "../../shared/payload/constants";
const fields = {
name: "name",

View File

@ -1,8 +1,8 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointWording } from "../../../sdk";
import { CollectionEndpoint } from "../../../types/payload";
import { isPayloadType } from "../../../utils/asserts";
import { Collections } from "../../../shared/payload/constants";
import { EndpointWording } from "../../../shared/payload/endpoint-types";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",

View File

@ -3,7 +3,7 @@ import QueryString from "qs";
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { LanguageCodes } from "../constants";
import { LanguageCodes } from "../shared/payload/constants";
type Props = {
slug: string;

View File

@ -1,312 +0,0 @@
import { EndpointAudioPreview, EndpointImagePreview, EndpointVideoPreview } from "./sdk";
import type { BreakBlock, SectionBlock, TranscriptBlock } from "./types/collections";
// END MOCKING SECTION
export enum Collections {
Attributes = "attributes",
Audios = "audios",
ChronologyEvents = "chronology-events",
Collectibles = "collectibles",
CreditsRole = "credits-roles",
Currencies = "currencies",
Files = "files",
Folders = "folders",
GenericContents = "generic-contents",
Images = "images",
Languages = "languages",
MediaThumbnails = "media-thumbnails",
Pages = "pages",
Recorders = "recorders",
Scans = "scans",
Tags = "tags",
Videos = "videos",
VideosChannels = "videos-channels",
VideosSubtitles = "videos-subtitles",
Wordings = "wordings",
WebsiteConfig = "website-config",
}
export enum CollectionGroups {
Collections = "Collections",
Media = "Media",
Meta = "Meta",
}
export enum LanguageCodes {
en = "English",
fr = "French",
ja = "Japan",
es = "Spanish",
"pt-br" = "Portuguese",
"zh" = "Chinese",
}
export enum BreakBlockType {
sceneBreak = "Scene break",
space = "Empty space",
solidLine = "Solid line",
dottedLine = "Dotted line",
}
export enum CollectibleBindingTypes {
Paperback = "Paperback",
Hardcover = "Hardcover",
}
export enum CollectiblePageOrders {
LeftToRight = "Left to right",
RightToLeft = "Right to left",
}
export enum CollectibleNature {
Physical = "Physical",
Digital = "Digital",
}
export enum CollectibleContentType {
None = "None",
Indexes = "Index-based",
Pages = "Page-based",
}
export enum RecordersRoles {
Admin = "Admin",
Recorder = "Recorder",
Api = "Api",
}
export enum CollectionStatus {
Draft = "draft",
Published = "published",
}
export enum AttributeTypes {
Number = "Number",
Text = "Text",
Tags = "Tags",
}
/* WEB HOOKS */
export type AfterOperationWebHookMessage = {
collection: Collections;
id?: string;
addedDependantIds: string[];
urls: string[];
};
/* RICH TEXT */
export type RichTextContent = {
root: {
children: RichTextNode[];
direction: ("ltr" | "rtl") | null;
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
indent: number;
type: string;
version: number;
};
[k: string]: unknown;
};
export type RichTextNode = {
type: string;
version: number;
[k: string]: unknown;
};
export interface RichTextNodeWithChildren extends RichTextNode {
children: RichTextNode[];
}
export interface RichTextParagraphNode extends RichTextNodeWithChildren {
type: "paragraph";
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
}
export interface RichTextListNode extends RichTextNode {
type: "list";
children: RichTextNodeWithChildren[];
listType: string;
}
export interface RichTextListNumberNode extends RichTextListNode {
listType: "number";
}
export interface RichTextListBulletNode extends RichTextListNode {
listType: "bullet";
}
export interface RichTextListCheckNode extends RichTextListNode {
listType: "check";
}
export interface RichTextLinebreakNode extends RichTextNode {
type: "linebreak";
}
export interface RichTextUploadNode extends RichTextNode {
type: "upload";
relationTo: string;
}
export interface RichTextUploadImageNode extends RichTextUploadNode {
relationTo: Collections.Images;
value: EndpointImagePreview;
}
export interface RichTextUploadVideoNode extends RichTextUploadNode {
relationTo: Collections.Videos;
value: EndpointVideoPreview;
}
export interface RichTextUploadAudioNode extends RichTextUploadNode {
relationTo: Collections.Audios;
value: EndpointAudioPreview;
}
export interface RichTextTextNode extends RichTextNode {
type: "text";
format: number;
text: string;
}
export interface RichTextTabNode extends RichTextNode {
type: "tab";
format: number;
}
export interface RichTextLinkNode extends RichTextNodeWithChildren {
type: "link";
fields: {
linkType: "internal" | "custom";
};
}
export interface RichTextLinkInternalNode extends RichTextLinkNode {
fields: {
linkType: "internal";
newTab: boolean;
doc: {
relationTo: string;
value: any;
};
};
}
export interface RichTextLinkCustomNode extends RichTextLinkNode {
fields: {
linkType: "custom";
newTab: boolean;
url: string;
};
}
export interface RichTextBlockNode extends RichTextNode {
type: "block";
fields: {
blockType: string;
};
}
export interface RichTextSectionBlock extends RichTextBlockNode {
fields: SectionBlock;
anchorHash: string;
}
export interface RichTextTranscriptBlock extends RichTextBlockNode {
fields: TranscriptBlock;
}
export interface RichTextBreakBlock extends RichTextBlockNode {
fields: BreakBlock;
anchorHash: string;
}
export const isNodeParagraphNode = (node: RichTextNode): node is RichTextParagraphNode =>
node.type === "paragraph";
export const isNodeUploadNode = (node: RichTextNode): node is RichTextUploadNode =>
node.type === "upload";
export const isUploadNodeImageNode = (node: RichTextUploadNode): node is RichTextUploadImageNode =>
node.relationTo === Collections.Images;
export const isUploadNodeVideoNode = (node: RichTextUploadNode): node is RichTextUploadVideoNode =>
node.relationTo === Collections.Videos;
export const isUploadNodeAudioNode = (node: RichTextUploadNode): node is RichTextUploadAudioNode =>
node.relationTo === Collections.Audios;
export const isNodeListNode = (node: RichTextNode): node is RichTextListNode =>
node.type === "list";
export const isListNodeNumberListNode = (node: RichTextListNode): node is RichTextListNumberNode =>
node.listType === "number";
export const isListNodeBulletListNode = (node: RichTextListNode): node is RichTextListBulletNode =>
node.listType === "bullet";
export const isListNodeCheckListNode = (node: RichTextListNode): node is RichTextListCheckNode =>
node.listType === "check";
export const isNodeLinebreakNode = (node: RichTextNode): node is RichTextLinebreakNode =>
node.type === "linebreak";
export const isNodeTextNode = (node: RichTextNode): node is RichTextTextNode =>
node.type === "text";
export const isNodeTabNode = (node: RichTextNode): node is RichTextTabNode => node.type === "tab";
export const isNodeLinkNode = (node: RichTextNode): node is RichTextLinkNode =>
node.type === "link";
export const isLinkNodeInternalLinkNode = (
node: RichTextLinkNode
): node is RichTextLinkInternalNode => node.fields.linkType === "internal";
export const isLinkNodeCustomLinkNode = (node: RichTextLinkNode): node is RichTextLinkCustomNode =>
node.fields.linkType === "custom";
export const isNodeBlockNode = (node: RichTextNode): node is RichTextBlockNode =>
node.type === "block";
export const isBlockNodeSectionBlock = (node: RichTextBlockNode): node is RichTextSectionBlock =>
node.fields.blockType === "sectionBlock";
export const isBlockNodeTranscriptBlock = (
node: RichTextBlockNode
): node is RichTextTranscriptBlock => node.fields.blockType === "transcriptBlock";
export const isBlockNodeBreakBlock = (node: RichTextBlockNode): node is RichTextBreakBlock =>
node.fields.blockType === "breakBlock";
/* BLOCKS */
/* TODO: TO BE REMOVED WHEN https://github.com/payloadcms/payload/issues/5216 is closed */
export interface CueBlock {
content: RichTextContent;
blockType: "cueBlock";
id?: string | null;
blockName?: string | null;
}
export interface LineBlock {
content: RichTextContent;
blockType: "lineBlock";
id?: string | null;
blockName?: string | null;
}
export interface GenericBlock {
id?: string | null;
blockName?: string | null;
blockType: string;
}
export const isBlockCueBlock = (block: GenericBlock): block is CueBlock =>
block.blockType === "cueBlock";
export const isBlockLineBlock = (block: GenericBlock): block is LineBlock =>
block.blockType === "lineBlock";

View File

@ -1,11 +1,27 @@
import payload from "payload";
import { Endpoint } from "payload/config";
import { Collections } from "../constants";
import { EndpointAllIds } from "../sdk";
import { Collections } from "../shared/payload/constants";
import { EndpointChange } from "../shared/payload/webhooks";
import {
getEndpointChangesForAudio,
getEndpointChangesForChronologyEvent,
getEndpointChangesForCollectible,
getEndpointChangesForCurrency,
getEndpointChangesForFile,
getEndpointChangesForFolder,
getEndpointChangesForImage,
getEndpointChangesForLanguage,
getEndpointChangesForPage,
getEndpointChangesForRecorder,
getEndpointChangesForVideo,
getEndpointChangesForWebsiteConfig,
getEndpointChangesForWording,
} from "../hooks/afterOperationSendChangesWebhook";
import { uniqueBy } from "../utils/array";
export const getAllIds: Endpoint = {
export const getAllEndpoint: Endpoint = {
method: "get",
path: "/all-ids",
path: "/all",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
@ -95,18 +111,22 @@ export const getAllIds: Endpoint = {
},
});
const result: EndpointAllIds = {
collectibles: { slugs: collectibles.docs.map(({ slug }) => slug) },
pages: { slugs: pages.docs.map(({ slug }) => slug) },
folders: { slugs: folders.docs.map(({ slug }) => slug) },
videos: { ids: videos.docs.map(({ id }) => id) },
audios: { ids: audios.docs.map(({ id }) => id) },
images: { ids: images.docs.map(({ id }) => id) },
files: { ids: files.docs.map(({ id }) => id) },
recorders: { ids: recorders.docs.map(({ id }) => id) },
chronologyEvents: { ids: chronologyEvents.docs.map(({ id }) => id) },
};
const result: EndpointChange[] = [
...getEndpointChangesForWebsiteConfig(),
...getEndpointChangesForLanguage(),
...getEndpointChangesForCurrency(),
...getEndpointChangesForWording(),
...folders.docs.flatMap(getEndpointChangesForFolder),
...pages.docs.flatMap(getEndpointChangesForPage),
...collectibles.docs.flatMap(getEndpointChangesForCollectible),
...audios.docs.flatMap(getEndpointChangesForAudio),
...images.docs.flatMap(getEndpointChangesForImage),
...videos.docs.flatMap(getEndpointChangesForVideo),
...files.docs.flatMap(getEndpointChangesForFile),
...recorders.docs.flatMap(getEndpointChangesForRecorder),
...chronologyEvents.docs.flatMap(getEndpointChangesForChronologyEvent),
];
return res.status(200).send(result);
return res.status(200).send(uniqueBy(result, ({ url }) => url));
},
};

View File

@ -1,207 +0,0 @@
import payload from "payload";
import { Endpoint } from "payload/config";
import { Collections } from "../constants";
import { EndpointAllSDKUrls, getSDKEndpoint } from "../sdk";
import { Collectible } from "../types/collections";
export const getAllSDKUrlsEndpoint: Endpoint = {
method: "get",
path: "/all-sdk-urls",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const collectibles = await payload.find({
collection: Collections.Collectibles,
depth: 0,
pagination: false,
user: req.user,
where: {
_status: {
equals: "published",
},
},
});
const pages = await payload.find({
collection: Collections.Pages,
depth: 0,
pagination: false,
user: req.user,
where: {
_status: {
equals: "published",
},
},
});
const folders = await payload.find({
collection: Collections.Folders,
depth: 0,
pagination: false,
user: req.user,
});
const videos = await payload.find({
collection: Collections.Videos,
depth: 0,
pagination: false,
user: req.user,
});
const audios = await payload.find({
collection: Collections.Audios,
depth: 0,
pagination: false,
user: req.user,
});
const images = await payload.find({
collection: Collections.Images,
depth: 0,
pagination: false,
user: req.user,
});
const files = await payload.find({
collection: Collections.Files,
depth: 0,
pagination: false,
user: req.user,
});
const recorders = await payload.find({
collection: Collections.Recorders,
depth: 0,
pagination: false,
user: req.user,
});
const chronologyEvents = await payload.find({
collection: Collections.ChronologyEvents,
depth: 0,
pagination: false,
user: req.user,
where: {
_status: {
equals: "published",
},
},
});
const urls = new Set([
getSDKEndpoint.getConfigEndpoint(),
getSDKEndpoint.getLanguagesEndpoint(),
getSDKEndpoint.getCurrenciesEndpoint(),
getSDKEndpoint.getWordingsEndpoint(),
...folders.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Folders, doc)),
...pages.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Pages, doc)),
...chronologyEvents.docs.flatMap((doc) =>
getSDKUrlsForDocument(Collections.ChronologyEvents, doc)
),
...videos.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Videos, doc)),
...audios.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Audios, doc)),
...images.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Images, doc)),
...files.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Files, doc)),
...collectibles.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Collectibles, doc)),
...recorders.docs.flatMap((doc) => getSDKUrlsForDocument(Collections.Recorders, doc)),
]);
const result: EndpointAllSDKUrls = {
urls: [...urls],
};
return res.status(200).send(result);
},
};
export const getSDKUrlsForDocument = (collection: Collections, doc: any): string[] => {
switch (collection) {
case Collections.WebsiteConfig:
return [getSDKEndpoint.getConfigEndpoint()];
case Collections.Folders:
return [getSDKEndpoint.getFolderEndpoint(doc.slug)];
case Collections.Languages:
return [getSDKEndpoint.getLanguagesEndpoint()];
case Collections.Currencies:
return [getSDKEndpoint.getCurrenciesEndpoint()];
case Collections.Wordings:
return [getSDKEndpoint.getWordingsEndpoint()];
case Collections.Pages:
return [getSDKEndpoint.getPageEndpoint(doc.slug)];
case Collections.Collectibles: {
const { slug, gallery, scans, scansEnabled } = doc as Collectible;
const urls: string[] = [getSDKEndpoint.getCollectibleEndpoint(slug)];
if (gallery && gallery.length > 0) {
urls.push(getSDKEndpoint.getCollectibleGalleryEndpoint(slug));
urls.push(
...gallery.map((_, index) =>
getSDKEndpoint.getCollectibleGalleryImageEndpoint(slug, index.toString())
)
);
}
if (scans && scansEnabled) {
urls.push(getSDKEndpoint.getCollectibleScansEndpoint(slug));
// TODO: Add other pages for cover, obi, dustjacket...
if (scans.pages) {
urls.push(
...scans.pages.map(({ page }) =>
getSDKEndpoint.getCollectibleScanPageEndpoint(slug, page.toString())
)
);
}
}
return urls;
}
case Collections.ChronologyEvents:
return [
getSDKEndpoint.getChronologyEventsEndpoint(),
getSDKEndpoint.getChronologyEventByIDEndpoint(doc.id),
];
case Collections.Images:
return [getSDKEndpoint.getImageByIDEndpoint(doc.id)];
case Collections.Audios:
return [getSDKEndpoint.getAudioByIDEndpoint(doc.id)];
case Collections.Videos:
return [getSDKEndpoint.getVideoByIDEndpoint(doc.id)];
case Collections.Recorders:
return [getSDKEndpoint.getRecorderByIDEndpoint(doc.id)];
case Collections.Files:
return [getSDKEndpoint.getFileByIDEndpoint(doc.id)];
case Collections.Attributes:
case Collections.CreditsRole:
case Collections.GenericContents:
case Collections.MediaThumbnails:
case Collections.Scans:
case Collections.Tags:
case Collections.VideosChannels:
case Collections.VideosSubtitles:
return [];
default: {
console.warn("Unrecognized collection", collection);
return [];
}
}
};

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,9 +1,9 @@
import { array } from "payload/dist/fields/validations";
import { ArrayField } from "payload/types";
import { Collections } from "../../constants";
import { Credits } from "../../types/collections";
import { hasDuplicates, isDefined, isPayloadType, isUndefined } from "../../utils/asserts";
import { rowField } from "../rowField/rowField";
import { Collections } from "../../shared/payload/constants";
type Props = Omit<ArrayField, "type" | "fields">;

View File

@ -1,11 +1,11 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { array } from "payload/dist/fields/validations";
import { ArrayField, Field } from "payload/types";
import { Collections } from "../../constants";
import { hasDuplicates, isDefined, isUndefined } from "../../utils/asserts";
import { rowField } from "../rowField/rowField";
import { Cell } from "./Cell";
import { RowLabel } from "./RowLabel";
import { Collections } from "../../shared/payload/constants";
const fieldsNames = {
language: "language",

View File

@ -0,0 +1,432 @@
import { Collections } from "../shared/payload/constants";
import { SDKEndpointNames, getSDKEndpoint } from "../shared/payload/sdk";
import { EndpointChange } from "../shared/payload/webhooks";
import {
Audio,
ChronologyEvent,
Collectible,
File,
Folder,
Image,
Page,
Recorder,
Relationship,
Video,
} from "../types/collections";
import { isDefined, isPayloadType } from "../utils/asserts";
import {
AfterChangeHook,
AfterDeleteHook,
BeforeChangeHook,
BeforeDeleteHook,
} from "payload/dist/collections/config/types";
import { GeneratedTypes } from "payload";
import { uniqueBy } from "../utils/array";
import { GlobalAfterChangeHook } from "payload/types";
import { findRelationByID } from "payloadcms-relationships/dist/utils";
export const beforeChangePrepareChanges: BeforeChangeHook = async ({
collection,
originalDoc,
context,
data,
}) => {
if ("_status" in data && data._status === "draft") return data;
if (!originalDoc) return data;
context.beforeChangeChanges = await getChanges(
collection.slug as keyof GeneratedTypes["collections"],
originalDoc
);
return data;
};
export const afterChangeSendChangesWebhook: AfterChangeHook = async ({
doc,
collection,
context,
}) => {
if ("_status" in doc && doc._status === "draft") return doc;
const changes = await getChanges(collection.slug as keyof GeneratedTypes["collections"], doc);
const previousChanges = context.beforeChangeChanges as EndpointChange[] | undefined;
if (isDefined(previousChanges)) {
await sendWebhookMessage(uniqueBy([...previousChanges, ...changes], ({ url }) => url));
} else {
await sendWebhookMessage(changes);
}
return doc;
};
export const beforeDeletePrepareChanges: BeforeDeleteHook = async ({ id, collection, context }) => {
context.beforeDeleteChanges = await getChanges(
collection.slug as keyof GeneratedTypes["collections"],
{ id }
);
};
export const afterDeleteSendChangesWebhook: AfterDeleteHook = async ({ doc, context }) => {
const changes = context.beforeDeleteChanges as EndpointChange[] | undefined;
if (isDefined(changes)) {
await sendWebhookMessage(changes);
}
return doc;
};
export const globalAfterChangeSendChangesWebhook: GlobalAfterChangeHook = async ({
doc,
global,
}) => {
const changes: EndpointChange[] = [];
switch (global.slug as keyof GeneratedTypes["globals"]) {
case Collections.WebsiteConfig:
changes.push(...getEndpointChangesForWebsiteConfig());
break;
default:
break;
}
await sendWebhookMessage(uniqueBy(changes, ({ url }) => url));
return doc;
};
const getChanges = async (
slug: keyof GeneratedTypes["collections"],
doc: any
): Promise<EndpointChange[]> => {
if (slug === "relationships") return [];
if (slug === "payload-migrations") return [];
if (slug === "payload-preferences") return [];
let relation: Relationship;
try {
relation = await findRelationByID(slug, doc.id);
} catch (e) {
relation = {
id: "",
document: {
relationTo: slug,
value: doc,
},
outgoingRelations: [],
};
}
const changes: EndpointChange[] = getEndpointChangesFromDocument(relation.document);
relation.incomingRelations?.forEach((relation) =>
changes.push(...getEndpointChangesFromIncomingRelation(relation))
);
relation.outgoingRelations?.forEach((relation) =>
changes.push(...getEndpointChangesFromOutgoingRelation(relation))
);
return uniqueBy(changes, ({ url }) => url);
};
// -------------------------------------------------------------------------------------------------
const getEndpointChangesFromDocument = ({
relationTo,
value,
}: NonNullable<Relationship["document"]>): EndpointChange[] => {
if (!isPayloadType(value)) return [];
switch (relationTo) {
case Collections.Folders:
return getEndpointChangesForFolder(value);
case Collections.Pages:
return getEndpointChangesForPage(value);
case Collections.Collectibles:
return getEndpointChangesForCollectible(value);
case Collections.Audios:
return getEndpointChangesForAudio(value);
case Collections.Images:
return getEndpointChangesForImage(value);
case Collections.Videos:
return getEndpointChangesForVideo(value);
case Collections.Files:
return getEndpointChangesForFile(value);
case Collections.Recorders:
return getEndpointChangesForRecorder(value);
case Collections.ChronologyEvents:
return getEndpointChangesForChronologyEvent(value);
case Collections.Languages:
return getEndpointChangesForLanguage();
case Collections.Currencies:
return getEndpointChangesForCurrency();
case Collections.Wordings:
return getEndpointChangesForWording();
case Collections.Attributes:
case Collections.CreditsRole:
case Collections.GenericContents:
case Collections.MediaThumbnails:
case Collections.Scans:
case Collections.Tags:
case Collections.VideosChannels:
case Collections.VideosSubtitles:
default:
return [];
}
};
const getEndpointChangesFromIncomingRelation = ({
relationTo,
value,
}: NonNullable<Relationship["incomingRelations"]>[number]): EndpointChange[] => {
if (!isPayloadType(value)) return [];
switch (relationTo) {
case Collections.Folders:
return getEndpointChangesForFolder(value);
case Collections.Pages:
return getEndpointChangesForPage(value);
case Collections.Collectibles:
return getEndpointChangesForCollectible(value);
case Collections.Audios:
return getEndpointChangesForAudio(value);
case Collections.Images:
return getEndpointChangesForImage(value);
case Collections.Videos:
return getEndpointChangesForVideo(value);
case Collections.Files:
return getEndpointChangesForFile(value);
case Collections.Recorders:
return getEndpointChangesForRecorder(value);
case Collections.ChronologyEvents:
return getEndpointChangesForChronologyEvent(value);
case Collections.Languages:
case Collections.Currencies:
case Collections.Wordings:
case Collections.Attributes:
case Collections.CreditsRole:
case Collections.GenericContents:
case Collections.MediaThumbnails:
case Collections.Scans:
case Collections.Tags:
case Collections.VideosChannels:
case Collections.VideosSubtitles:
default:
return [];
}
};
const getEndpointChangesFromOutgoingRelation = ({
relationTo,
value,
}: NonNullable<Relationship["outgoingRelations"]>[number]): EndpointChange[] => {
if (!isPayloadType(value)) return [];
switch (relationTo) {
case Collections.Folders:
return getEndpointChangesForFolder(value);
case Collections.Pages:
return getEndpointChangesForPage(value);
case Collections.Collectibles:
return getEndpointChangesForCollectible(value);
case Collections.Audios:
return getEndpointChangesForAudio(value);
case Collections.Images:
return getEndpointChangesForImage(value);
case Collections.Videos:
return getEndpointChangesForVideo(value);
case Collections.Files:
return getEndpointChangesForFile(value);
case Collections.Languages:
case Collections.Currencies:
case Collections.Wordings:
case Collections.Attributes:
case Collections.CreditsRole:
case Collections.GenericContents:
case Collections.MediaThumbnails:
case Collections.Scans:
case Collections.Tags:
case Collections.VideosChannels:
case Collections.VideosSubtitles:
case Collections.ChronologyEvents:
case Collections.Recorders:
default:
return [];
}
};
export const getEndpointChangesForWebsiteConfig = (): EndpointChange[] => [
{
type: SDKEndpointNames.getWebsiteConfig,
url: getSDKEndpoint.getWebsiteConfig(),
},
];
export const getEndpointChangesForFolder = ({ slug }: Folder): EndpointChange[] => [
{ type: SDKEndpointNames.getFolder, slug, url: getSDKEndpoint.getFolder(slug) },
];
export const getEndpointChangesForLanguage = (): EndpointChange[] => [
{ type: SDKEndpointNames.getLanguages, url: getSDKEndpoint.getLanguages() },
];
export const getEndpointChangesForCurrency = (): EndpointChange[] => [
{ type: SDKEndpointNames.getCurrencies, url: getSDKEndpoint.getCurrencies() },
];
export const getEndpointChangesForWording = (): EndpointChange[] => [
{ type: SDKEndpointNames.getWordings, url: getSDKEndpoint.getWordings() },
];
export const getEndpointChangesForPage = ({ slug }: Page): EndpointChange[] => [
{ type: SDKEndpointNames.getPage, slug, url: getSDKEndpoint.getPage(slug) },
];
export const getEndpointChangesForCollectible = ({
slug,
gallery,
scans,
scansEnabled,
}: Collectible): EndpointChange[] => {
const changes: EndpointChange[] = [];
changes.push({
type: SDKEndpointNames.getCollectible,
slug,
url: getSDKEndpoint.getCollectible(slug),
});
if (gallery && gallery.length > 0) {
changes.push({
type: SDKEndpointNames.getCollectibleGallery,
slug,
url: getSDKEndpoint.getCollectibleGallery(slug),
});
gallery.forEach((_, indexNumber) => {
const index = indexNumber.toString();
changes.push({
type: SDKEndpointNames.getCollectibleGalleryImage,
slug,
index: index,
url: getSDKEndpoint.getCollectibleGalleryImage(slug, index),
});
});
}
if (scans && scansEnabled) {
changes.push({
type: SDKEndpointNames.getCollectibleScans,
slug,
url: getSDKEndpoint.getCollectibleScans(slug),
});
// TODO: Add other changes for cover, obi, dustjacket...
scans.pages?.forEach(({ page }) => {
const index = page.toString();
changes.push({
type: SDKEndpointNames.getCollectibleScanPage,
slug,
index: index,
url: getSDKEndpoint.getCollectibleScanPage(slug, index),
});
});
}
return changes;
};
export const getEndpointChangesForAudio = ({ id }: Audio): EndpointChange[] => [
{ type: SDKEndpointNames.getAudioByID, id, url: getSDKEndpoint.getAudioByID(id) },
];
export const getEndpointChangesForImage = ({ id }: Image): EndpointChange[] => [
{ type: SDKEndpointNames.getImageByID, id, url: getSDKEndpoint.getImageByID(id) },
];
export const getEndpointChangesForVideo = ({ id }: Video): EndpointChange[] => [
{ type: SDKEndpointNames.getVideoByID, id, url: getSDKEndpoint.getVideoByID(id) },
];
export const getEndpointChangesForFile = ({ id }: File): EndpointChange[] => [
{ type: SDKEndpointNames.getFileByID, id, url: getSDKEndpoint.getFileByID(id) },
];
export const getEndpointChangesForRecorder = ({ id }: Recorder): EndpointChange[] => [
{ type: SDKEndpointNames.getRecorderByID, id, url: getSDKEndpoint.getRecorderByID(id) },
];
export const getEndpointChangesForChronologyEvent = ({ id }: ChronologyEvent): EndpointChange[] => [
{
type: SDKEndpointNames.getChronologyEventByID,
id,
url: getSDKEndpoint.getChronologyEventByID(id),
},
{
type: SDKEndpointNames.getChronologyEvents,
url: getSDKEndpoint.getChronologyEvents(),
},
];
// -------------------------------------------------------------------------------------------------
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 (changes: EndpointChange[]) => {
if (changes.length === 0) return;
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(changes),
method: "POST",
});
})
);
} catch (e) {
if (e instanceof Error) {
console.warn("Error while sending webhook", e.message);
} else {
console.warn("Error while sending webhook", e);
}
}
};

View File

@ -1,80 +0,0 @@
import {
AfterDeleteHook,
AfterChangeHook as CollectionAfterChangeHook,
} from "payload/dist/collections/config/types";
import { AfterChangeHook as GlobalAfterChangeHook } from "payload/dist/globals/config/types";
import { AfterOperationWebHookMessage, Collections } from "../constants";
import { getSDKUrlsForDocument } from "../endpoints/getAllSDKUrlsEndpoint";
import { getAddedBackPropagationRelationships } from "../fields/backPropagationField/backPropagationUtils";
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 sendWebhookMessage = async (message: AfterOperationWebHookMessage) => {
await fetch(`${process.env.WEB_HOOK_URI}/collection-operation`, {
headers: {
Authorization: `Bearer ${process.env.WEB_HOOK_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(message),
method: "POST",
}).catch((e) => {
console.warn("Error while sending webhook", e);
});
};

View File

@ -1,5 +1,5 @@
import { BeforeDuplicate } from "payload/types";
import { CollectionStatus } from "../constants";
import { CollectionStatus } from "../shared/payload/constants";
export const beforeDuplicateUnpublish: BeforeDuplicate = ({ data }) => ({
...data,

View File

@ -27,10 +27,12 @@ import { WebsiteConfig } from "./collections/WebsiteConfig/WebsiteConfig";
import { Wordings } from "./collections/Wordings/Wordings";
import { Icon } from "./components/Icon";
import { Logo } from "./components/Logo";
import { Collections } from "./constants";
import { getAllIds } from "./endpoints/getAllIdsEndpoint";
import { getAllSDKUrlsEndpoint } from "./endpoints/getAllSDKUrlsEndpoint";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
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: {
@ -88,7 +90,7 @@ export default buildConfig({
typescript: {
outputFile: path.resolve(__dirname, "types/collections.ts"),
},
endpoints: [getAllSDKUrlsEndpoint, getAllIds],
endpoints: [getAllEndpoint],
graphQL: {
disable: true,
},
@ -96,6 +98,20 @@ export default buildConfig({
skip: () => true,
},
plugins: [
relationshipsPlugin({
// rebuildOnInit: true,
collectionConfig: {
admin: {
hidden: shownOnlyToAdmin,
},
access: {
update: mustBeAdmin,
create: mustBeAdmin,
delete: mustBeAdmin,
},
},
}),
cloudStorage({
collections: {
[Collections.Videos]: {

View File

@ -1,727 +0,0 @@
import {
AttributeTypes,
CollectibleBindingTypes,
CollectibleNature,
CollectiblePageOrders,
Collections,
RichTextContent,
} from "./constants";
import { Currency, Language } from "./types/collections";
// END MOCKING SECTION
export type EndpointFolderPreview = {
id: string;
slug: string;
icon?: string;
translations: {
language: string;
title: string;
}[];
};
export type EndpointFolder = Omit<EndpointFolderPreview, "translations"> & {
translations: (EndpointFolderPreview["translations"][number] & {
description?: RichTextContent;
})[];
sections:
| { type: "single"; subfolders: EndpointFolderPreview[] }
| {
type: "multiple";
sections: {
translations: { language: string; name: string }[];
subfolders: EndpointFolderPreview[];
}[];
};
files: (
| {
relationTo: Collections.Collectibles;
value: EndpointCollectiblePreview;
}
| {
relationTo: Collections.Pages;
value: EndpointPagePreview;
}
| {
relationTo: Collections.Images;
value: EndpointImagePreview;
}
| {
relationTo: Collections.Audios;
value: EndpointAudioPreview;
}
| {
relationTo: Collections.Videos;
value: EndpointVideoPreview;
}
| {
relationTo: Collections.Files;
value: EndpointFilePreview;
}
)[];
parentPages: EndpointSource[];
};
export type EndpointWebsiteConfig = {
home: {
backgroundImage?: EndpointPayloadImage;
folders: (EndpointFolderPreview & {
lightThumbnail?: EndpointPayloadImage;
darkThumbnail?: EndpointPayloadImage;
})[];
};
timeline: {
backgroundImage?: EndpointPayloadImage;
breaks: number[];
eventCount: number;
eras: {
startingYear: number;
endingYear: number;
name: string;
}[];
};
defaultOpenGraphImage?: EndpointPayloadImage;
};
export type EndpointRecorderPreview = {
id: string;
username: string;
};
export type EndpointRecorder = EndpointRecorderPreview & {
avatar?: EndpointPayloadImage;
translations: {
language: string;
biography: RichTextContent;
}[];
languages: string[];
};
export type EndpointWording = {
name: string;
translations: {
language: string;
name: string;
}[];
};
export type EndpointTag = {
id: string;
slug: string;
page?: { slug: string };
translations: {
language: string;
name: string;
}[];
};
export type EndpointGenericAttribute = {
id: string;
slug: string;
icon: string;
translations: {
language: string;
name: string;
}[];
};
export type EndpointNumberAttribute = EndpointGenericAttribute & {
type: AttributeTypes.Number;
value: number;
};
export type EndpointTextAttribute = EndpointGenericAttribute & {
type: AttributeTypes.Text;
value: string;
};
export type EndpointTagsAttribute = EndpointGenericAttribute & {
type: AttributeTypes.Tags;
value: EndpointTag[];
};
export type EndpointAttribute =
| EndpointNumberAttribute
| EndpointTextAttribute
| EndpointTagsAttribute;
export type EndpointRole = {
id: string;
icon: string;
translations: {
language: string;
name: string;
}[];
};
export type EndpointCredit = {
role: EndpointRole;
recorders: EndpointRecorderPreview[];
};
export type EndpointPagePreview = {
id: string;
slug: string;
thumbnail?: EndpointPayloadImage;
attributes: EndpointAttribute[];
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
}[];
updatedAt: string;
};
export type EndpointPage = Omit<EndpointPagePreview, "translations"> & {
backgroundImage?: EndpointPayloadImage;
translations: (EndpointPagePreview["translations"][number] & {
sourceLanguage: string;
summary?: RichTextContent;
content: RichTextContent;
credits: EndpointCredit[];
toc: TableOfContentEntry[];
})[];
createdAt: string;
updatedBy?: EndpointRecorderPreview;
parentPages: EndpointSource[];
};
export type EndpointCollectiblePreview = {
id: string;
slug: string;
thumbnail?: EndpointPayloadImage;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
}[];
attributes: EndpointAttribute[];
releaseDate?: string;
languages: string[];
price?: {
amount: number;
currency: string;
};
};
export type EndpointCollectible = Omit<EndpointCollectiblePreview, "translations"> & {
translations: (EndpointCollectiblePreview["translations"][number] & {
description?: RichTextContent;
})[];
backgroundImage?: EndpointPayloadImage;
nature: CollectibleNature;
gallery?: { count: number; thumbnail: EndpointPayloadImage };
scans?: { count: number; thumbnail: EndpointPayloadImage };
urls: { url: string; label: string }[];
size?: {
width: number;
height: number;
thickness?: number;
};
weight?: number;
pageInfo?: {
pageCount: number;
bindingType?: CollectibleBindingTypes;
pageOrder?: CollectiblePageOrders;
};
subitems: EndpointCollectiblePreview[];
files: EndpointFilePreview[];
contents: {
content:
| {
relationTo: Collections.Pages;
value: EndpointPagePreview;
}
| {
relationTo: Collections.Audios;
value: EndpointAudioPreview;
}
| {
relationTo: Collections.Videos;
value: EndpointVideoPreview;
}
| {
relationTo: Collections.GenericContents;
value: {
translations: {
language: string;
name: string;
}[];
};
};
range?:
| {
type: "pageRange";
start: number;
end: number;
}
| {
type: "timeRange";
start: string;
end: string;
}
| {
type: "other";
translations: {
language: string;
note: RichTextContent;
}[];
};
}[];
createdAt: string;
updatedAt: string;
updatedBy?: EndpointRecorderPreview;
parentPages: EndpointSource[];
};
export type EndpointCollectibleScans = {
slug: string;
thumbnail?: EndpointPayloadImage;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
description?: RichTextContent;
}[];
credits: EndpointCredit[];
cover?: {
front?: EndpointScanImage;
spine?: EndpointScanImage;
back?: EndpointScanImage;
insideFront?: EndpointScanImage;
insideBack?: EndpointScanImage;
flapFront?: EndpointScanImage;
flapBack?: EndpointScanImage;
insideFlapFront?: EndpointScanImage;
insideFlapBack?: EndpointScanImage;
};
dustjacket?: {
front?: EndpointScanImage;
spine?: EndpointScanImage;
back?: EndpointScanImage;
insideFront?: EndpointScanImage;
insideSpine?: EndpointScanImage;
insideBack?: EndpointScanImage;
flapFront?: EndpointScanImage;
flapBack?: EndpointScanImage;
insideFlapFront?: EndpointScanImage;
insideFlapBack?: EndpointScanImage;
};
obi?: {
front?: EndpointScanImage;
spine?: EndpointScanImage;
back?: EndpointScanImage;
insideFront?: EndpointScanImage;
insideSpine?: EndpointScanImage;
insideBack?: EndpointScanImage;
flapFront?: EndpointScanImage;
flapBack?: EndpointScanImage;
insideFlapFront?: EndpointScanImage;
insideFlapBack?: EndpointScanImage;
};
pages: EndpointScanImage[];
parentPages: EndpointSource[];
};
export type EndpointCollectibleGallery = {
slug: string;
thumbnail?: EndpointPayloadImage;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
description?: RichTextContent;
}[];
images: EndpointPayloadImage[];
parentPages: EndpointSource[];
};
export type EndpointCollectibleGalleryImage = {
slug: string;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
description?: RichTextContent;
}[];
image: EndpointImage;
previousIndex?: string;
nextIndex?: string;
parentPages: EndpointSource[];
};
export type EndpointCollectibleScanPage = {
slug: string;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
description?: RichTextContent;
}[];
image: EndpointScanImage;
previousIndex?: string;
nextIndex?: string;
parentPages: EndpointSource[];
};
export type EndpointScanImage = PayloadImage & {
index: string;
sizes: PayloadImage[];
};
export type TableOfContentEntry = {
prefix: string;
title: string;
type: "sceneBreak" | "break" | "section";
index: number;
children: TableOfContentEntry[];
};
export type EndpointChronologyEvent = {
id: string;
date: {
year: number;
month?: number;
day?: number;
};
events: {
sources: EndpointSource[];
translations: {
language: string;
sourceLanguage: string;
title?: string;
description?: RichTextContent;
notes?: RichTextContent;
credits: EndpointCredit[];
}[];
}[];
};
export type EndpointSourcePreview = {
id: string;
slug: string;
translations: { language: string; pretitle?: string; title: string; subtitle?: string }[];
};
export type EndpointSource =
| { type: "url"; url: string; label: string }
| {
type: "collectible";
collectible: EndpointSourcePreview;
range?:
| { type: "page"; page: number }
| { type: "timestamp"; timestamp: string }
| { type: "custom"; translations: { language: string; note: string }[] };
}
| { type: "page"; page: EndpointSourcePreview }
| { type: "folder"; folder: EndpointSourcePreview }
| { type: "scans"; collectible: EndpointSourcePreview }
| { type: "gallery"; collectible: EndpointSourcePreview };
export type EndpointMediaPreview = {
id: string;
url: string;
filename: string;
mimeType: string;
attributes: EndpointAttribute[];
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
}[];
};
export type EndpointMedia = Omit<EndpointMediaPreview, "translations"> & {
filesize: number;
updatedAt: string;
createdAt: string;
translations: (EndpointMediaPreview["translations"][number] & {
description?: RichTextContent;
})[];
credits: EndpointCredit[];
};
export type EndpointImagePreview = EndpointMediaPreview & {
width: number;
height: number;
sizes: PayloadImage[];
openGraph?: PayloadImage;
};
export type EndpointImage = EndpointMedia & {
width: number;
height: number;
sizes: PayloadImage[];
openGraph?: PayloadImage;
};
export type EndpointAudioPreview = EndpointMediaPreview & {
thumbnail?: EndpointPayloadImage;
duration: number;
};
export type EndpointAudio = EndpointMedia & {
thumbnail?: EndpointPayloadImage;
duration: number;
};
export type EndpointVideoPreview = EndpointMediaPreview & {
thumbnail?: EndpointPayloadImage;
subtitles: {
language: string;
url: string;
}[];
duration: number;
};
export type EndpointVideo = EndpointMedia & {
thumbnail?: EndpointPayloadImage;
subtitles: {
language: string;
url: string;
}[];
platform?: {
channel: {
url: string;
title: string;
subscribers: number;
};
views?: number;
likes?: number;
dislikes?: number;
url: string;
publishedDate: string;
};
duration: number;
};
export type EndpointFilePreview = EndpointMediaPreview & {
filesize: number;
thumbnail?: EndpointPayloadImage;
};
export type EndpointFile = EndpointMedia & {
filesize: number;
thumbnail?: EndpointPayloadImage;
};
export type EndpointPayloadImage = PayloadImage & {
sizes: PayloadImage[];
openGraph?: PayloadImage;
};
export type PayloadMedia = {
id: string;
url: string;
mimeType: string;
filename: string;
filesize: number;
};
export type PayloadImage = PayloadMedia & {
width: number;
height: number;
};
export type EndpointAllSDKUrls = {
urls: string[];
};
export type EndpointAllIds = {
collectibles: { slugs: string[] };
pages: { slugs: string[] };
folders: { slugs: string[] };
videos: { ids: string[] };
audios: { ids: string[] };
images: { ids: string[] };
files: { ids: string[] };
recorders: { ids: string[] };
chronologyEvents: { ids: string[] };
};
// SDK
export const getSDKEndpoint = {
getConfigEndpoint: () => `/globals/${Collections.WebsiteConfig}/config`,
getFolderEndpoint: (slug: string) => `/${Collections.Folders}/slug/${slug}`,
getLanguagesEndpoint: () => `/${Collections.Languages}/all`,
getCurrenciesEndpoint: () => `/${Collections.Currencies}/all`,
getWordingsEndpoint: () => `/${Collections.Wordings}/all`,
getPageEndpoint: (slug: string) => `/${Collections.Pages}/slug/${slug}`,
getCollectibleEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}`,
getCollectibleScansEndpoint: (slug: string) => `/${Collections.Collectibles}/slug/${slug}/scans`,
getCollectibleScanPageEndpoint: (slug: string, index: string) =>
`/${Collections.Collectibles}/slug/${slug}/scans/${index}`,
getCollectibleGalleryEndpoint: (slug: string) =>
`/${Collections.Collectibles}/slug/${slug}/gallery`,
getCollectibleGalleryImageEndpoint: (slug: string, index: string) =>
`/${Collections.Collectibles}/slug/${slug}/gallery/${index}`,
getChronologyEventsEndpoint: () => `/${Collections.ChronologyEvents}/all`,
getChronologyEventByIDEndpoint: (id: string) => `/${Collections.ChronologyEvents}/id/${id}`,
getImageByIDEndpoint: (id: string) => `/${Collections.Images}/id/${id}`,
getAudioByIDEndpoint: (id: string) => `/${Collections.Audios}/id/${id}`,
getVideoByIDEndpoint: (id: string) => `/${Collections.Videos}/id/${id}`,
getFileByIDEndpoint: (id: string) => `/${Collections.Files}/id/${id}`,
getRecorderByIDEndpoint: (id: string) => `/${Collections.Recorders}/id/${id}`,
getAllSDKUrlsEndpoint: () => `/all-sdk-urls`,
getAllIds: () => `/all-ids`,
getLoginEndpoint: () => `/${Collections.Recorders}/login`,
};
type PayloadSDKResponse<T> = {
data: T;
endpointCalled: string;
};
type PayloadTokenCache = {
set: (token: string, expirationTimestamp: number) => void;
get: () => string | undefined;
};
type PayloadDataCache = {
set: (url: string, response: any) => void;
get: (url: string) => any | undefined;
};
export class PayloadSDK {
private tokenCache: PayloadTokenCache | undefined;
private dataCache: PayloadDataCache | undefined;
constructor(
private readonly apiURL: string,
private readonly email: string,
private readonly password: string
) {}
addTokenCache(tokenCache: PayloadTokenCache) {
this.tokenCache = tokenCache;
}
addDataCache(dataCache: PayloadDataCache) {
this.dataCache = dataCache;
}
private logResponse(res: Response) {
console.log(res.status, res.statusText, res.url);
}
private async refreshToken() {
const loginUrl = `${this.apiURL}${getSDKEndpoint.getLoginEndpoint()}`;
const loginResult = await fetch(loginUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: this.email, password: this.password }),
});
this.logResponse(loginResult);
if (loginResult.status !== 200) {
throw new Error("Unable to login");
}
const { token, exp } = (await loginResult.json()) as {
token: string;
exp: number;
};
this.tokenCache?.set(token, exp);
return token;
}
async request<T>(endpoint: string): Promise<PayloadSDKResponse<T>> {
const cachedResponse = this.dataCache?.get(endpoint);
if (cachedResponse) {
return cachedResponse;
}
const result = await fetch(`${this.apiURL}${endpoint}`, {
headers: {
Authorization: `JWT ${this.tokenCache?.get() ?? (await this.refreshToken())}`,
},
});
this.logResponse(result);
if (!result.ok) {
throw new Error("Unhandled fetch error");
}
const response = { data: await result.json(), endpointCalled: endpoint };
this.dataCache?.set(endpoint, response);
return response;
}
async getConfig(): Promise<PayloadSDKResponse<EndpointWebsiteConfig>> {
return await this.request(getSDKEndpoint.getConfigEndpoint());
}
async getFolder(slug: string): Promise<PayloadSDKResponse<EndpointFolder>> {
return await this.request(getSDKEndpoint.getFolderEndpoint(slug));
}
async getLanguages(): Promise<PayloadSDKResponse<Language[]>> {
return await this.request(getSDKEndpoint.getLanguagesEndpoint());
}
async getCurrencies(): Promise<PayloadSDKResponse<Currency[]>> {
return await this.request(getSDKEndpoint.getCurrenciesEndpoint());
}
async getWordings(): Promise<PayloadSDKResponse<EndpointWording[]>> {
return await this.request(getSDKEndpoint.getWordingsEndpoint());
}
async getPage(slug: string): Promise<PayloadSDKResponse<EndpointPage>> {
return await this.request(getSDKEndpoint.getPageEndpoint(slug));
}
async getCollectible(slug: string): Promise<PayloadSDKResponse<EndpointCollectible>> {
return await this.request(getSDKEndpoint.getCollectibleEndpoint(slug));
}
async getCollectibleScans(slug: string): Promise<PayloadSDKResponse<EndpointCollectibleScans>> {
return await this.request(getSDKEndpoint.getCollectibleScansEndpoint(slug));
}
async getCollectibleScanPage(
slug: string,
index: string
): Promise<PayloadSDKResponse<EndpointCollectibleScanPage>> {
return await this.request(getSDKEndpoint.getCollectibleScanPageEndpoint(slug, index));
}
async getCollectibleGallery(
slug: string
): Promise<PayloadSDKResponse<EndpointCollectibleGallery>> {
return await this.request(getSDKEndpoint.getCollectibleGalleryEndpoint(slug));
}
async getCollectibleGalleryImage(
slug: string,
index: string
): Promise<PayloadSDKResponse<EndpointCollectibleGalleryImage>> {
return await this.request(getSDKEndpoint.getCollectibleGalleryImageEndpoint(slug, index));
}
async getChronologyEvents(): Promise<PayloadSDKResponse<EndpointChronologyEvent[]>> {
return await this.request(getSDKEndpoint.getChronologyEventsEndpoint());
}
async getChronologyEventByID(id: string): Promise<PayloadSDKResponse<EndpointChronologyEvent>> {
return await this.request(getSDKEndpoint.getChronologyEventByIDEndpoint(id));
}
async getImageByID(id: string): Promise<PayloadSDKResponse<EndpointImage>> {
return await this.request(getSDKEndpoint.getImageByIDEndpoint(id));
}
async getAudioByID(id: string): Promise<PayloadSDKResponse<EndpointAudio>> {
return await this.request(getSDKEndpoint.getAudioByIDEndpoint(id));
}
async getVideoByID(id: string): Promise<PayloadSDKResponse<EndpointVideo>> {
return await this.request(getSDKEndpoint.getVideoByIDEndpoint(id));
}
async getFileByID(id: string): Promise<PayloadSDKResponse<EndpointFile>> {
return await this.request(getSDKEndpoint.getFileByIDEndpoint(id));
}
async getRecorderByID(id: string): Promise<PayloadSDKResponse<EndpointRecorder>> {
return await this.request(getSDKEndpoint.getRecorderByIDEndpoint(id));
}
async getAllSdkUrls(): Promise<PayloadSDKResponse<EndpointAllSDKUrls>> {
return await this.request(getSDKEndpoint.getAllSDKUrlsEndpoint());
}
async getAllIds(): Promise<PayloadSDKResponse<EndpointAllIds>> {
return await this.request(getSDKEndpoint.getAllIds());
}
}

View File

@ -1,10 +1,9 @@
import "dotenv/config";
import express from "express";
import { readFileSync } from "fs";
import path from "path";
import payload from "payload";
import { Collections, RecordersRoles } from "./constants";
import { isDefined, isUndefined } from "./utils/asserts";
import { isUndefined } from "./utils/asserts";
import { Collections, RecordersRoles } from "./shared/payload/constants";
const app = express();
@ -29,62 +28,36 @@ 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();
},
});
// Add your own express routes here
app.use("/public", express.static(path.join(__dirname, "../public")));
app.get("/api/sdk", (_, res) => {
const removeMockingSection = (text: string): string => {
const lines = text.split("\n");
const endMockingLine = lines.findIndex((line) => line === "// END MOCKING SECTION") ?? 0;
return lines.slice(endMockingLine + 1).join("\n");
};
const removeDeclare = (text: string): string => {
const lines = text.split("\n");
const startDeclareLine = lines.findIndex((line) => line.startsWith("declare module")) ?? 0;
return lines.slice(0, startDeclareLine).join("\n");
};
const result = [];
result.push(removeDeclare(readFileSync(path.join(__dirname, "types/collections.ts"), "utf-8")));
result.push("/////////////// CONSTANTS ///////////////");
result.push(removeMockingSection(readFileSync(path.join(__dirname, "constants.ts"), "utf-8")));
result.push("////////////////// SDK //////////////////");
result.push(removeMockingSection(readFileSync(path.join(__dirname, "sdk.ts"), "utf-8")));
res.type("text/plain");
res.send(result.join("\n\n"));
});
app.get("/robots.txt", (_, res) => {
res.type("text/plain");
res.send("User-agent: *\nDisallow: /");

1
src/shared Submodule

@ -0,0 +1 @@
Subproject commit c2702ea508dde59264ee66100054bacc0834c029

View File

@ -40,6 +40,7 @@ export interface Config {
currencies: Currency;
wordings: Wording;
"generic-contents": GenericContent;
relationships: Relationship;
"payload-preferences": PayloadPreference;
"payload-migrations": PayloadMigration;
};
@ -94,10 +95,9 @@ export interface Page {
[k: string]: unknown;
};
credits?: Credits;
sourceUrls?: string[] | null;
id?: string | null;
}[];
folders?: (string | Folder)[] | null;
collectibles?: (string | Collectible)[] | null;
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
@ -352,79 +352,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 +535,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 +876,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 +1078,262 @@ 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;
}
)[]
| null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".

9
src/utils/array.ts Normal file
View File

@ -0,0 +1,9 @@
export const uniqueBy = <T, K extends string | number>(array: T[], getKey: (item: T) => K) => {
const alreadyFoundKeys: K[] = [];
return array.filter((item) => {
var currentItemKey = getKey(item);
if (alreadyFoundKeys.includes(currentItemKey)) return false;
alreadyFoundKeys.push(currentItemKey);
return true;
});
};

View File

@ -1,5 +1,5 @@
import { RichTextContent, isNodeParagraphNode } from "../constants";
import { PayloadImage, PayloadMedia } from "../sdk";
import { PayloadImage, PayloadMedia } from "../shared/payload/endpoint-types";
import { RichTextContent, isNodeParagraphNode } from "../shared/payload/rich-text";
import { Audio, File, Image, MediaThumbnail, Scan, Video } from "../types/collections";
export const isDefined = <T>(value: T | null | undefined): value is T =>

View File

@ -1,20 +1,21 @@
import { GeneratedTypes } from "payload";
import { CollectionConfig } from "payload/types";
import { afterDeleteWebhook, collectionAfterChangeWebhook } from "../hooks/afterOperationWebhook";
import { formatToPascalCase } from "./string";
import {
afterChangeSendChangesWebhook,
afterDeleteSendChangesWebhook,
beforeChangePrepareChanges,
beforeDeletePrepareChanges,
} from "../hooks/afterOperationSendChangesWebhook";
type CollectionConfigWithPlugins = CollectionConfig;
export type BuildCollectionConfig = Omit<
CollectionConfigWithPlugins,
"slug" | "typescript" | "labels" | "custom"
"slug" | "typescript" | "labels"
> & {
slug: keyof GeneratedTypes["collections"];
labels: { singular: string; plural: string };
custom?: {
getBackPropagatedRelationships?: (object: any) => string[];
[key: string]: unknown;
};
};
export const buildCollectionConfig = (config: BuildCollectionConfig): CollectionConfig => ({
@ -22,7 +23,9 @@ export const buildCollectionConfig = (config: BuildCollectionConfig): Collection
typescript: { interface: formatToPascalCase(config.labels.singular) },
hooks: {
...config.hooks,
afterChange: [...(config.hooks?.afterChange ?? []), collectionAfterChangeWebhook],
afterDelete: [...(config.hooks?.afterDelete ?? []), afterDeleteWebhook],
beforeChange: [...(config.hooks?.beforeChange ?? []), beforeChangePrepareChanges],
afterChange: [...(config.hooks?.afterChange ?? []), afterChangeSendChangesWebhook],
beforeDelete: [...(config.hooks?.beforeDelete ?? []), beforeDeletePrepareChanges],
afterDelete: [...(config.hooks?.afterDelete ?? []), afterDeleteSendChangesWebhook],
},
});

View File

@ -1,42 +1,45 @@
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, Collections } from "../shared/payload/constants";
import {
AttributeTypes,
RichTextBreakBlock,
RichTextContent,
RichTextSectionBlock,
RichTextUploadNode,
isBlockNodeBreakBlock,
isBlockNodeSectionBlock,
isNodeBlockNode,
isNodeUploadNode,
isUploadNodeAudioNode,
isUploadNodeImageNode,
isUploadNodeVideoNode,
} from "../constants";
import {
EndpointAttribute,
EndpointCredit,
EndpointPayloadImage,
EndpointRole,
EndpointScanImage,
EndpointSource,
EndpointSourcePreview,
EndpointTag,
EndpointRole,
EndpointCredit,
EndpointAttribute,
PayloadImage,
} from "../sdk";
EndpointScanImage,
EndpointPayloadImage,
EndpointRelation,
} from "../shared/payload/endpoint-types";
import {
RichTextContent,
isNodeBlockNode,
isBlockNodeSectionBlock,
RichTextSectionBlock,
isBlockNodeBreakBlock,
RichTextBreakBlock,
isNodeUploadNode,
RichTextUploadNode,
isUploadNodeImageNode,
isUploadNodeAudioNode,
isUploadNodeVideoNode,
} 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<EndpointRelation>(({ 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);

View File

@ -1,8 +1,8 @@
import { ImageSize } from "payload/dist/uploads/types";
import { CollectionConfig } from "payload/types";
import { publicAccess } from "../accesses/publicAccess";
import { CollectionGroups } from "../constants";
import { BuildCollectionConfig, buildCollectionConfig } from "./collectionConfig";
import { CollectionGroups } from "../shared/payload/constants";
const fields = {
filename: "filename",

View File

@ -1,12 +1,12 @@
import tags from "language-tags";
import { RichTextContent } from "../constants";
import { isUndefined } from "./asserts";
import { RichTextContent } from "../shared/payload/rich-text";
export const shortenEllipsis = (text: string, length: number): string =>
text.length - 3 > length ? `${text.substring(0, length)}...` : text;
export const formatLanguageCode = (code: string): string =>
tags(code).valid() ? tags(code).language()?.descriptions()[0] ?? code : code;
tags(code).valid() ? (tags(code).language()?.descriptions()[0] ?? code) : code;
export const capitalize = (string: string): string => {
const [firstLetter, ...otherLetters] = string;

View File

@ -1,6 +1,6 @@
import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types";
import { Collections } from "../constants";
import { BuildCollectionConfig, buildCollectionConfig } from "./collectionConfig";
import { Collections } from "../shared/payload/constants";
const fields = { updatedBy: "updatedBy" };