From 6074ccceff141d95632f4bd5bcc9fdcedc194d4a Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Sat, 19 Aug 2023 21:11:22 +0200 Subject: [PATCH] Lots of things, again, again --- src/accesses/endpoints/mustBeAdmin.ts | 9 + src/accesses/endpoints/mustBeApi.ts | 9 + .../endpoints/mustHaveAtLeastOneRole.ts | 8 + .../ChronologyItems/ChronologyItems.ts | 29 +-- .../hooks/beforeValidatePopulateNameField.ts | 11 +- .../validations/validateDate.ts | 16 ++ .../validateEventsTranslationsDescription.ts | 15 ++ .../validateEventsTranslationsTitle.ts | 15 ++ src/collections/Contents/Contents.ts | 6 +- .../ContentsThumbnails/ContentsThumbnails.ts | 28 ++- src/collections/Files/Files.ts | 12 ++ .../hooks/beforeValidateCheckFileExists.ts | 45 +++++ src/collections/LibraryItems/LibraryItems.ts | 30 ++- .../LibraryItems/components/RowLabel.tsx | 1 - .../LibraryItemsGallery.ts | 10 +- .../LibraryItemsScans/LibraryItemsScans.ts | 10 +- .../LibraryItemsThumbnails.ts | 21 +- src/collections/Posts/Posts.ts | 2 +- .../PostsThumbnails/PostsThumbnails.ts | 21 +- src/collections/Recorders/Recorders.ts | 2 +- .../Recorders/endpoints/importFromStrapi.ts | 27 ++- .../RecordersThumbnails.ts | 26 +-- src/collections/Videos/Videos.ts | 1 - .../Videos/endpoints/importFromStrapi.ts | 49 ++--- .../VideosChannels/VideosChannels.ts | 2 - .../endpoints/importFromStrapi.ts | 24 --- src/collections/Weapons/Weapons.ts | 10 +- .../WeaponsThumbnails/WeaponsThumbnails.ts | 21 +- .../UploadsGridView/Grid/index.scss | 69 +++++++ src/components/UploadsGridView/Grid/index.tsx | 100 ++++++++++ .../UploadsGridView/UploadsGridView.tsx | 185 ++++++++++++++++++ src/constants.ts | 1 + ...teByEndpoint.ts => createGetByEndpoint.ts} | 2 +- src/endpoints/createGetSlugsEndpoint.ts | 39 ++++ .../createImageRegenerationEndpoint.ts | 66 +++++++ .../backPropagationField.ts | 2 +- src/fields/fileField/fileField.ts | 21 +- src/fields/keysField/keysField.ts | 2 +- src/types/collections.ts | 45 +++-- src/types/payload.ts | 4 +- src/utils/imageCollectionConfig.ts | 13 ++ tsconfig.json | 4 +- 42 files changed, 826 insertions(+), 187 deletions(-) create mode 100644 src/accesses/endpoints/mustBeAdmin.ts create mode 100644 src/accesses/endpoints/mustBeApi.ts create mode 100644 src/accesses/endpoints/mustHaveAtLeastOneRole.ts create mode 100644 src/collections/ChronologyItems/validations/validateDate.ts create mode 100644 src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts create mode 100644 src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts create mode 100644 src/collections/Files/hooks/beforeValidateCheckFileExists.ts delete mode 100644 src/collections/VideosChannels/endpoints/importFromStrapi.ts create mode 100644 src/components/UploadsGridView/Grid/index.scss create mode 100644 src/components/UploadsGridView/Grid/index.tsx create mode 100644 src/components/UploadsGridView/UploadsGridView.tsx rename src/endpoints/{createByEndpoint.ts => createGetByEndpoint.ts} (91%) create mode 100644 src/endpoints/createGetSlugsEndpoint.ts create mode 100644 src/endpoints/createImageRegenerationEndpoint.ts diff --git a/src/accesses/endpoints/mustBeAdmin.ts b/src/accesses/endpoints/mustBeAdmin.ts new file mode 100644 index 0000000..f5fbbb7 --- /dev/null +++ b/src/accesses/endpoints/mustBeAdmin.ts @@ -0,0 +1,9 @@ +import { RecordersRoles } from "../../constants"; +import { Recorder } from "../../types/collections"; +import { EndpointAccess } from "../../types/payload"; +import { isDefined, isUndefined } from "../../utils/asserts"; + +export const mustBeAdmin: EndpointAccess = ({ user }) => { + if (isUndefined(user)) return false; + return isDefined(user.role) && user.role.includes(RecordersRoles.Admin); +}; diff --git a/src/accesses/endpoints/mustBeApi.ts b/src/accesses/endpoints/mustBeApi.ts new file mode 100644 index 0000000..79678d8 --- /dev/null +++ b/src/accesses/endpoints/mustBeApi.ts @@ -0,0 +1,9 @@ +import { RecordersRoles } from "../../constants"; +import { Recorder } from "../../types/collections"; +import { EndpointAccess } from "../../types/payload"; +import { isDefined, isUndefined } from "../../utils/asserts"; + +export const mustBeApi: EndpointAccess = ({ user }) => { + if (isUndefined(user)) return false; + return isDefined(user.role) && user.role.includes(RecordersRoles.Api); +}; diff --git a/src/accesses/endpoints/mustHaveAtLeastOneRole.ts b/src/accesses/endpoints/mustHaveAtLeastOneRole.ts new file mode 100644 index 0000000..9e87622 --- /dev/null +++ b/src/accesses/endpoints/mustHaveAtLeastOneRole.ts @@ -0,0 +1,8 @@ +import { Recorder } from "../../types/collections"; +import { EndpointAccess } from "../../types/payload"; +import { isDefined, isUndefined } from "../../utils/asserts"; + +export const mustHaveAtLeastOneRole: EndpointAccess = ({ user }) => { + if (isUndefined(user)) return false; + return isDefined(user.role) && user.role.length > 0; +}; diff --git a/src/collections/ChronologyItems/ChronologyItems.ts b/src/collections/ChronologyItems/ChronologyItems.ts index b4f7f4f..dcff0d4 100644 --- a/src/collections/ChronologyItems/ChronologyItems.ts +++ b/src/collections/ChronologyItems/ChronologyItems.ts @@ -1,4 +1,3 @@ -import { DateTime } from "luxon"; import { CollectionConfig } from "payload/types"; import { QuickFilters, @@ -7,10 +6,12 @@ import { } from "../../components/QuickFilters"; import { CollectionGroups, Collections } from "../../constants"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; -import { isEmpty, isUndefined } from "../../utils/asserts"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; import { beforeValidatePopulateNameField } from "./hooks/beforeValidatePopulateNameField"; +import { validateDate } from "./validations/validateDate"; +import { validateEventsTranslationsDescription } from "./validations/validateEventsTranslationsDescription"; +import { validateEventsTranslationsTitle } from "./validations/validateEventsTranslationsTitle"; const fields = { name: "name", @@ -65,15 +66,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig( { type: "group", name: fields.date, - validate: ({ year, month, day } = {}) => { - if (isUndefined(day)) return true; - if (isUndefined(month)) return "A month is required if a day is set"; - const stringDate = `${year}/${month}/${day}`; - if (!DateTime.fromObject({ year, month, day }).isValid) { - return `The given date (${stringDate}) is not a valid date.`; - } - return true; - }, + validate: validateDate, fields: [ { type: "row", @@ -116,22 +109,12 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig( fields: [ { name: fields.eventsTranslationsTitle, - validate: (_, { siblingData: { description, title } }) => { - if (isEmpty(description) && isEmpty(title)) { - return "This field is required if no description is set."; - } - return true; - }, + validate: validateEventsTranslationsTitle, type: "text", }, { name: fields.eventsTranslationsDescription, - validate: (_, { siblingData: { description, title } }) => { - if (isEmpty(description) && isEmpty(title)) { - return "This field is required if no title is set."; - } - return true; - }, + validate: validateEventsTranslationsDescription, type: "textarea", }, { name: fields.eventsTranslationsNotes, type: "textarea" }, diff --git a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts b/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts index e638019..164d159 100644 --- a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts +++ b/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts @@ -7,17 +7,14 @@ export const beforeValidatePopulateNameField: FieldHook< ChronologyItem["name"], ChronologyItem > = ({ data }) => { - if (isUndefined(data)) { + if (isUndefined(data) || isUndefined(data.date) || isUndefined(data.date.year)) return "????-??-??"; - } - const { date } = data; - if (isUndefined(date) || isUndefined(date?.year)) return "????-??-??"; - const { year, month, day } = date; + const { year, month, day } = data.date; let result = String(year).padStart(5, " "); if (isDefined(month)) { - result += `-${String(date.month).padStart(2, "0")}`; + result += `-${String(month).padStart(2, "0")}`; if (isDefined(day)) { - result += `-${String(date.day).padStart(2, "0")}`; + result += `-${String(day).padStart(2, "0")}`; } } return result; diff --git a/src/collections/ChronologyItems/validations/validateDate.ts b/src/collections/ChronologyItems/validations/validateDate.ts new file mode 100644 index 0000000..1f2798f --- /dev/null +++ b/src/collections/ChronologyItems/validations/validateDate.ts @@ -0,0 +1,16 @@ +import { DateTime } from "luxon"; +import { Validate } from "payload/types"; +import { ChronologyItem } from "../../../types/collections"; +import { isUndefined } from "../../../utils/asserts"; + +export const validateDate: Validate = (date) => { + if (isUndefined(date)) return "This field is required."; + const { year, month, day } = date; + if (isUndefined(day)) return true; + if (isUndefined(month)) return "A month is required if a day is set."; + const stringDate = `${year}/${month}/${day}`; + if (!DateTime.fromObject({ year, month, day }).isValid) { + return `The given date (${stringDate}) is not a valid date.`; + } + return true; +}; diff --git a/src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts b/src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts new file mode 100644 index 0000000..b50daf4 --- /dev/null +++ b/src/collections/ChronologyItems/validations/validateEventsTranslationsDescription.ts @@ -0,0 +1,15 @@ +import { Validate } from "payload/types"; +import { ChronologyItem } from "../../../types/collections"; +import { isEmpty } from "../../../utils/asserts"; + +export const validateEventsTranslationsDescription: Validate< + string | undefined, + ChronologyItem, + ChronologyItem["events"][number]["translations"][number], + unknown +> = (_, { siblingData: { description, title } }) => { + if (isEmpty(description) && isEmpty(title)) { + return "This field is required if no title is set."; + } + return true; +}; diff --git a/src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts b/src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts new file mode 100644 index 0000000..571f568 --- /dev/null +++ b/src/collections/ChronologyItems/validations/validateEventsTranslationsTitle.ts @@ -0,0 +1,15 @@ +import { Validate } from "payload/types"; +import { ChronologyItem } from "../../../types/collections"; +import { isEmpty } from "../../../utils/asserts"; + +export const validateEventsTranslationsTitle: Validate< + string | undefined, + ChronologyItem, + ChronologyItem["events"][number]["translations"][number], + unknown +> = (_, { siblingData: { description, title } }) => { + if (isEmpty(description) && isEmpty(title)) { + return "This field is required if no description is set."; + } + return true; +}; diff --git a/src/collections/Contents/Contents.ts b/src/collections/Contents/Contents.ts index 789ec1e..b5c5a4c 100644 --- a/src/collections/Contents/Contents.ts +++ b/src/collections/Contents/Contents.ts @@ -46,8 +46,8 @@ export const Contents = buildVersionedCollectionConfig({ description: "All the contents (textual, audio, and video) from the Library or other online sources.", defaultColumns: [ - fields.slug, fields.thumbnail, + fields.slug, fields.categories, fields.type, fields.translations, @@ -175,7 +175,7 @@ export const Contents = buildVersionedCollectionConfig({ fields: [ fileField({ name: fields.video, - filterOptions: { type: { equals: FileTypes.ContentVideo } }, + relationTo: FileTypes.ContentVideo, admin: { width: "50%" }, }), { @@ -196,7 +196,7 @@ export const Contents = buildVersionedCollectionConfig({ fields: [ fileField({ name: fields.audio, - filterOptions: { type: { equals: FileTypes.ContentAudio } }, + relationTo: FileTypes.ContentAudio, admin: { width: "50%" }, }), { diff --git a/src/collections/ContentsThumbnails/ContentsThumbnails.ts b/src/collections/ContentsThumbnails/ContentsThumbnails.ts index a6441d8..ecc8431 100644 --- a/src/collections/ContentsThumbnails/ContentsThumbnails.ts +++ b/src/collections/ContentsThumbnails/ContentsThumbnails.ts @@ -1,10 +1,13 @@ -import { CollectionGroups, Collections } from "../../constants"; +import { Collections } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig"; const fields = { filename: "filename", mimeType: "mimeType", filesize: "filesize", + contents: "contents", + updatedAt: "updatedAt", } as const satisfies Record; export const ContentsThumbnails = buildImageCollectionConfig({ @@ -13,12 +16,7 @@ export const ContentsThumbnails = buildImageCollectionConfig({ singular: "Contents Thumbnail", plural: "Contents Thumbnails", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.contents, fields.updatedAt] }, upload: { imageSizes: [ { @@ -39,7 +37,21 @@ export const ContentsThumbnails = buildImageCollectionConfig({ options: { effort: 6, quality: 80, alphaQuality: 80 }, }, }, + { + name: "max", + formatOptions: { + format: "webp", + options: { effort: 6, quality: 80, alphaQuality: 80 }, + }, + }, ], }, - fields: [], + fields: [ + backPropagationField({ + name: fields.contents, + hasMany: true, + relationTo: Collections.Contents, + where: ({ id }) => ({ thumbnail: { equals: id } }), + }), + ], }); diff --git a/src/collections/Files/Files.ts b/src/collections/Files/Files.ts index 3b855bb..8404b00 100644 --- a/src/collections/Files/Files.ts +++ b/src/collections/Files/Files.ts @@ -1,5 +1,10 @@ import { CollectionGroups, Collections, FileTypes } from "../../constants"; +import { File } from "../../types/collections"; import { buildCollectionConfig } from "../../utils/collectionConfig"; +import { + beforeValidateCheckFileExists, + generatePathForFile, +} from "./hooks/beforeValidateCheckFileExists"; const fields = { filename: "filename", @@ -17,6 +22,13 @@ export const Files = buildCollectionConfig({ useAsTitle: fields.filename, disableDuplicate: true, group: CollectionGroups.Media, + preview: (doc) => { + const { filename, type } = doc as unknown as File; + return generatePathForFile(type, filename); + }, + }, + hooks: { + beforeValidate: [beforeValidateCheckFileExists], }, fields: [ { diff --git a/src/collections/Files/hooks/beforeValidateCheckFileExists.ts b/src/collections/Files/hooks/beforeValidateCheckFileExists.ts new file mode 100644 index 0000000..c848e90 --- /dev/null +++ b/src/collections/Files/hooks/beforeValidateCheckFileExists.ts @@ -0,0 +1,45 @@ +import { CollectionBeforeValidateHook } from "payload/types"; +import { FileTypes } from "../../../constants"; +import { File } from "../../../types/collections"; +import { isUndefined } from "../../../utils/asserts"; + +const reshareSubFolderFromType: Record = { + ContentAudio: "/contents/audios", + ContentVideo: "/contents/videos", + LibraryScans: "/library/scans", + LibrarySoundtracks: "/library/tracks", +}; + +const expectedMimeFromType = { + ContentAudio: "audio/", + ContentVideo: "video/", + LibraryScans: "application/zip", + LibrarySoundtracks: "audio/", +}; + +export const generatePathForFile = (type: keyof typeof FileTypes, filename: string) => + `https://resha.re/accords${reshareSubFolderFromType[type]}/${filename}`; + +export const beforeValidateCheckFileExists: CollectionBeforeValidateHook = async ({ + data, +}) => { + if (isUndefined(data)) throw new Error("The data is undefined"); + const { type, filename } = data; + if (isUndefined(filename)) throw new Error("Filename is undefined"); + if (isUndefined(type)) throw new Error("Filename is undefined"); + + const url = generatePathForFile(type, filename); + + const result = await fetch(url, { method: "HEAD" }); + + if (result.status !== 200) { + throw new Error(`Unable to locate the file at the following address: ${url}`); + } + + const contentType = result.headers.get("content-type"); + if (isUndefined(contentType) || !contentType.startsWith(expectedMimeFromType[type])) { + throw new Error( + `Wrong MIME type found: ${contentType}. The expected MIME type was ${expectedMimeFromType[type]}` + ); + } +}; diff --git a/src/collections/LibraryItems/LibraryItems.ts b/src/collections/LibraryItems/LibraryItems.ts index e73024c..7fb38c1 100644 --- a/src/collections/LibraryItems/LibraryItems.ts +++ b/src/collections/LibraryItems/LibraryItems.ts @@ -2,11 +2,13 @@ import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types import { CollectionGroups, Collections, + FileTypes, KeysTypes, LibraryItemsTextualBindingTypes, LibraryItemsTextualPageOrders, LibraryItemsTypes, } from "../../constants"; +import { fileField } from "../../fields/fileField/fileField"; import { imageField } from "../../fields/imageField/imageField"; import { keysField } from "../../fields/keysField/keysField"; import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField"; @@ -66,6 +68,9 @@ const fields = { audio: "audio", audioSubtype: "audioSubtype", + audioTracks: "tracks", + audioTracksFile: "file", + audioTracksTitle: "title", scans: "scans", @@ -129,7 +134,7 @@ export const LibraryItems = buildVersionedCollectionConfig({ description: "A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, \ stage plays, manga, drama CDs, and comics).", - defaultColumns: [fields.slug, fields.thumbnail, fields.status], + defaultColumns: [fields.thumbnail, fields.slug, fields.status], group: CollectionGroups.Collections, hooks: { beforeDuplicate: beforeDuplicatePiping([ @@ -596,6 +601,29 @@ export const LibraryItems = buildVersionedCollectionConfig({ }), ], }, + { + name: fields.audioTracks, + type: "array", + fields: [ + { + type: "row", + fields: [ + { + name: fields.audioTracksTitle, + type: "text", + required: true, + admin: { width: "50%" }, + }, + fileField({ + name: fields.audioTracksFile, + relationTo: FileTypes.LibrarySoundtracks, + required: true, + admin: { width: "50%" }, + }), + ], + }, + ], + }, ], }, ], diff --git a/src/collections/LibraryItems/components/RowLabel.tsx b/src/collections/LibraryItems/components/RowLabel.tsx index 95782cf..9da31a0 100644 --- a/src/collections/LibraryItems/components/RowLabel.tsx +++ b/src/collections/LibraryItems/components/RowLabel.tsx @@ -1,7 +1,6 @@ import React from "react"; import { styled } from "styled-components"; import { isDefined } from "../../../utils/asserts"; -import { formatLanguageCode, shortenEllipsis } from "../../../utils/string"; interface Props { page?: number; diff --git a/src/collections/LibraryItemsGallery/LibraryItemsGallery.ts b/src/collections/LibraryItemsGallery/LibraryItemsGallery.ts index 500e554..5c9c41d 100644 --- a/src/collections/LibraryItemsGallery/LibraryItemsGallery.ts +++ b/src/collections/LibraryItemsGallery/LibraryItemsGallery.ts @@ -1,10 +1,11 @@ -import { CollectionGroups, Collections } from "../../constants"; +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 LibraryItemsGallery = buildImageCollectionConfig({ @@ -13,12 +14,7 @@ export const LibraryItemsGallery = buildImageCollectionConfig({ singular: "Library Item Gallery", plural: "Library Item Gallery", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.updatedAt] }, upload: { imageSizes: [ { diff --git a/src/collections/LibraryItemsScans/LibraryItemsScans.ts b/src/collections/LibraryItemsScans/LibraryItemsScans.ts index af019ec..99d3c84 100644 --- a/src/collections/LibraryItemsScans/LibraryItemsScans.ts +++ b/src/collections/LibraryItemsScans/LibraryItemsScans.ts @@ -1,10 +1,11 @@ -import { CollectionGroups, Collections } from "../../constants"; +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 LibraryItemsScans = buildImageCollectionConfig({ @@ -13,12 +14,7 @@ export const LibraryItemsScans = buildImageCollectionConfig({ singular: "Library Item Scans", plural: "Library Item Scans", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.updatedAt] }, upload: { imageSizes: [ { diff --git a/src/collections/LibraryItemsThumbnails/LibraryItemsThumbnails.ts b/src/collections/LibraryItemsThumbnails/LibraryItemsThumbnails.ts index 8b2758e..f7c66b2 100644 --- a/src/collections/LibraryItemsThumbnails/LibraryItemsThumbnails.ts +++ b/src/collections/LibraryItemsThumbnails/LibraryItemsThumbnails.ts @@ -1,10 +1,13 @@ -import { CollectionGroups, Collections } from "../../constants"; +import { Collections } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig"; const fields = { filename: "filename", mimeType: "mimeType", filesize: "filesize", + libraryItem: "libraryItem", + updatedAt: "updatedAt", } as const satisfies Record; export const LibraryItemsThumbnails = buildImageCollectionConfig({ @@ -13,12 +16,7 @@ export const LibraryItemsThumbnails = buildImageCollectionConfig({ singular: "Library Item Thumbnail", plural: "Library Item Thumbnails", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.libraryItem, fields.updatedAt] }, upload: { imageSizes: [ { @@ -51,5 +49,12 @@ export const LibraryItemsThumbnails = buildImageCollectionConfig({ }, ], }, - fields: [], + fields: [ + backPropagationField({ + name: fields.libraryItem, + hasMany: true, + relationTo: Collections.LibraryItems, + where: ({ id }) => ({ thumbnail: { equals: id } }), + }), + ], }); diff --git a/src/collections/Posts/Posts.ts b/src/collections/Posts/Posts.ts index 58e7c85..939656e 100644 --- a/src/collections/Posts/Posts.ts +++ b/src/collections/Posts/Posts.ts @@ -37,7 +37,7 @@ export const Posts = buildVersionedCollectionConfig({ description: "News articles written by our Recorders! Here you will find announcements about \ new merch/items releases, guides, theories, unboxings, showcases...", - defaultColumns: [fields.slug, fields.thumbnail, fields.categories], + defaultColumns: [fields.thumbnail, fields.slug, fields.categories], group: CollectionGroups.Collections, components: { BeforeListTable: [ diff --git a/src/collections/PostsThumbnails/PostsThumbnails.ts b/src/collections/PostsThumbnails/PostsThumbnails.ts index 91a9993..bf28670 100644 --- a/src/collections/PostsThumbnails/PostsThumbnails.ts +++ b/src/collections/PostsThumbnails/PostsThumbnails.ts @@ -1,10 +1,13 @@ -import { CollectionGroups, Collections } from "../../constants"; +import { Collections } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig"; const fields = { filename: "filename", mimeType: "mimeType", filesize: "filesize", + posts: "posts", + updatedAt: "updatedAt", } as const satisfies Record; export const PostsThumbnails = buildImageCollectionConfig({ @@ -13,12 +16,7 @@ export const PostsThumbnails = buildImageCollectionConfig({ singular: "Post Thumbnail", plural: "Post Thumbnails", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.posts, fields.updatedAt] }, upload: { imageSizes: [ { @@ -41,5 +39,12 @@ export const PostsThumbnails = buildImageCollectionConfig({ }, ], }, - fields: [], + fields: [ + backPropagationField({ + name: fields.posts, + hasMany: true, + relationTo: Collections.Posts, + where: ({ id }) => ({ thumbnail: { equals: id } }), + }), + ], }); diff --git a/src/collections/Recorders/Recorders.ts b/src/collections/Recorders/Recorders.ts index 9a08b9d..c54cff8 100644 --- a/src/collections/Recorders/Recorders.ts +++ b/src/collections/Recorders/Recorders.ts @@ -32,8 +32,8 @@ export const Recorders = buildCollectionConfig({ "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.", defaultColumns: [ - fields.username, fields.avatar, + fields.username, fields.anonymize, fields.biographies, fields.languages, diff --git a/src/collections/Recorders/endpoints/importFromStrapi.ts b/src/collections/Recorders/endpoints/importFromStrapi.ts index c445045..8d0c14c 100644 --- a/src/collections/Recorders/endpoints/importFromStrapi.ts +++ b/src/collections/Recorders/endpoints/importFromStrapi.ts @@ -4,7 +4,7 @@ import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImpor import { Recorder } from "../../../types/collections"; import { PayloadCreateData } from "../../../types/payload"; import { StrapiImage, StrapiLanguage } from "../../../types/strapi"; -import { isUndefined } from "../../../utils/asserts"; +import { isDefined, isUndefined } from "../../../utils/asserts"; import { uploadStrapiImage } from "../../../utils/localApi"; type StrapiRecorder = { @@ -31,9 +31,7 @@ export const importFromStrapi = createStrapiImportEndpoint = { - email: `${anonymous_code}@accords-library.com`, - password: process.env.RECORDER_DEFAULT_PASSWORD, + const data: Omit, "password" | "email"> = { username, anonymize, languages: languages.data?.map((language) => language.attributes.code), @@ -49,7 +47,26 @@ export const importFromStrapi = createStrapiImportEndpoint; export const RecordersThumbnails = buildImageCollectionConfig({ @@ -15,27 +16,14 @@ export const RecordersThumbnails = buildImageCollectionConfig({ singular: "Recorders Thumbnail", plural: "Recorders Thumbnails", }, - defaultSort: fields.filename, - admin: { - useAsTitle: fields.filename, - disableDuplicate: true, - group: CollectionGroups.Media, - }, + admin: { defaultColumns: [fields.filename, fields.recorder, fields.updatedAt] }, upload: { imageSizes: [ { - name: "og", - height: 256, - width: 256, - formatOptions: { - format: "jpg", - options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 }, - }, - }, - { - name: "small", - height: 128, - width: 128, + name: "square", + height: 150, + width: 150, + fit: "cover", formatOptions: { format: "webp", options: { effort: 6, quality: 80, alphaQuality: 80 }, diff --git a/src/collections/Videos/Videos.ts b/src/collections/Videos/Videos.ts index 16a165e..610d3bc 100644 --- a/src/collections/Videos/Videos.ts +++ b/src/collections/Videos/Videos.ts @@ -94,7 +94,6 @@ export const Videos: CollectionConfig = buildCollectionConfig({ name: fields.channel, type: "relationship", relationTo: Collections.VideosChannels, - required: true, admin: { position: "sidebar" }, }, ], diff --git a/src/collections/Videos/endpoints/importFromStrapi.ts b/src/collections/Videos/endpoints/importFromStrapi.ts index 9485a38..5d4810b 100644 --- a/src/collections/Videos/endpoints/importFromStrapi.ts +++ b/src/collections/Videos/endpoints/importFromStrapi.ts @@ -1,9 +1,9 @@ import payload from "payload"; -import { Collections } from "../../../constants"; +import { Collections, VideoSources } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { Video, VideosChannel } from "../../../types/collections"; import { PayloadCreateData } from "../../../types/payload"; -import { isUndefined } from "../../../utils/asserts"; +import { isDefined, isUndefined } from "../../../utils/asserts"; type StapiVideo = { uid: string; @@ -16,7 +16,7 @@ type StapiVideo = { }; views: number; likes: number; - source?: "YouTube" | "NicoNico" | "Tumblr"; + source?: VideoSources; gone: boolean; channel: { data?: { attributes: { uid: string; title: string; subscribers: number } } }; }; @@ -43,31 +43,34 @@ export const importFromStrapi = createStrapiImportEndpoint({ user ) => { if (isUndefined(source)) throw new Error("A source is required to create a Video"); - if (isUndefined(channel.data)) throw new Error("A channel is required to create a Video"); + if (source === VideoSources.YouTube && isUndefined(channel.data)) + throw new Error("A channel is required to create a YouTube Video"); - try { - const videoChannel: PayloadCreateData = { - uid: channel.data.attributes.uid, - title: channel.data.attributes.title, - subscribers: channel.data.attributes.subscribers, - }; - await payload.create({ + let videoChannelId; + if (isDefined(channel.data)) { + try { + const videoChannel: PayloadCreateData = { + uid: channel.data.attributes.uid, + title: channel.data.attributes.title, + subscribers: channel.data.attributes.subscribers, + }; + await payload.create({ + collection: Collections.VideosChannels, + data: videoChannel, + user, + }); + } catch (e) {} + + const result = await payload.find({ collection: Collections.VideosChannels, - data: videoChannel, - user, + where: { uid: { equals: channel.data.attributes.uid } }, }); - } catch (e) {} - const result = await payload.find({ - collection: Collections.VideosChannels, - where: { uid: { equals: channel.data.attributes.uid } }, - }); - - if (result.docs.length === 0) { - throw new Error("A video channel is required to create a video"); + if (result.docs.length > 0) { + videoChannelId = result.docs[0].id; + } } - const videoChannel = result.docs[0] as VideosChannel; const video: PayloadCreateData