From b91159e61f47a127eb2399e9f1dadf1f8cfc0e9d Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:52:23 +0100 Subject: [PATCH] Updated payload + folders --- package-lock.json | 24 ++--- package.json | 6 +- src/collections/Currencies/Currencies.ts | 3 +- .../Currencies/endpoints/getAllEndpoint.ts | 30 ++++++ src/collections/Folders/Folders.ts | 58 +++++++---- .../Folders/endpoints/getBySlugEndpoint.ts | 96 +++++++++++++++++++ .../Folders/endpoints/rootEndpoint.ts | 46 +++++++++ .../FoldersThumbnails/FoldersThumbnails.ts | 33 +++++++ src/collections/Languages/Languages.ts | 3 +- .../Languages/endpoints/getAllEndpoint.ts | 30 ++++++ src/constants.ts | 3 +- src/fields/iconField/iconField.ts | 8 ++ src/payload.config.ts | 2 + src/sdk.ts | 42 ++++++++ src/types/collections.ts | 56 ++++++++++- src/utils/asserts.ts | 10 +- 16 files changed, 414 insertions(+), 36 deletions(-) create mode 100644 src/collections/Currencies/endpoints/getAllEndpoint.ts create mode 100644 src/collections/Folders/endpoints/getBySlugEndpoint.ts create mode 100644 src/collections/Folders/endpoints/rootEndpoint.ts create mode 100644 src/collections/FoldersThumbnails/FoldersThumbnails.ts create mode 100644 src/collections/Languages/endpoints/getAllEndpoint.ts create mode 100644 src/fields/iconField/iconField.ts diff --git a/package-lock.json b/package-lock.json index d5b5acc..75c0567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8f0a707..f28cc9f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/collections/Currencies/Currencies.ts b/src/collections/Currencies/Currencies.ts index 12db66e..87cae7b 100644 --- a/src/collections/Currencies/Currencies.ts +++ b/src/collections/Currencies/Currencies.ts @@ -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: [ { diff --git a/src/collections/Currencies/endpoints/getAllEndpoint.ts b/src/collections/Currencies/endpoints/getAllEndpoint.ts new file mode 100644 index 0000000..d1981e0 --- /dev/null +++ b/src/collections/Currencies/endpoints/getAllEndpoint.ts @@ -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); + }, +}; diff --git a/src/collections/Folders/Folders.ts b/src/collections/Folders/Folders.ts index 834bc66..d1999c5 100644 --- a/src/collections/Folders/Folders.ts +++ b/src/collections/Folders/Folders.ts @@ -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; 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", - admin: { - condition: (data) => data[fields.sections]?.length > 1, + 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, - }, - ]), + ], + }), + { + name: fields.sectionsSubfolders, + type: "relationship", + relationTo: Collections.Folders, + hasMany: true, + }, ], }, { diff --git a/src/collections/Folders/endpoints/getBySlugEndpoint.ts b/src/collections/Folders/endpoints/getBySlugEndpoint.ts new file mode 100644 index 0000000..8cb686a --- /dev/null +++ b/src/collections/Folders/endpoints/getBySlugEndpoint.ts @@ -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; diff --git a/src/collections/Folders/endpoints/rootEndpoint.ts b/src/collections/Folders/endpoints/rootEndpoint.ts new file mode 100644 index 0000000..158fd4b --- /dev/null +++ b/src/collections/Folders/endpoints/rootEndpoint.ts @@ -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(convertFolderToPreview); + + res.status(200).json(result); + }, +}; diff --git a/src/collections/FoldersThumbnails/FoldersThumbnails.ts b/src/collections/FoldersThumbnails/FoldersThumbnails.ts new file mode 100644 index 0000000..3eab1db --- /dev/null +++ b/src/collections/FoldersThumbnails/FoldersThumbnails.ts @@ -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; + +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: [], +}); diff --git a/src/collections/Languages/Languages.ts b/src/collections/Languages/Languages.ts index 7371120..d8e3b59 100644 --- a/src/collections/Languages/Languages.ts +++ b/src/collections/Languages/Languages.ts @@ -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, diff --git a/src/collections/Languages/endpoints/getAllEndpoint.ts b/src/collections/Languages/endpoints/getAllEndpoint.ts new file mode 100644 index 0000000..015177a --- /dev/null +++ b/src/collections/Languages/endpoints/getAllEndpoint.ts @@ -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); + }, +}; diff --git a/src/constants.ts b/src/constants.ts index 320c816..928b9a5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -22,7 +22,8 @@ export enum Collections { Weapons = "weapons", WeaponsGroups = "weapons-groups", WeaponsThumbnails = "weapons-thumbnails", - Folders = "folders" + Folders = "folders", + FoldersThumbnails = "FoldersThumbnails", } export enum CollectionGroups { diff --git a/src/fields/iconField/iconField.ts b/src/fields/iconField/iconField.ts new file mode 100644 index 0000000..851e0c8 --- /dev/null +++ b/src/fields/iconField/iconField.ts @@ -0,0 +1,8 @@ +import { TextField } from "payload/types"; + +type Props = Omit; + +export const iconField = (props: Props): TextField => ({ + ...props, + type: "text", +}); diff --git a/src/payload.config.ts b/src/payload.config.ts index bbb7c7e..7ee17c9 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -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, diff --git a/src/sdk.ts b/src/sdk.ts index 87dec67..ead0f69 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -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 => await (await request(payloadApiUrl(Collections.ChronologyEras, `all`))).json(), + getRootFolders: async (): Promise => + await (await request(payloadApiUrl(Collections.Folders, `root`))).json(), + getFolder: async (slug: string): Promise => + await (await request(payloadApiUrl(Collections.Folders, `slug/${slug}`))).json(), + getLanguages: async (): Promise => + await (await request(payloadApiUrl(Collections.Languages, `all`))).json(), + getCurrencies: async (): Promise => + await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(), }; diff --git a/src/types/collections.ts b/src/types/collections.ts index 940ad89..8d6efc1 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -39,6 +39,7 @@ export type CategoryTranslations = export interface Config { collections: { folders: Folder; + FoldersThumbnails: FoldersThumbnail; 'library-items': LibraryItem; contents: Content; 'contents-folders': ContentsFolder; @@ -70,16 +71,40 @@ 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?: | { - name?: string | null; + translations?: + | { + language: string | Language; + name: string; + id?: string | null; + }[] + | 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; diff --git a/src/utils/asserts.ts b/src/utils/asserts.ts index 53e2d13..9a4d497 100644 --- a/src/utils/asserts.ts +++ b/src/utils/asserts.ts @@ -24,7 +24,15 @@ export const hasIntersection = (a: Span, b: Span): boolean => !hasNoIntersection export const hasDuplicates = (list: T[]): boolean => list.length !== new Set(list).size; export const isValidPayloadImage = ( - image: Partial | 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;