Added files collection

This commit is contained in:
DrMint 2024-06-22 20:30:58 +02:00
parent dd2475e660
commit d336a52d9f
13 changed files with 359 additions and 68 deletions

View File

@ -113,6 +113,8 @@ const fields = {
contents: "contents",
contentsContent: "content",
files: "files",
pageInfo: "pageInfo",
pageInfoBindingType: "bindingType",
pageInfoPageCount: "pageCount",
@ -229,7 +231,7 @@ export const Collectibles = buildVersionedCollectionConfig({
type: "array",
admin: {
description:
"Additional images of the item (unboxing, on shelf, promotional images...)",
"Additional images of the collectible (e.g: unboxing, on shelf, promotional images...)",
},
labels: { singular: "Image", plural: "Images" },
fields: [
@ -444,7 +446,7 @@ export const Collectibles = buildVersionedCollectionConfig({
label: "URLs",
type: "array",
admin: {
description: "Links to official websites where to get/buy the item.",
description: "Links to official websites where to get/buy the collectible.",
},
fields: [{ name: fields.urlsUrl, type: "text", required: true }],
},
@ -567,33 +569,27 @@ export const Collectibles = buildVersionedCollectionConfig({
label: "Contents",
fields: [
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 } }),
}),
{
name: fields.subitems,
type: "relationship",
hasMany: true,
relationTo: Collections.Collectibles,
admin: {
description:
"Collectibles that are part of this collectible (e.g: artbook in a collector's edition, booklet in a CD...)",
},
},
{
name: fields.files,
type: "relationship",
hasMany: true,
relationTo: Collections.Files,
admin: {
description: "Files related to the collectible (e.g: zip of all the scans)",
},
},
]),
{
name: fields.contents,
type: "array",
@ -699,6 +695,29 @@ 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 } }),
}),
]),
],
},
],

View File

@ -5,6 +5,7 @@ import { Collectible } from "../../../types/collections";
import {
isAudio,
isDefined,
isFile,
isImage,
isNotEmpty,
isPayloadArrayType,
@ -21,6 +22,7 @@ import {
getDomainFromUrl,
} from "../../../utils/endpoints";
import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID";
import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID";
import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertRecorderToEndpointRecorderPreview } from "../../Recorders/endpoints/getByID";
import { convertVideoToEndpointVideoPreview } from "../../Videos/endpoints/getByID";
@ -64,6 +66,7 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp
nature,
urls,
subitems,
files,
gallery: rawGallery,
contents,
priceEnabled,
@ -110,6 +113,11 @@ const convertCollectibleToEndpointCollectible = (collectible: Collectible): Endp
subitems: isPayloadArrayType(subitems)
? subitems.filter(isPublished).map(convertCollectibleToEndpointCollectiblePreview)
: [],
files:
files?.flatMap((file) => {
if (!isPayloadType(file) || !isFile(file)) return [];
return convertFileToEndpointFilePreview(file);
}) ?? [],
contents: handleContents(contents),
...handlePrice(price, priceEnabled),
createdAt,

View File

@ -0,0 +1,71 @@
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 { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getByID } from "./endpoints/getByID";
const fields = {
filename: "filename",
mimeType: "mimeType",
filesize: "filesize",
updatedAt: "updatedAt",
translations: "translations",
translationsPretitle: "pretitle",
translationsTitle: "title",
translationsSubtitle: "subtitle",
translationsDescription: "description",
thumbnail: "thumbnail",
attributes: "attributes",
credits: "credits",
};
export const Files = buildCollectionConfig({
slug: Collections.Files,
labels: { singular: "File", plural: "Files" },
defaultSort: fields.filename,
admin: {
group: CollectionGroups.Media,
preview: ({ id }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/files/${id}`,
description: "For any file that isn't a video, an image, or an audio file.",
defaultColumns: [
fields.filename,
fields.thumbnail,
fields.mimeType,
fields.filesize,
fields.translations,
fields.updatedAt,
],
},
upload: {
disableLocalStorage: true,
},
endpoints: [getByID],
fields: [
imageField({
name: fields.thumbnail,
relationTo: Collections.MediaThumbnails,
}),
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.translationsTitle },
fields: [
rowField([
{ name: fields.translationsPretitle, type: "text" },
{ name: fields.translationsTitle, type: "text", required: true },
{ name: fields.translationsSubtitle, type: "text" },
]),
{
name: fields.translationsDescription,
type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }),
},
],
}),
attributesField({ name: fields.attributes }),
creditsField({ name: fields.credits }),
],
});

View File

@ -0,0 +1,96 @@
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";
import {
convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointPayloadImage,
convertRTCToEndpointRTC,
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.Files,
id: req.params.id,
});
if (!isFile(result)) {
return res.sendStatus(404);
}
return res.status(200).json(convertFileToEndpointFile(result));
} catch {
return res.sendStatus(404);
}
},
};
export const convertFileToEndpointFilePreview = ({
url,
attributes,
translations,
mimeType,
filename,
id,
thumbnail,
filesize,
}: File & PayloadMedia): EndpointFilePreview => ({
id,
url,
filename,
filesize,
mimeType,
attributes: convertAttributesToEndpointAttributes(attributes),
translations:
translations?.map(({ language, title, pretitle, subtitle }) => ({
language: getLanguageId(language),
...(isNotEmpty(pretitle) ? { pretitle } : {}),
title,
...(isNotEmpty(subtitle) ? { subtitle } : {}),
})) ?? [],
...(isMediaThumbnail(thumbnail)
? { thumbnail: convertMediaThumbnailToEndpointPayloadImage(thumbnail) }
: {}),
});
const convertFileToEndpointFile = (file: File & PayloadMedia): EndpointFile => {
const { translations, createdAt, updatedAt, filesize, credits } = file;
return {
...convertFileToEndpointFilePreview(file),
createdAt,
filesize,
updatedAt,
translations:
translations?.map(({ language, title, pretitle, subtitle, description }) => ({
language: getLanguageId(language),
...(isNotEmpty(pretitle) ? { pretitle } : {}),
title,
...(isNotEmpty(subtitle) ? { subtitle } : {}),
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
credits: convertCreditsToEndpointCredits(credits),
};
};

View File

@ -107,6 +107,7 @@ export const Folders = buildCollectionConfig({
Collections.Videos,
Collections.Images,
Collections.Audios,
Collections.Files,
],
hasMany: true,
},

View File

@ -5,6 +5,7 @@ import { Folder, Language } from "../../../types/collections";
import {
isAudio,
isDefined,
isFile,
isImage,
isNotEmpty,
isPayloadType,
@ -14,6 +15,7 @@ import {
import { convertSourceToEndpointSource, getLanguageId } from "../../../utils/endpoints";
import { convertAudioToEndpointAudioPreview } from "../../Audios/endpoints/getByID";
import { convertCollectibleToEndpointCollectiblePreview } from "../../Collectibles/endpoints/getBySlugEndpoint";
import { convertFileToEndpointFilePreview } from "../../Files/endpoints/getByID";
import { convertImageToEndpointImagePreview } from "../../Images/endpoints/getByID";
import { convertPageToEndpointPagePreview } from "../../Pages/endpoints/getBySlugEndpoint";
import { convertVideoToEndpointVideoPreview } from "../../Videos/endpoints/getByID";
@ -105,6 +107,11 @@ const convertFolderToEndpointFolder = (folder: Folder): EndpointFolder => {
return [
{ relationTo: Collections.Videos, value: convertVideoToEndpointVideoPreview(value) },
];
case Collections.Files:
if (!isFile(value)) return [];
return [
{ relationTo: Collections.Files, value: convertFileToEndpointFilePreview(value) },
];
default:
return [];
}

View File

@ -31,6 +31,7 @@ export const Recorders = buildCollectionConfig({
defaultSort: fields.username,
admin: {
useAsTitle: fields.username,
preview: ({ id }) => `${process.env.PAYLOAD_PUBLIC_FRONTEND_BASE_URL}/en/recorders/${id}`,
description:
"Recorders are contributors of the Accord's Library project. Ask an admin to create a \
Recorder here to be able to credit them in other collections.",

View File

@ -10,6 +10,7 @@ export enum Collections {
Collectibles = "collectibles",
CreditsRole = "credits-roles",
Currencies = "currencies",
Files = "files",
Folders = "folders",
GenericContents = "generic-contents",
Images = "images",

View File

@ -69,6 +69,13 @@ export const getAllPathsEndpoint: Endpoint = {
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,
@ -95,6 +102,7 @@ export const getAllPathsEndpoint: Endpoint = {
videos: videos.docs.map(({ id }) => id),
audios: audios.docs.map(({ id }) => id),
images: images.docs.map(({ id }) => id),
files: files.docs.map(({ id }) => id),
recorders: recorders.docs.map(({ id }) => id),
chronologyEvents: chronologyEvents.docs.map(({ id }) => id),
};

View File

@ -10,6 +10,7 @@ import { ChronologyEvents } from "./collections/ChronologyEvents/ChronologyEvent
import { Collectibles } from "./collections/Collectibles/Collectibles";
import { CreditsRoles } from "./collections/CreditsRoles/CreditsRoles";
import { Currencies } from "./collections/Currencies/Currencies";
import { Files } from "./collections/Files/Files";
import { Folders } from "./collections/Folders/Folders";
import { GenericContents } from "./collections/GenericContents/GenericContents";
import { Images } from "./collections/Images/Images";
@ -30,7 +31,7 @@ import { Collections } from "./constants";
import { getAllPathsEndpoint } from "./endpoints/getAllPathsEndpoint";
import { createEditor } from "./utils/editor";
const configuredFtpAdapter = sftpAdapter({
const configuredSftpAdapter = sftpAdapter({
connectOptions: {
host: process.env.SFTP_HOST,
username: process.env.SFTP_USERNAME,
@ -66,6 +67,7 @@ export default buildConfig({
Videos,
VideosSubtitles,
VideosChannels,
Files,
Scans,
Tags,
@ -96,17 +98,22 @@ export default buildConfig({
cloudStorage({
collections: {
[Collections.Videos]: {
adapter: configuredFtpAdapter,
adapter: configuredSftpAdapter,
disableLocalStorage: true,
disablePayloadAccessControl: true,
},
[Collections.VideosSubtitles]: {
adapter: configuredFtpAdapter,
adapter: configuredSftpAdapter,
disableLocalStorage: true,
disablePayloadAccessControl: true,
},
[Collections.Audios]: {
adapter: configuredFtpAdapter,
adapter: configuredSftpAdapter,
disableLocalStorage: true,
disablePayloadAccessControl: true,
},
[Collections.Files]: {
adapter: configuredSftpAdapter,
disableLocalStorage: true,
disablePayloadAccessControl: true,
},

View File

@ -54,6 +54,10 @@ export type EndpointFolder = EndpointFolderPreview & {
relationTo: Collections.Videos;
value: EndpointVideoPreview;
}
| {
relationTo: Collections.Files;
value: EndpointFilePreview;
}
)[];
parentPages: EndpointSource[];
};
@ -223,6 +227,7 @@ export type EndpointCollectible = EndpointCollectiblePreview & {
pageOrder?: CollectiblePageOrders;
};
subitems: EndpointCollectiblePreview[];
files: EndpointFilePreview[];
contents: {
content:
| {
@ -498,6 +503,16 @@ export type EndpointVideo = EndpointMedia & {
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;
@ -523,6 +538,7 @@ export type EndpointAllPaths = {
videos: string[];
audios: string[];
images: string[];
files: string[];
recorders: string[];
chronologyEvents: string[];
};
@ -565,6 +581,7 @@ export const getSDKEndpoint = {
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}`,
getAllPathsEndpoint: () => `/all-paths`,
getLoginEndpoint: () => `/${Collections.Recorders}/login`,
@ -659,6 +676,8 @@ export const getPayloadSDK = ({
await request(getSDKEndpoint.getAudioByIDEndpoint(id)),
getVideoByID: async (id: string): Promise<EndpointVideo> =>
await request(getSDKEndpoint.getVideoByIDEndpoint(id)),
getFileByID: async (id: string): Promise<EndpointFile> =>
await request(getSDKEndpoint.getFileByIDEndpoint(id)),
getRecorderByID: async (id: string): Promise<EndpointRecorder> =>
await request(getSDKEndpoint.getRecorderByIDEndpoint(id)),
getAllPaths: async (): Promise<EndpointAllPaths> =>

View File

@ -30,6 +30,7 @@ export interface Config {
videos: Video;
"videos-subtitles": VideoSubtitle;
"videos-channels": VideosChannel;
files: File;
scans: Scan;
tags: Tag;
attributes: Attribute;
@ -414,6 +415,10 @@ export interface Folder {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "files";
value: string | File;
}
)[]
| null;
updatedAt: string;
@ -536,9 +541,8 @@ export interface Collectible {
bindingType?: ("Paperback" | "Hardcover") | null;
pageOrder?: ("Left to right" | "Right to left") | null;
};
folders?: (string | Folder)[] | null;
parentItems?: (string | Collectible)[] | null;
subitems?: (string | Collectible)[] | null;
files?: (string | File)[] | null;
contents?:
| {
content:
@ -603,6 +607,8 @@ export interface Collectible {
id?: string | null;
}[]
| null;
folders?: (string | Folder)[] | null;
parentItems?: (string | Collectible)[] | null;
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
@ -676,49 +682,35 @@ export interface Currency {
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "generic-contents".
* via the `definition` "files".
*/
export interface GenericContent {
export interface File {
id: string;
name: string;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "audios".
*/
export interface Audio {
id: string;
duration: number;
thumbnail?: string | MediaThumbnail | null;
translations: {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
description?: {
root: {
type: string;
children: {
type: string;
version: number;
translations?:
| {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
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;
}[];
direction: ("ltr" | "rtl") | null;
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[];
} | null;
id?: string | null;
}[]
| null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits;
updatedAt: string;
@ -823,6 +815,64 @@ export interface MediaThumbnail {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "generic-contents".
*/
export interface GenericContent {
id: string;
name: string;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "audios".
*/
export interface Audio {
id: string;
duration: number;
thumbnail?: string | MediaThumbnail | null;
translations: {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
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;
}[];
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits;
updatedAt: string;
createdAt: string;
url?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "videos".

View File

@ -1,6 +1,6 @@
import { RichTextContent, isNodeParagraphNode } from "../constants";
import { PayloadImage, PayloadMedia } from "../sdk";
import { Audio, Image, MediaThumbnail, Scan, Video } from "../types/collections";
import { Audio, File, Image, MediaThumbnail, Scan, Video } from "../types/collections";
export const isDefined = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined;
@ -55,6 +55,9 @@ export const isPayloadImage = (image: unknown): image is PayloadImage => {
export const isVideo = (video: string | Video | null | undefined): video is PayloadMedia & Video =>
isPayloadMedia(video);
export const isFile = (file: string | File | null | undefined): file is PayloadMedia & File =>
isPayloadMedia(file);
export const isAudio = (video: string | Audio | null | undefined): video is PayloadMedia & Audio =>
isPayloadMedia(video);