Updated payload + folders

This commit is contained in:
DrMint 2024-02-18 10:52:23 +01:00
parent a0728eac51
commit b91159e61f
16 changed files with 414 additions and 36 deletions

24
package-lock.json generated
View File

@ -11,12 +11,12 @@
"dependencies": {
"@fontsource/vollkorn": "5.0.18",
"@payloadcms/bundler-webpack": "1.0.6",
"@payloadcms/db-mongodb": "1.3.2",
"@payloadcms/db-mongodb": "1.4.0",
"@payloadcms/richtext-lexical": "0.5.2",
"cross-env": "7.0.3",
"language-tags": "1.0.9",
"luxon": "3.4.4",
"payload": "2.8.2",
"payload": "2.9.0",
"sharp": "0.33.2",
"styled-components": "6.1.8"
},
@ -30,7 +30,7 @@
"@types/styled-components": "5.1.34",
"copyfiles": "2.4.1",
"nodemon": "3.0.3",
"npm-check-updates": "16.14.12",
"npm-check-updates": "16.14.14",
"prettier": "3.2.4",
"ts-node": "10.9.1",
"ts-unused-exports": "10.0.1",
@ -3083,9 +3083,9 @@
}
},
"node_modules/@payloadcms/db-mongodb": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@payloadcms/db-mongodb/-/db-mongodb-1.3.2.tgz",
"integrity": "sha512-4eXlhy1tLHNY6fluLZF89mgkveyPoMqvoC3z763iBbowECz5dWd8lWKuO43ce+N38TDeRRjfzG2x0HzwPuVzLQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@payloadcms/db-mongodb/-/db-mongodb-1.4.0.tgz",
"integrity": "sha512-9DVazhkV5+T2+NpIvIDMZ3AmJScjkj7r8OPwrz3BBvXXKid2NUDewTKFQSVtqWK6Ed5ArlvCAlk9J6zO3Q0+cA==",
"dependencies": {
"bson-objectid": "2.0.4",
"deepmerge": "4.3.1",
@ -10746,9 +10746,9 @@
}
},
"node_modules/npm-check-updates": {
"version": "16.14.12",
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.12.tgz",
"integrity": "sha512-5FvqaDX8AqWWTDQFbBllgLwoRXTvzlqVIRSKl9Kg8bYZTfNwMnrp1Zlmb5e/ocf11UjPTc+ShBFjYQ7kg6FL0w==",
"version": "16.14.14",
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.14.tgz",
"integrity": "sha512-Y3ajS/Ep40jM489rLBdz9jehn/BMil5s9fA4PSr2ZJxxSmtLWCSmRqsI2IEZ9Nb3MTMu8a3s7kBs0l+JbjdkTA==",
"dev": true,
"dependencies": {
"chalk": "^5.3.0",
@ -11512,9 +11512,9 @@
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/payload": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/payload/-/payload-2.8.2.tgz",
"integrity": "sha512-n1WXnhZKe/wHFz4akW+w16OUzCU45i2fFJzgiXakMGC58JbvpDGo/yh287ENrdiHOdtb/a/YkPsKpa6lbZ+gBA==",
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/payload/-/payload-2.9.0.tgz",
"integrity": "sha512-C8QekOUOh1689qWWrCLNXhgC5K1LnKYFmFEru/GxhQBEO6SFEULdQHMnmjP1yrHARu1eVU24UdFVniKY+9oR7A==",
"dependencies": {
"@date-io/date-fns": "2.16.0",
"@dnd-kit/core": "6.0.8",

View File

@ -24,12 +24,12 @@
"dependencies": {
"@fontsource/vollkorn": "5.0.18",
"@payloadcms/bundler-webpack": "1.0.6",
"@payloadcms/db-mongodb": "1.3.2",
"@payloadcms/db-mongodb": "1.4.0",
"@payloadcms/richtext-lexical": "0.5.2",
"cross-env": "7.0.3",
"language-tags": "1.0.9",
"luxon": "3.4.4",
"payload": "2.8.2",
"payload": "2.9.0",
"sharp": "0.33.2",
"styled-components": "6.1.8"
},
@ -43,7 +43,7 @@
"@types/styled-components": "5.1.34",
"copyfiles": "2.4.1",
"nodemon": "3.0.3",
"npm-check-updates": "16.14.12",
"npm-check-updates": "16.14.14",
"prettier": "3.2.4",
"ts-node": "10.9.1",
"ts-unused-exports": "10.0.1",

View File

@ -2,6 +2,7 @@ import { text } from "payload/dist/fields/validations";
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { importFromStrapi } from "./endpoints/importFromStrapi";
const fields = {
@ -23,7 +24,7 @@ export const Currencies = buildCollectionConfig({
group: CollectionGroups.Meta,
},
access: { create: mustBeAdmin, update: mustBeAdmin },
endpoints: [importFromStrapi],
endpoints: [importFromStrapi, getAllEndpoint],
timestamps: false,
fields: [
{

View File

@ -0,0 +1,30 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { Currency } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",
path: "/all",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const currencies: Currency[] = (
await payload.find({
collection: Collections.Currencies,
limit: 100,
sort: "id"
})
).docs;
res.status(200).header("Cache-Control", "max-age=60").json(currencies);
},
};

View File

@ -1,17 +1,27 @@
import { CollectionGroups, Collections } from "../../constants";
import { iconField } from "../../fields/iconField/iconField";
import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { createEditor } from "../../utils/editor";
import { getBySlugEndpoint } from "./endpoints/getBySlugEndpoint";
import { getRootFoldersEndpoint } from "./endpoints/rootEndpoint";
const fields = {
slug: "slug",
translations: "translations",
translationsName: "name",
translationsDescription: "description",
sections: "sections",
sectionsSubfolders: "subfolders",
sectionsName: "name",
sectionsTranslations: "translations",
sectionsTranslationsName: "name",
files: "files",
darkThumbnail: "darkThumbnail",
lightThumbnail: "lightThumbnail",
icon: "icon",
} as const satisfies Record<string, string>;
export const Folders = buildCollectionConfig({
@ -21,37 +31,53 @@ export const Folders = buildCollectionConfig({
useAsTitle: fields.slug,
group: CollectionGroups.Collections,
},
endpoints: [getRootFoldersEndpoint, getBySlugEndpoint],
fields: [
slugField({ name: fields.slug }),
rowField([slugField({ name: fields.slug }), iconField({ name: fields.icon })]),
rowField([
imageField({ name: fields.lightThumbnail, relationTo: Collections.FoldersThumbnails }),
imageField({ name: fields.darkThumbnail, relationTo: Collections.FoldersThumbnails }),
]),
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.translationsName },
fields: [
{
name: fields.translationsName,
type: "text",
required: true,
},
{
name: fields.translationsDescription,
type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }),
},
],
}),
{
name: "sections",
type: "array",
fields: [
rowField([
{
name: fields.sectionsName,
type: "text",
translatedFields({
name: fields.sectionsTranslations,
admin: {
useAsTitle: fields.sectionsTranslationsName,
condition: (data) => data[fields.sections]?.length > 1,
},
fields: [
{
name: fields.sectionsTranslationsName,
type: "text",
required: true,
},
],
}),
{
name: fields.sectionsSubfolders,
type: "relationship",
relationTo: Collections.Folders,
hasMany: true,
},
]),
],
},
{

View File

@ -0,0 +1,96 @@
import { Collections } from "../../../constants";
import { createGetByEndpoint } from "../../../endpoints/createGetByEndpoint";
import { EndpointFolder, EndpointFolderPreview, PayloadImage } from "../../../sdk";
import { Folder, FoldersThumbnail, Language } from "../../../types/collections";
import { isDefined, isUndefined, isValidPayloadImage } from "../../../utils/asserts";
export const getBySlugEndpoint = createGetByEndpoint(
Collections.Folders,
"slug",
(folder: Folder): EndpointFolder => {
return {
...convertFolderToPreview(folder),
sections:
folder.sections?.length === 1
? {
type: "single",
subfolders:
folder.sections[0]?.subfolders?.filter(isValidFolder).map(convertFolderToPreview) ??
[],
}
: {
type: "multiple",
sections:
folder.sections?.filter(isValidSection).map(({ translations, subfolders }) => ({
translations: translations.map(({ language, name }) => ({
language: getLanguageId(language),
name,
})),
subfolders: subfolders.map(convertFolderToPreview),
})) ?? [],
},
};
}
);
export const convertFolderToPreview = ({
slug,
darkThumbnail,
lightThumbnail,
translations,
icon,
}: Folder): EndpointFolderPreview => {
return {
slug,
...(isDefined(icon) ? { icon } : {}),
translations:
translations?.map(({ language, name, description }) => ({
language: getLanguageId(language),
name,
description: JSON.stringify(description),
})) ?? [],
darkThumbnail: getThumbnail(darkThumbnail),
lightThumbnail: getThumbnail(lightThumbnail),
};
};
const isValidSection = (section: {
translations?:
| {
language: string | Language;
name: string;
id?: string | null;
}[]
| null;
subfolders?: (string | Folder)[] | null | undefined;
}): section is {
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
subfolders: Folder[];
} => {
if (!section.translations) {
return false;
}
if (!section.subfolders) {
return false;
}
return section.subfolders.every(isValidFolder);
};
export const isValidFolder = (folder: string | Folder): folder is Folder =>
typeof folder !== "string";
const getThumbnail = (
thumbnail: string | FoldersThumbnail | null | undefined
): PayloadImage | undefined => {
if (isUndefined(thumbnail)) return undefined;
if (typeof thumbnail === "string") return undefined;
if (!isValidPayloadImage(thumbnail)) return undefined;
return thumbnail;
};
const getLanguageId = (language: string | Language) =>
typeof language === "object" ? language.id : language;

View File

@ -0,0 +1,46 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { EndpointFolderPreview } from "../../../sdk";
import { CollectionEndpoint } from "../../../types/payload";
import { convertFolderToPreview, isValidFolder } from "./getBySlugEndpoint";
export const getRootFoldersEndpoint: CollectionEndpoint = {
method: "get",
path: "/root",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const homeFolder = (
await payload.find({
collection: Collections.Folders,
limit: 100,
where: { slug: { equals: "home" } },
})
).docs[0];
if (!homeFolder) {
res.status(404);
return;
}
const folders = homeFolder.sections?.[0]?.subfolders;
if (!folders) {
res.status(404);
return;
}
const result = folders.filter(isValidFolder).map<EndpointFolderPreview>(convertFolderToPreview);
res.status(200).json(result);
},
};

View File

@ -0,0 +1,33 @@
import { Collections } from "../../constants";
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
const fields = {
filename: "filename",
mimeType: "mimeType",
filesize: "filesize",
updatedAt: "updatedAt",
} as const satisfies Record<string, string>;
export const FoldersThumbnails = buildImageCollectionConfig({
slug: Collections.FoldersThumbnails,
labels: {
singular: "Folders Thumbnail",
plural: "Folders Thumbnails",
},
admin: { defaultColumns: [fields.filename, fields.updatedAt] },
upload: {
imageSizes: [
{
name: "medium",
height: 400,
width: 200,
fit: "contain",
formatOptions: {
format: "webp",
options: { effort: 6, quality: 80, alphaQuality: 80 },
},
},
],
},
fields: [],
});

View File

@ -3,6 +3,7 @@ import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { publicAccess } from "../../accesses/publicAccess";
import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
import { getAllEndpoint } from "./endpoints/getAllEndpoint";
import { importFromStrapi } from "./endpoints/importFromStrapi";
const fields = {
@ -26,7 +27,7 @@ export const Languages = buildCollectionConfig({
},
access: { create: mustBeAdmin, update: mustBeAdmin, read: publicAccess },
timestamps: false,
endpoints: [importFromStrapi],
endpoints: [importFromStrapi, getAllEndpoint],
fields: [
{
name: fields.id,

View File

@ -0,0 +1,30 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { Language } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
export const getAllEndpoint: CollectionEndpoint = {
method: "get",
path: "/all",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const languages: Language[] = (
await payload.find({
collection: Collections.Languages,
limit: 100,
sort: "name"
})
).docs;
res.status(200).json(languages);
},
};

View File

@ -22,7 +22,8 @@ export enum Collections {
Weapons = "weapons",
WeaponsGroups = "weapons-groups",
WeaponsThumbnails = "weapons-thumbnails",
Folders = "folders"
Folders = "folders",
FoldersThumbnails = "FoldersThumbnails",
}
export enum CollectionGroups {

View File

@ -0,0 +1,8 @@
import { TextField } from "payload/types";
type Props = Omit<TextField, "type" | "hasMany" | "maxRows" | "minRows">;
export const iconField = (props: Props): TextField => ({
...props,
type: "text",
});

View File

@ -10,6 +10,7 @@ import { ContentsThumbnails } from "./collections/ContentsThumbnails/ContentsThu
import { Currencies } from "./collections/Currencies/Currencies";
import { Files } from "./collections/Files/Files";
import { Folders } from "./collections/Folders/Folders";
import { FoldersThumbnails } from "./collections/FoldersThumbnails/FoldersThumbnails";
import { Keys } from "./collections/Keys/Keys";
import { Languages } from "./collections/Languages/Languages";
import { LibraryItems } from "./collections/LibraryItems/LibraryItems";
@ -47,6 +48,7 @@ export default buildConfig({
editor: createEditor({}),
collections: [
Folders,
FoldersThumbnails,
LibraryItems,
Contents,
ContentsFolders,

View File

@ -1,4 +1,5 @@
import { Collections } from "./constants";
import { Currency, Language } from "./types/collections";
class NodeCache {
constructor(_params: any) {}
@ -158,6 +159,39 @@ export type EndpointEra = {
}[];
};
export type EndpointFolder = {
slug: string;
icon?: string;
translations: {
language: string;
name: string;
description?: string;
}[];
sections:
| { type: "single"; subfolders: EndpointFolderPreview[] }
| {
type: "multiple";
sections: {
translations: { language: string; name: string }[];
subfolders: EndpointFolderPreview[];
}[];
};
lightThumbnail?: PayloadImage;
darkThumbnail?: PayloadImage;
};
export type EndpointFolderPreview = {
slug: string;
icon?: string;
translations: {
language: string;
name: string;
description?: string;
}[];
lightThumbnail?: PayloadImage;
darkThumbnail?: PayloadImage;
};
export type PayloadImage = {
url: string;
width: number;
@ -171,4 +205,12 @@ export const payload = {
await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(),
getEras: async (): Promise<EndpointEra[]> =>
await (await request(payloadApiUrl(Collections.ChronologyEras, `all`))).json(),
getRootFolders: async (): Promise<EndpointFolderPreview[]> =>
await (await request(payloadApiUrl(Collections.Folders, `root`))).json(),
getFolder: async (slug: string): Promise<EndpointFolder> =>
await (await request(payloadApiUrl(Collections.Folders, `slug/${slug}`))).json(),
getLanguages: async (): Promise<Language[]> =>
await (await request(payloadApiUrl(Collections.Languages, `all`))).json(),
getCurrencies: async (): Promise<Currency[]> =>
await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(),
};

View File

@ -39,6 +39,7 @@ export type CategoryTranslations =
export interface Config {
collections: {
folders: Folder;
FoldersThumbnails: FoldersThumbnail;
'library-items': LibraryItem;
contents: Content;
'contents-folders': ContentsFolder;
@ -70,6 +71,33 @@ export interface Config {
export interface Folder {
id: string;
slug: string;
icon?: string | null;
lightThumbnail?: string | FoldersThumbnail | null;
darkThumbnail?: string | FoldersThumbnail | null;
translations?:
| {
language: string | Language;
name: string;
description?: {
root: {
children: {
type: string;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
type: string;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[]
| null;
sections?:
| {
translations?:
| {
language: string | Language;
@ -77,9 +105,6 @@ export interface Folder {
id?: string | null;
}[]
| null;
sections?:
| {
name?: string | null;
subfolders?: (string | Folder)[] | null;
id?: string | null;
}[]
@ -99,6 +124,35 @@ export interface Folder {
updatedAt: string;
createdAt: string;
}
export interface FoldersThumbnail {
id: string;
updatedAt: string;
createdAt: string;
url?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
sizes?: {
thumb?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
medium?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
export interface Language {
id: string;
name: string;

View File

@ -24,7 +24,15 @@ export const hasIntersection = (a: Span, b: Span): boolean => !hasNoIntersection
export const hasDuplicates = <T>(list: T[]): boolean => list.length !== new Set(list).size;
export const isValidPayloadImage = (
image: Partial<PayloadImage> | undefined
image:
| {
filename?: string | null;
mimeType?: string | null;
width?: number | null;
height?: number | null;
url?: string | null;
}
| undefined | null
): image is PayloadImage => {
if (isUndefined(image)) return false;
if (isEmpty(image.filename)) return false;