Removed preview types and added media to RTC, collectibles and folders

This commit is contained in:
DrMint 2024-04-07 02:23:07 +02:00
parent b15fe7a0ce
commit 40e54ea2a0
20 changed files with 764 additions and 318 deletions

View File

@ -5,6 +5,7 @@ import { tagsField } from "../../fields/tagsField/tagsField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";
const fields = {
filename: "filename",
@ -38,6 +39,7 @@ export const Audios = buildCollectionConfig({
mimeTypes: ["audio/*"],
disableLocalStorage: true,
},
endpoints: [getByID],
fields: [
rowField([
{ name: fields.duration, type: "number", min: 0, required: true },

View File

@ -0,0 +1,77 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointAudio, PayloadMedia } from "../../../sdk";
import { Audio } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isNotEmpty, isValidPayloadImage, isValidPayloadMedia } from "../../../utils/asserts";
import {
convertRTCToEndpointRTC,
convertTagsEndpointTagsGroups,
getLanguageId,
} from "../../../utils/endpoints";
export const getByID: CollectionEndpoint = {
method: "get",
path: "/id/:id",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
if (!req.params.id) {
return res.status(400).send({ errors: [{ message: "Missing 'id' query params" }] });
}
try {
const result = await payload.findByID({
collection: Collections.Audios,
id: req.params.id,
});
if (!isValidPayloadMedia(result)) {
return res.sendStatus(404);
}
return res.status(200).json(convertAudioToEndpointAudio(result));
} catch {
return res.sendStatus(404);
}
},
};
export const convertAudioToEndpointAudio = ({
url,
tags,
translations,
mimeType,
createdAt,
updatedAt,
filename,
filesize,
duration,
id,
thumbnail,
}: Audio & PayloadMedia): EndpointAudio => ({
url,
tagGroups: convertTagsEndpointTagsGroups(tags),
createdAt,
filename,
filesize,
id,
mimeType,
updatedAt,
translations:
translations?.map(({ language, title, description }) => ({
language: getLanguageId(language),
title,
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
duration,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
});

View File

@ -4,9 +4,10 @@ import { EndpointChronologyEvent, EndpointSource } from "../../../sdk";
import { ChronologyEvent, CollectibleBlock } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isNotEmpty, isPayloadArrayType, isPayloadType } from "../../../utils/asserts";
import { getDomainFromUrl, handleRecorder } from "../../../utils/endpoints";
import { convertCollectibleToPreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { getDomainFromUrl } from "../../../utils/endpoints";
import { convertCollectibleToEndpointCollectible } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToEndpointPage } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertRecorderToEndpointRecorder } from "../../Recorders/endpoints/getByUsername";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",
@ -82,9 +83,15 @@ export const eventToEndpointEvent = ({
...(isNotEmpty(title) ? { title } : {}),
...(isNotEmpty(description) ? { description } : {}),
...(isNotEmpty(notes) ? { notes } : {}),
proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(handleRecorder) : [],
transcribers: isPayloadArrayType(transcribers) ? transcribers.map(handleRecorder) : [],
translators: isPayloadArrayType(translators) ? translators.map(handleRecorder) : [],
proofreaders: isPayloadArrayType(proofreaders)
? proofreaders.map(convertRecorderToEndpointRecorder)
: [],
transcribers: isPayloadArrayType(transcribers)
? transcribers.map(convertRecorderToEndpointRecorder)
: [],
translators: isPayloadArrayType(translators)
? translators.map(convertRecorderToEndpointRecorder)
: [],
})
),
sources: handleSources(sources),
@ -100,7 +107,7 @@ const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): E
if (!isPayloadType(source.collectible)) return [];
return {
type: "collectible",
collectible: convertCollectibleToPreview(source.collectible),
collectible: convertCollectibleToEndpointCollectible(source.collectible),
...(isDefined(range) ? { range } : {}),
};
@ -108,7 +115,7 @@ const handleSources = (sources: ChronologyEvent["events"][number]["sources"]): E
if (!isPayloadType(source.page)) return [];
return {
type: "page",
page: convertPageToPreview(source.page),
page: convertPageToEndpointPage(source.page),
};
case "urlBlock":

View File

@ -1,6 +1,6 @@
import { CollectibleNature, Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointCollectible, EndpointCollectiblePreview, PayloadImage } from "../../../sdk";
import { EndpointCollectible, PayloadImage } from "../../../sdk";
import { Collectible } from "../../../types/collections";
import {
isDefined,
@ -9,52 +9,76 @@ import {
isPayloadType,
isPublished,
isValidPayloadImage,
isValidPayloadMedia,
} from "../../../utils/asserts";
import { convertTagsToGroups, getDomainFromUrl, handleParentPages } from "../../../utils/endpoints";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
import {
convertSourceToEndpointSource,
convertTagsEndpointTagsGroups,
getDomainFromUrl,
} from "../../../utils/endpoints";
import { convertAudioToEndpointAudio } from "../../Audios/endpoints/getByID";
import { convertPageToEndpointPage } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertVideoToEndpointVideo } from "../../Videos/endpoints/getByID";
export const getBySlugEndpoint = createGetByEndpoint({
collection: Collections.Collectibles,
attribute: "slug",
depth: 3,
handler: (collectible: Collectible): EndpointCollectible => {
const {
nature,
urls,
subitems,
gallery,
contents,
priceEnabled,
price,
size,
sizeEnabled,
weight,
weightEnabled,
pageInfo,
pageInfoEnabled,
parentItems,
folders,
backgroundImage,
} = collectible;
handler: (collectible) => convertCollectibleToEndpointCollectible(collectible),
});
return {
...convertCollectibleToPreview(collectible),
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
contents: handleContents(contents),
gallery: handleGallery(gallery),
scans: handleScans(collectible.scans),
nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital,
parentPages: handleParentPages({ collectibles: parentItems, folders }),
subitems: isPayloadArrayType(subitems)
? subitems.filter(isPublished).map(convertCollectibleToPreview)
: [],
urls: urls?.map(({ url }) => ({ url, label: getDomainFromUrl(url) })) ?? [],
...(weightEnabled && isDefined(weight) ? { weight: weight.amount } : {}),
...handleSize(size, sizeEnabled),
...handlePageInfo(pageInfo, pageInfoEnabled),
...handlePrice(price, priceEnabled),
};
},
export const convertCollectibleToEndpointCollectible = ({
nature,
urls,
subitems,
gallery,
contents,
priceEnabled,
price,
size,
sizeEnabled,
weight,
weightEnabled,
pageInfo,
pageInfoEnabled,
parentItems,
folders,
backgroundImage,
slug,
thumbnail,
translations,
releaseDate,
languages,
scans,
tags,
}: Collectible): EndpointCollectible => ({
slug,
languages: languages?.map((language) => (isPayloadType(language) ? language.id : language)) ?? [],
...(isDefined(releaseDate) ? { releaseDate } : {}),
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsEndpointTagsGroups(tags),
translations:
translations?.map(({ language, title, description, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language,
title,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
...(isNotEmpty(subtitle) ? { subtitle } : {}),
...(isNotEmpty(description) ? { description } : {}),
})) ?? [],
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
contents: handleContents(contents),
gallery: handleGallery(gallery),
scans: handleScans(scans),
nature: nature === "Physical" ? CollectibleNature.Physical : CollectibleNature.Digital,
parentPages: convertSourceToEndpointSource({ collectibles: parentItems, folders }),
subitems: isPayloadArrayType(subitems)
? subitems.filter(isPublished).map(convertCollectibleToEndpointCollectible)
: [],
urls: urls?.map(({ url }) => ({ url, label: getDomainFromUrl(url) })) ?? [],
...(weightEnabled && isDefined(weight) ? { weight: weight.amount } : {}),
...handleSize(size, sizeEnabled),
...handlePageInfo(pageInfo, pageInfoEnabled),
...handlePrice(price, priceEnabled),
});
const handlePrice = (
@ -173,10 +197,10 @@ const handleContents = (contents: Collectible["contents"]): EndpointCollectible[
const handleContent = (): EndpointCollectible["contents"][number]["content"] | undefined => {
switch (content.relationTo) {
case "generic-contents":
case Collections.GenericContents:
return isPayloadType(content.value)
? {
relationTo: "generic-contents",
relationTo: Collections.GenericContents,
value: {
translations: content.value.translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
@ -186,9 +210,19 @@ const handleContents = (contents: Collectible["contents"]): EndpointCollectible[
}
: undefined;
case "pages":
case Collections.Pages:
return isPayloadType(content.value) && isPublished(content.value)
? { relationTo: "pages", value: convertPageToPreview(content.value) }
? { relationTo: Collections.Pages, value: convertPageToEndpointPage(content.value) }
: undefined;
case Collections.Audios:
return isPayloadType(content.value) && isValidPayloadMedia(content.value)
? { relationTo: Collections.Audios, value: convertAudioToEndpointAudio(content.value) }
: undefined;
case Collections.Videos:
return isPayloadType(content.value) && isValidPayloadMedia(content.value)
? { relationTo: Collections.Videos, value: convertVideoToEndpointVideo(content.value) }
: undefined;
default:
@ -203,29 +237,3 @@ const handleContents = (contents: Collectible["contents"]): EndpointCollectible[
return [{ content: newContent, range }];
});
};
export const convertCollectibleToPreview = ({
slug,
thumbnail,
translations,
releaseDate,
languages,
tags,
}: Collectible): EndpointCollectiblePreview => {
return {
slug,
languages:
languages?.map((language) => (isPayloadType(language) ? language.id : language)) ?? [],
...(isDefined(releaseDate) ? { releaseDate } : {}),
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsToGroups(tags),
translations:
translations?.map(({ language, title, description, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language,
title,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
...(isNotEmpty(subtitle) ? { subtitle } : {}),
...(isNotEmpty(description) ? { description } : {}),
})) ?? [],
};
};

View File

@ -1,79 +1,95 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointFolder, EndpointFolderPreview } from "../../../sdk";
import { EndpointFolder } from "../../../sdk";
import { Folder, Language } from "../../../types/collections";
import { isDefined, isNotEmpty, isPayloadType, isPublished } from "../../../utils/asserts";
import { handleParentPages } from "../../../utils/endpoints";
import { convertCollectibleToPreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertPageToPreview } from "../../Pages/endpoints/getBySlugEndpoint";
import {
isDefined,
isNotEmpty,
isPayloadType,
isPublished,
isValidPayloadImage,
isValidPayloadMedia,
} from "../../../utils/asserts";
import { convertSourceToEndpointSource, getLanguageId } from "../../../utils/endpoints";
import { convertAudioToEndpointAudio } from "../../Audios/endpoints/getByID";
import { convertCollectibleToEndpointCollectible } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertImageToEndpointImage } from "../../Images/endpoints/getByID";
import { convertPageToEndpointPage } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertVideoToEndpointVideo } from "../../Videos/endpoints/getByID";
export const getBySlugEndpoint = createGetByEndpoint({
collection: Collections.Folders,
attribute: "slug",
depth: 3,
handler: (folder: Folder): EndpointFolder => {
const { sections, files, parentFolders } = folder;
return {
...convertFolderToPreview(folder),
sections:
sections?.length === 1
? {
type: "single",
subfolders:
sections[0]?.subfolders?.filter(isPayloadType).map(convertFolderToPreview) ?? [],
}
: {
type: "multiple",
sections:
sections?.filter(isValidSection).map(({ translations, subfolders }) => ({
translations: translations.map(({ language, name }) => ({
language: getLanguageId(language),
name,
})),
subfolders: subfolders.map(convertFolderToPreview),
})) ?? [],
},
files:
files?.flatMap<EndpointFolder["files"][number]>(({ relationTo, value }) => {
if (!isPayloadType(value) || ("_status" in value && !isPublished(value))) {
return [];
}
switch (relationTo) {
case "collectibles":
return [{ relationTo, value: convertCollectibleToPreview(value) }];
case "pages":
return [{ relationTo, value: convertPageToPreview(value) }];
// TODO: handle media type files
case "images":
return [];
case "audios":
return [];
case "videos":
return [];
}
}) ?? [],
parentPages: handleParentPages({ folders: parentFolders }),
};
},
handler: (folder) => convertFolderToEndpointFolder(folder),
});
export const convertFolderToPreview = ({
export const convertFolderToEndpointFolder = ({
slug,
translations,
icon,
}: Folder): EndpointFolderPreview => {
return {
slug,
...(isDefined(icon) ? { icon } : {}),
translations:
translations?.map(({ language, name, description }) => ({
language: getLanguageId(language),
name,
...(isNotEmpty(description) ? { description } : {}),
})) ?? [],
};
};
translations,
sections,
files,
parentFolders,
}: Folder): EndpointFolder => ({
slug,
...(isDefined(icon) ? { icon } : {}),
translations:
translations?.map(({ language, name, description }) => ({
language: getLanguageId(language),
name,
...(isNotEmpty(description) ? { description } : {}),
})) ?? [],
sections:
sections?.length === 1
? {
type: "single",
subfolders:
sections[0]?.subfolders?.filter(isPayloadType).map(convertFolderToEndpointFolder) ?? [],
}
: {
type: "multiple",
sections:
sections?.filter(isValidSection).map(({ translations, subfolders }) => ({
translations: translations.map(({ language, name }) => ({
language: getLanguageId(language),
name,
})),
subfolders: subfolders.map(convertFolderToEndpointFolder),
})) ?? [],
},
files:
files?.flatMap<EndpointFolder["files"][number]>(({ relationTo, value }) => {
if (!isPayloadType(value) || ("_status" in value && !isPublished(value))) {
return [];
}
switch (relationTo) {
case Collections.Collectibles:
return [
{
relationTo: Collections.Collectibles,
value: convertCollectibleToEndpointCollectible(value),
},
];
case Collections.Pages:
return [{ relationTo: Collections.Pages, value: convertPageToEndpointPage(value) }];
// TODO: handle media type files
case Collections.Images:
if (!isValidPayloadImage(value)) return [];
return [{ relationTo: Collections.Images, value: convertImageToEndpointImage(value) }];
case Collections.Audios:
if (!isValidPayloadMedia(value)) return [];
return [{ relationTo: Collections.Audios, value: convertAudioToEndpointAudio(value) }];
case Collections.Videos:
if (!isValidPayloadMedia(value)) return [];
return [{ relationTo: Collections.Videos, value: convertVideoToEndpointVideo(value) }];
default:
return [];
}
}) ?? [],
parentPages: convertSourceToEndpointSource({ folders: parentFolders }),
});
const isValidSection = (section: {
translations?:
@ -100,6 +116,3 @@ const isValidSection = (section: {
}
return section.subfolders.every(isPayloadType);
};
const getLanguageId = (language: string | Language) =>
typeof language === "object" ? language.id : language;

View File

@ -3,6 +3,7 @@ import { tagsField } from "../../fields/tagsField/tagsField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { createEditor } from "../../utils/editor";
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
import { getByID } from "./endpoints/getByID";
const fields = {
filename: "filename",
@ -36,6 +37,7 @@ export const Images = buildImageCollectionConfig({
},
],
},
endpoints: [getByID],
fields: [
translatedFields({
name: fields.translations,

View File

@ -0,0 +1,77 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointImage, PayloadImage } from "../../../sdk";
import { Image } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isNotEmpty, isValidPayloadImage } from "../../../utils/asserts";
import {
convertRTCToEndpointRTC,
convertTagsEndpointTagsGroups,
getLanguageId,
} from "../../../utils/endpoints";
export const getByID: CollectionEndpoint = {
method: "get",
path: "/id/:id",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
if (!req.params.id) {
return res.status(400).send({ errors: [{ message: "Missing 'id' query params" }] });
}
try {
const result = await payload.findByID({
collection: Collections.Images,
id: req.params.id,
});
if (!isValidPayloadImage(result)) {
return res.sendStatus(404);
}
return res.status(200).json(convertImageToEndpointImage(result));
} catch {
return res.sendStatus(404);
}
},
};
export const convertImageToEndpointImage = ({
url,
width,
height,
tags,
translations,
mimeType,
createdAt,
updatedAt,
filename,
filesize,
id,
}: Image & PayloadImage): EndpointImage => ({
url,
width,
height,
tagGroups: convertTagsEndpointTagsGroups(tags),
createdAt,
filename,
filesize,
id,
mimeType,
updatedAt,
translations:
translations?.map(({ language, title, description }) => ({
language: getLanguageId(language),
title,
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
});

View File

@ -2,15 +2,13 @@ import {
BreakBlockType,
Collections,
PageType,
RichTextBreakBlock,
RichTextContent,
RichTextSectionBlock,
isBlockNodeBreakBlock,
isBlockNodeSectionBlock,
isNodeBlockNode,
} from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointPage, EndpointPagePreview, TableOfContentEntry } from "../../../sdk";
import { EndpointPage, TableOfContentEntry } from "../../../sdk";
import { Page } from "../../../types/collections";
import {
isNotEmpty,
@ -18,85 +16,70 @@ import {
isPayloadType,
isValidPayloadImage,
} from "../../../utils/asserts";
import { convertTagsToGroups, handleParentPages, handleRecorder } from "../../../utils/endpoints";
import {
convertRTCToEndpointRTC,
convertSourceToEndpointSource,
convertTagsEndpointTagsGroups,
} from "../../../utils/endpoints";
import { convertRecorderToEndpointRecorder } from "../../Recorders/endpoints/getByUsername";
export const getBySlugEndpoint = createGetByEndpoint({
collection: Collections.Pages,
attribute: "slug",
handler: (page: Page): EndpointPage => {
const { translations, collectibles, folders, backgroundImage } = page;
return {
...convertPageToPreview(page),
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
translations: translations.map(
({
content,
language,
sourceLanguage,
title,
pretitle,
subtitle,
proofreaders,
summary,
transcribers,
translators,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
title,
...(isNotEmpty(subtitle) ? { subtitle } : {}),
...(isNotEmpty(summary) ? { summary } : {}),
content: handleContent(content),
toc: handleToc(content),
translators: isPayloadArrayType(translators) ? translators.map(handleRecorder) : [],
transcribers: isPayloadArrayType(transcribers) ? transcribers.map(handleRecorder) : [],
proofreaders: isPayloadArrayType(proofreaders) ? proofreaders.map(handleRecorder) : [],
})
),
parentPages: handleParentPages({ collectibles, folders }),
};
},
handler: (page) => convertPageToEndpointPage(page),
});
const handleContent = (
{ root: { children, ...others } }: RichTextContent,
parentPrefix = ""
): RichTextContent => {
let index = 0;
return {
root: {
...others,
children: children.map((node) => {
if (isNodeBlockNode(node)) {
if (isBlockNodeSectionBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextSectionBlock = {
...node,
fields: {
...node.fields,
content: handleContent(node.fields.content, anchorHash),
},
anchorHash,
};
return newNode;
} else if (isBlockNodeBreakBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextBreakBlock = {
...node,
anchorHash,
};
return newNode;
}
}
return node;
}),
},
};
};
export const convertPageToEndpointPage = ({
translations,
collectibles,
folders,
backgroundImage,
authors,
slug,
tags,
thumbnail,
type,
}: Page): EndpointPage => ({
slug,
type: type as PageType,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsEndpointTagsGroups(tags),
authors: isPayloadArrayType(authors) ? authors.map(convertRecorderToEndpointRecorder) : [],
...(isValidPayloadImage(backgroundImage) ? { backgroundImage } : {}),
translations: translations.map(
({
content,
language,
sourceLanguage,
title,
pretitle,
subtitle,
proofreaders,
summary,
transcribers,
translators,
}) => ({
language: isPayloadType(language) ? language.id : language,
sourceLanguage: isPayloadType(sourceLanguage) ? sourceLanguage.id : sourceLanguage,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
title,
...(isNotEmpty(subtitle) ? { subtitle } : {}),
...(isNotEmpty(summary) ? { summary } : {}),
content: convertRTCToEndpointRTC(content),
toc: handleToc(content),
translators: isPayloadArrayType(translators)
? translators.map(convertRecorderToEndpointRecorder)
: [],
transcribers: isPayloadArrayType(transcribers)
? transcribers.map(convertRecorderToEndpointRecorder)
: [],
proofreaders: isPayloadArrayType(proofreaders)
? proofreaders.map(convertRecorderToEndpointRecorder)
: [],
})
),
parentPages: convertSourceToEndpointSource({ collectibles, folders }),
});
const handleToc = (content: RichTextContent, parentPrefix = ""): TableOfContentEntry[] => {
let index = 0;
@ -150,24 +133,3 @@ const handleToc = (content: RichTextContent, parentPrefix = ""): TableOfContentE
return [];
});
};
export const convertPageToPreview = ({
authors,
slug,
translations,
tags,
thumbnail,
type,
}: Page): EndpointPagePreview => ({
slug,
type: type as PageType,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
tagGroups: convertTagsToGroups(tags),
translations: translations.map(({ language, title, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
title,
...(isNotEmpty(subtitle) ? { subtitle } : {}),
})),
authors: isPayloadArrayType(authors) ? authors.map(handleRecorder) : [],
});

View File

@ -7,6 +7,7 @@ import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { getByUsernameEndpoint } from "./endpoints/getByUsername";
import { importFromStrapi } from "./endpoints/importFromStrapi";
import { beforeLoginMustHaveAtLeastOneRole } from "./hooks/beforeLoginMustHaveAtLeastOneRole";
@ -74,7 +75,7 @@ export const Recorders = buildCollectionConfig({
hooks: {
beforeLogin: [beforeLoginMustHaveAtLeastOneRole],
},
endpoints: [importFromStrapi, getAllEndpoint],
endpoints: [importFromStrapi, getAllEndpoint, getByUsernameEndpoint],
timestamps: false,
fields: [
rowField([

View File

@ -0,0 +1,24 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointRecorder } from "../../../sdk";
import { Recorder } from "../../../types/collections";
import { isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
export const getByUsernameEndpoint = createGetByEndpoint({
collection: Collections.Recorders,
attribute: "username",
handler: (recorder) => convertRecorderToEndpointRecorder(recorder),
});
export const convertRecorderToEndpointRecorder = ({
id,
languages,
username,
avatar,
anonymize,
}: Recorder): EndpointRecorder => ({
id,
languages: languages?.map((language) => (isPayloadType(language) ? language.id : language)) ?? [],
username: anonymize ? `Recorder#${id.substring(0, 5)}` : username,
...(isValidPayloadImage(avatar) ? { avatar } : {}),
});

View File

@ -6,6 +6,7 @@ import { tagsField } from "../../fields/tagsField/tagsField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";
const fields = {
filename: "filename",
@ -47,13 +48,13 @@ export const Videos = buildCollectionConfig({
mimeTypes: ["video/*"],
disableLocalStorage: true,
},
endpoints: [getByID],
fields: [
rowField([
{ name: fields.duration, type: "number", min: 0, required: true },
imageField({
name: fields.thumbnail,
relationTo: Collections.MediaThumbnails,
required: true,
}),
]),
translatedFields({

View File

@ -0,0 +1,108 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointVideo, PayloadMedia } from "../../../sdk";
import { Video } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import {
isDefined,
isEmpty,
isNotEmpty,
isPayloadType,
isUndefined,
isValidPayloadImage,
isValidPayloadMedia,
} from "../../../utils/asserts";
import {
convertRTCToEndpointRTC,
convertTagsEndpointTagsGroups,
getLanguageId,
} from "../../../utils/endpoints";
export const getByID: CollectionEndpoint = {
method: "get",
path: "/id/:id",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
if (!req.params.id) {
return res.status(400).send({ errors: [{ message: "Missing 'id' query params" }] });
}
try {
const result = await payload.findByID({
collection: Collections.Videos,
id: req.params.id,
});
if (!isValidPayloadMedia(result)) {
return res.sendStatus(404);
}
return res.status(200).json(convertVideoToEndpointVideo(result));
} catch {
return res.sendStatus(404);
}
},
};
export const convertVideoToEndpointVideo = ({
url,
tags,
translations,
mimeType,
createdAt,
updatedAt,
filename,
filesize,
duration,
id,
thumbnail,
platform,
platformEnabled,
}: Video & PayloadMedia): EndpointVideo => ({
url,
tagGroups: convertTagsEndpointTagsGroups(tags),
createdAt,
filename,
filesize,
id,
mimeType,
updatedAt,
translations:
translations?.map(({ language, title, description }) => ({
language: getLanguageId(language),
title,
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
duration,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
...(platformEnabled && isDefined(platform) && isPayloadType(platform.channel)
? {
platform: {
channel: platform.channel,
publishedDate: platform.publishedDate,
url: platform.url,
},
}
: {}),
subtitles:
translations.flatMap(({ language, subfile }) => {
if (
isUndefined(subfile) ||
!isPayloadType(subfile) ||
isUndefined(subfile.url) ||
isEmpty(subfile.url)
)
return [];
return { language: getLanguageId(language), url: subfile.url };
}) ?? [],
});

View File

@ -29,7 +29,7 @@ export const VideosChannels: CollectionConfig = buildCollectionConfig({
{ name: fields.url, type: "text", required: true, unique: true },
rowField([
{ name: fields.title, type: "text", required: true },
{ name: fields.subscribers, type: "number" },
{ name: fields.subscribers, type: "number", required: true },
]),
backPropagationField({
name: fields.videos,

View File

@ -3,7 +3,7 @@ import { Collections } from "../../../constants";
import { EndpointWebsiteConfig } from "../../../sdk";
import { CollectionEndpoint } from "../../../types/payload";
import { isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertFolderToPreview } from "../../Folders/endpoints/getBySlugEndpoint";
import { convertFolderToEndpointFolder } from "../../Folders/endpoints/getBySlugEndpoint";
export const getConfigEndpoint: CollectionEndpoint = {
method: "get",
@ -46,7 +46,7 @@ export const getConfigEndpoint: CollectionEndpoint = {
homeFolders?.flatMap(({ folder, darkThumbnail, lightThumbnail }) => {
if (!isPayloadType(folder)) return [];
return {
...convertFolderToPreview(folder),
...convertFolderToEndpointFolder(folder),
...(isValidPayloadImage(darkThumbnail) ? { darkThumbnail } : {}),
...(isValidPayloadImage(lightThumbnail) ? { lightThumbnail } : {}),
};

View File

@ -1,11 +1,5 @@
import type {
Audio,
BreakBlock,
Image,
SectionBlock,
TranscriptBlock,
Video,
} from "./types/collections";
import { EndpointAudio, EndpointImage, EndpointVideo } from "./sdk";
import type { BreakBlock, SectionBlock, TranscriptBlock } from "./types/collections";
// END MOCKING SECTION
@ -149,17 +143,17 @@ export interface RichTextUploadNode extends RichTextNode {
export interface RichTextUploadImageNode extends RichTextUploadNode {
relationTo: Collections.Images;
value: Image;
value: EndpointImage;
}
export interface RichTextUploadVideoNode extends RichTextUploadNode {
relationTo: Collections.Videos;
value: Video;
value: EndpointVideo;
}
export interface RichTextUploadAudioNode extends RichTextUploadNode {
relationTo: Collections.Audios;
value: Audio;
value: EndpointAudio;
}
export interface RichTextTextNode extends RichTextNode {

View File

@ -35,7 +35,7 @@ const configuredFtpAdapter = sftpAdapter({
privateKey: process.env.SFTP_PRIVATE_KEY,
},
destinationPathRoot: process.env.SFTP_DESTINATION_PATH_ROOT ?? "",
publicEndpoint: process.env.FTP_BASE_URL ?? "",
publicEndpoint: process.env.SFTP_BASE_URL ?? "",
});
export default buildConfig({

View File

@ -96,7 +96,7 @@ const request = async (url: string, init?: RequestInit): Promise<Response> => {
// SDK and Types
export type EndpointFolderPreview = {
export type EndpointFolder = {
slug: string;
icon?: string;
translations: {
@ -104,33 +104,42 @@ export type EndpointFolderPreview = {
name: string;
description?: RichTextContent;
}[];
};
export type EndpointFolder = EndpointFolderPreview & {
sections:
| { type: "single"; subfolders: EndpointFolderPreview[] }
| { type: "single"; subfolders: EndpointFolder[] }
| {
type: "multiple";
sections: {
translations: { language: string; name: string }[];
subfolders: EndpointFolderPreview[];
subfolders: EndpointFolder[];
}[];
};
files: (
| {
relationTo: "collectibles";
value: EndpointCollectiblePreview;
relationTo: Collections.Collectibles;
value: EndpointCollectible;
}
| {
relationTo: "pages";
value: EndpointPagePreview;
relationTo: Collections.Pages;
value: EndpointPage;
}
| {
relationTo: Collections.Images;
value: EndpointImage;
}
| {
relationTo: Collections.Audios;
value: EndpointAudio;
}
| {
relationTo: Collections.Videos;
value: EndpointVideo;
}
)[];
parentPages: EndpointSource[];
};
export type EndpointWebsiteConfig = {
homeFolders: (EndpointFolderPreview & {
homeFolders: (EndpointFolder & {
lightThumbnail?: PayloadImage;
darkThumbnail?: PayloadImage;
})[];
@ -178,23 +187,18 @@ export type EndpointTagsGroup = {
tags: EndpointTag[];
};
export type EndpointPagePreview = {
export type EndpointPage = {
slug: string;
type: PageType;
thumbnail?: PayloadImage;
authors: EndpointRecorder[];
tagGroups: EndpointTagsGroup[];
backgroundImage?: PayloadImage;
translations: {
language: string;
pretitle?: string;
title: string;
subtitle?: string;
}[];
};
export type EndpointPage = EndpointPagePreview & {
backgroundImage?: PayloadImage;
translations: (EndpointPagePreview["translations"][number] & {
sourceLanguage: string;
summary?: RichTextContent;
content: RichTextContent;
@ -202,11 +206,11 @@ export type EndpointPage = EndpointPagePreview & {
translators: EndpointRecorder[];
proofreaders: EndpointRecorder[];
toc: TableOfContentEntry[];
})[];
}[];
parentPages: EndpointSource[];
};
export type EndpointCollectiblePreview = {
export type EndpointCollectible = {
slug: string;
thumbnail?: PayloadImage;
translations: {
@ -219,9 +223,6 @@ export type EndpointCollectiblePreview = {
tagGroups: EndpointTagsGroup[];
releaseDate?: string;
languages: string[];
};
export type EndpointCollectible = EndpointCollectiblePreview & {
backgroundImage?: PayloadImage;
nature: CollectibleNature;
gallery: PayloadImage[];
@ -242,15 +243,23 @@ export type EndpointCollectible = EndpointCollectiblePreview & {
bindingType?: CollectibleBindingTypes;
pageOrder?: CollectiblePageOrders;
};
subitems: EndpointCollectiblePreview[];
subitems: EndpointCollectible[];
contents: {
content:
| {
relationTo: "pages";
value: EndpointPagePreview;
relationTo: Collections.Pages;
value: EndpointPage;
}
| {
relationTo: "generic-contents";
relationTo: Collections.Audios;
value: EndpointAudio;
}
| {
relationTo: Collections.Videos;
value: EndpointVideo;
}
| {
relationTo: Collections.GenericContents;
value: {
translations: {
language: string;
@ -315,21 +324,72 @@ export type EndpointSource =
| { type: "url"; url: string; label: string }
| {
type: "collectible";
collectible: EndpointCollectiblePreview;
collectible: EndpointCollectible;
range?:
| { type: "page"; page: number }
| { type: "timestamp"; timestamp: string }
| { type: "custom"; translations: { language: string; note: string }[] };
}
| { type: "page"; page: EndpointPagePreview }
| { type: "folder"; folder: EndpointFolderPreview };
| { type: "page"; page: EndpointPage }
| { type: "folder"; folder: EndpointFolder };
export type PayloadImage = {
export type EndpointMedia = {
id: string;
url: string;
filename: string;
mimeType: string;
filesize: number;
updatedAt: string;
createdAt: string;
tagGroups: EndpointTagsGroup[];
translations: {
language: string;
title: string;
description?: RichTextContent;
}[];
};
export type EndpointImage = EndpointMedia & {
width: number;
height: number;
};
export type EndpointAudio = EndpointMedia & {
thumbnail?: PayloadImage;
duration: number;
};
export type EndpointVideo = EndpointMedia & {
thumbnail?: PayloadImage;
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 PayloadMedia = {
url: string;
mimeType: string;
filename: string;
filesize: number;
};
export type PayloadImage = PayloadMedia & {
width: number;
height: number;
};
export const payload = {
@ -353,4 +413,10 @@ export const payload = {
await (await request(payloadApiUrl(Collections.ChronologyEvents, `all`))).json(),
getChronologyEventByID: async (id: string): Promise<EndpointChronologyEvent> =>
await (await request(payloadApiUrl(Collections.ChronologyEvents, `id/${id}`))).json(),
getImageByID: async (id: string): Promise<EndpointImage> =>
await (await request(payloadApiUrl(Collections.Images, `id/${id}`))).json(),
getAudioByID: async (id: string): Promise<EndpointAudio> =>
await (await request(payloadApiUrl(Collections.Audios, `id/${id}`))).json(),
getVideoByID: async (id: string): Promise<EndpointVideo> =>
await (await request(payloadApiUrl(Collections.Videos, `id/${id}`))).json(),
};

View File

@ -603,7 +603,7 @@ export interface MediaThumbnail {
export interface Video {
id: string;
duration: number;
thumbnail: string | MediaThumbnail;
thumbnail?: string | MediaThumbnail | null;
translations: {
language: string | Language;
title: string;
@ -665,7 +665,7 @@ export interface VideosChannel {
id: string;
url: string;
title: string;
subscribers?: number | null;
subscribers: number;
videos?: (string | Video)[] | null;
}
/**

View File

@ -1,5 +1,5 @@
import { RichTextContent, isNodeParagraphNode } from "../constants";
import { PayloadImage } from "../sdk";
import { PayloadImage, PayloadMedia } from "../sdk";
export const isDefined = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined;
@ -7,10 +7,11 @@ export const isDefined = <T>(value: T | null | undefined): value is T =>
export const isUndefined = <T>(value: T | null | undefined): value is null | undefined =>
!isDefined(value);
export const isNotEmpty = (value: string | null | undefined | RichTextContent): value is string =>
!isEmpty(value);
export const isNotEmpty = (
value: string | null | undefined | RichTextContent
): value is string | RichTextContent => !isEmpty(value);
export const isEmpty = (value: string | null | undefined | RichTextContent): value is string =>
export const isEmpty = (value: string | null | undefined | RichTextContent): boolean =>
isUndefined(value) ||
(typeof value === "string" && isEmptyString(value)) ||
(typeof value === "object" && isEmptyRichText(value));
@ -26,6 +27,7 @@ export const isValidPayloadImage = (
image:
| {
filename?: string | null;
filesize?: number | null;
mimeType?: string | null;
width?: number | null;
height?: number | null;
@ -42,6 +44,28 @@ export const isValidPayloadImage = (
if (isEmpty(image.mimeType)) return false;
if (isUndefined(image.width)) return false;
if (isUndefined(image.height)) return false;
if (isUndefined(image.filesize)) return false;
return true;
};
export const isValidPayloadMedia = (
media:
| {
filename?: string | null;
filesize?: number | null;
mimeType?: string | null;
url?: string | null;
}
| undefined
| null
| string
): media is PayloadMedia => {
if (isUndefined(media)) return false;
if (typeof media === "string") return false;
if (isEmpty(media.filename)) return false;
if (isEmpty(media.url)) return false;
if (isEmpty(media.mimeType)) return false;
if (isUndefined(media.filesize)) return false;
return true;
};

View File

@ -1,10 +1,32 @@
import { convertCollectibleToPreview } from "../collections/Collectibles/endpoints/getBySlugEndpoint";
import { convertFolderToPreview } from "../collections/Folders/endpoints/getBySlugEndpoint";
import { EndpointRecorder, EndpointSource, EndpointTag, EndpointTagsGroup } from "../sdk";
import { Collectible, Folder, Recorder, Tag } from "../types/collections";
import { isPayloadArrayType, isPayloadType, isPublished, isValidPayloadImage } from "./asserts";
import { convertAudioToEndpointAudio } from "../collections/Audios/endpoints/getByID";
import { convertCollectibleToEndpointCollectible } from "../collections/Collectibles/endpoints/getBySlugEndpoint";
import { convertFolderToEndpointFolder } from "../collections/Folders/endpoints/getBySlugEndpoint";
import { convertImageToEndpointImage } from "../collections/Images/endpoints/getByID";
import { convertVideoToEndpointVideo } from "../collections/Videos/endpoints/getByID";
import {
RichTextBreakBlock,
RichTextContent,
RichTextSectionBlock,
RichTextUploadNode,
isBlockNodeBreakBlock,
isBlockNodeSectionBlock,
isNodeBlockNode,
isNodeUploadNode,
isUploadNodeAudioNode,
isUploadNodeImageNode,
isUploadNodeVideoNode,
} from "../constants";
import { EndpointSource, EndpointTag, EndpointTagsGroup } from "../sdk";
import { Audio, Collectible, Folder, Image, Language, Tag, Video } from "../types/collections";
import {
isPayloadArrayType,
isPayloadType,
isPublished,
isValidPayloadImage,
isValidPayloadMedia,
} from "./asserts";
export const convertTagsToGroups = (
export const convertTagsEndpointTagsGroups = (
tags: (string | Tag)[] | null | undefined
): EndpointTagsGroup[] => {
if (!isPayloadArrayType(tags)) {
@ -44,7 +66,75 @@ export const convertTagsToGroups = (
return groups;
};
export const handleParentPages = ({
export const convertRTCToEndpointRTC = (
{ root: { children, ...others } }: RichTextContent,
parentPrefix = ""
): RichTextContent => {
let index = 0;
return {
root: {
...others,
children: children.map((node) => {
if (isNodeBlockNode(node)) {
// Add anchor hash on section block (TOC)
if (isBlockNodeSectionBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextSectionBlock = {
...node,
fields: {
...node.fields,
content: convertRTCToEndpointRTC(node.fields.content, anchorHash),
},
anchorHash,
};
return newNode;
// Add anchor hash on section block (TOC)
} else if (isBlockNodeBreakBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextBreakBlock = {
...node,
anchorHash,
};
return newNode;
}
} else if (isNodeUploadNode(node)) {
const errorUploadNode: RichTextUploadNode = {
type: "upload",
relationTo: "error",
version: 1,
};
if (isUploadNodeImageNode(node)) {
const value = node.value as Image | string;
if (!isPayloadType(value) || !isValidPayloadImage(value)) return errorUploadNode;
return {
...node,
value: convertImageToEndpointImage(value),
};
} else if (isUploadNodeAudioNode(node)) {
const value = node.value as Audio | string;
if (!isPayloadType(value) || !isValidPayloadMedia(value)) return errorUploadNode;
return {
...node,
value: convertAudioToEndpointAudio(value),
};
} else if (isUploadNodeVideoNode(node)) {
const value = node.value as Video | string;
if (!isPayloadType(value) || !isValidPayloadMedia(value)) return errorUploadNode;
return {
...node,
value: convertVideoToEndpointVideo(value),
};
}
}
return node;
}),
},
};
};
export const convertSourceToEndpointSource = ({
collectibles,
folders,
}: {
@ -57,7 +147,7 @@ export const handleParentPages = ({
collectibles.filter(isPublished).forEach((collectible) => {
result.push({
type: "collectible",
collectible: convertCollectibleToPreview(collectible),
collectible: convertCollectibleToEndpointCollectible(collectible),
});
});
}
@ -66,7 +156,7 @@ export const handleParentPages = ({
folders.forEach((folder) => {
result.push({
type: "folder",
folder: convertFolderToPreview(folder),
folder: convertFolderToEndpointFolder(folder),
});
});
}
@ -74,19 +164,6 @@ export const handleParentPages = ({
return result;
};
export const handleRecorder = ({
id,
languages,
username,
avatar,
anonymize,
}: Recorder): EndpointRecorder => ({
id,
languages: languages?.map((language) => (isPayloadType(language) ? language.id : language)) ?? [],
username: anonymize ? `Recorder#${id.substring(0, 5)}` : username,
...(isValidPayloadImage(avatar) ? { avatar } : {}),
});
export const getDomainFromUrl = (url: string): string => {
const urlObject = new URL(url);
let domain = urlObject.hostname;
@ -95,3 +172,6 @@ export const getDomainFromUrl = (url: string): string => {
}
return domain;
};
export const getLanguageId = (language: string | Language) =>
typeof language === "object" ? language.id : language;