diff --git a/src/accesses/collections/mustBeAdmin.ts b/src/accesses/collections/mustBeAdmin.ts new file mode 100644 index 0000000..ff533ed --- /dev/null +++ b/src/accesses/collections/mustBeAdmin.ts @@ -0,0 +1,9 @@ +import { Access } from "payload/config"; +import { RecordersRoles } from "../../constants"; +import { Recorder } from "../../types/collections"; +import { isDefined, isUndefined } from "../../utils/asserts"; + +export const mustBeAdmin: Access = ({ req: { user } }): boolean => { + if (isUndefined(user)) return false; + return isDefined(user.role) && user.role.includes(RecordersRoles.Admin); +}; diff --git a/src/accesses/collections/mustBeAdminOrSelf.ts b/src/accesses/collections/mustBeAdminOrSelf.ts index 8fb7f38..fd27cfa 100644 --- a/src/accesses/collections/mustBeAdminOrSelf.ts +++ b/src/accesses/collections/mustBeAdminOrSelf.ts @@ -1,11 +1,10 @@ import { Access } from "payload/config"; -import { Recorder } from "../../types/collections"; import { RecordersRoles } from "../../constants"; +import { Recorder } from "../../types/collections"; import { isUndefined } from "../../utils/asserts"; -export const mustBeAdminOrSelf: Access = ({ req }) => { - const user = req.user as Recorder | undefined; +export const mustBeAdminOrSelf: Access = ({ req: { user } }) => { if (isUndefined(user)) return false; - if (user.role.includes(RecordersRoles.Admin)) return true; + if (user.role?.includes(RecordersRoles.Admin)) return true; return { id: { equals: user.id } }; }; diff --git a/src/accesses/collections/mustHaveAtLeastOneRole.ts b/src/accesses/collections/mustHaveAtLeastOneRole.ts index 6e07c86..5dbfe7b 100644 --- a/src/accesses/collections/mustHaveAtLeastOneRole.ts +++ b/src/accesses/collections/mustHaveAtLeastOneRole.ts @@ -1,8 +1,8 @@ +import { Access } from "payload/config"; import { Recorder } from "../../types/collections"; -import { isUndefined } from "../../utils/asserts"; +import { isDefined, isUndefined } from "../../utils/asserts"; -export const mustHaveAtLeastOneRole = ({ req }): boolean => { - const user = req.user as Recorder | undefined; +export const mustHaveAtLeastOneRole: Access = ({ req: { user } }): boolean => { if (isUndefined(user)) return false; - return user.role.length > 0; + return isDefined(user.role) && user.role.length > 0; }; diff --git a/src/accesses/fields/mustBeAdmin.ts b/src/accesses/fields/mustBeAdmin.ts new file mode 100644 index 0000000..1c3c34b --- /dev/null +++ b/src/accesses/fields/mustBeAdmin.ts @@ -0,0 +1,9 @@ +import { FieldAccess } from "payload/types"; +import { RecordersRoles } from "../../constants"; +import { Recorder } from "../../types/collections"; +import { isDefined, isUndefined } from "../../utils/asserts"; + +export const mustBeAdmin: FieldAccess = ({ req: { user } }): boolean => { + if (isUndefined(user)) return false; + return isDefined(user.role) && user.role.includes(RecordersRoles.Admin); +}; diff --git a/src/accesses/mustBeAdmin.ts b/src/accesses/mustBeAdmin.ts deleted file mode 100644 index 476682f..0000000 --- a/src/accesses/mustBeAdmin.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Recorder } from "../types/collections"; -import { RecordersRoles } from "../constants"; -import { isUndefined } from "../utils/asserts"; - -export const mustBeAdmin = ({ req }): boolean => { - const user = req.user as Recorder | undefined; - if (isUndefined(user)) return false; - return user.role.includes(RecordersRoles.Admin); -}; diff --git a/src/collections/ChronologyEras/ChronologyEras.ts b/src/collections/ChronologyEras/ChronologyEras.ts index ea02a91..181c464 100644 --- a/src/collections/ChronologyEras/ChronologyEras.ts +++ b/src/collections/ChronologyEras/ChronologyEras.ts @@ -1,5 +1,5 @@ import { CollectionConfig } from "payload/types"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections } from "../../constants"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { slugField } from "../../fields/slugField/slugField"; diff --git a/src/collections/ChronologyEras/endpoints/importFromStrapi.ts b/src/collections/ChronologyEras/endpoints/importFromStrapi.ts index 8737f88..43c269d 100644 --- a/src/collections/ChronologyEras/endpoints/importFromStrapi.ts +++ b/src/collections/ChronologyEras/endpoints/importFromStrapi.ts @@ -1,8 +1,17 @@ import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { ChronologyEra } from "../../../types/collections"; +import { StrapiLanguage } from "../../../types/strapi"; +import { isUndefined } from "../../../utils/asserts"; -export const importFromStrapi = createStrapiImportEndpoint({ +type StrapiChronologyEra = { + slug: string; + starting_year: number; + ending_year: number; + title: { title: string; language: StrapiLanguage; description?: string }[]; +}; + +export const importFromStrapi = createStrapiImportEndpoint({ strapi: { collection: "chronology-eras", params: { @@ -15,11 +24,15 @@ export const importFromStrapi = createStrapiImportEndpoint({ slug, startingYear: starting_year, endingYear: ending_year, - translations: titles.map(({ language, title, description }) => ({ - language: language.data.attributes.code, - title, - description, - })), + translations: titles.map(({ language, title, description }) => { + if (isUndefined(language.data)) + throw new Error("Language is undefined for one of the translations"); + return { + language: language.data?.attributes.code, + title, + description, + }; + }), }), }, }); diff --git a/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts b/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts index 645f1db..ac61b0c 100644 --- a/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts +++ b/src/collections/ChronologyEras/hooks/beforeValidateEndingGreaterThanStarting.ts @@ -1,9 +1,14 @@ import { CollectionBeforeValidateHook } from "payload/types"; import { ChronologyEra } from "../../../types/collections"; +import { isUndefined } from "../../../utils/asserts"; export const beforeValidateEndingGreaterThanStarting: CollectionBeforeValidateHook< ChronologyEra -> = async ({ data: { startingYear, endingYear } }) => { +> = async ({ data }) => { + if (isUndefined(data)) throw new Error("The data is undefined"); + const { startingYear, endingYear } = data; + if (isUndefined(endingYear)) throw new Error("Ending year is undefined"); + if (isUndefined(startingYear)) throw new Error("Starting year is undefined"); if (endingYear < startingYear) { throw new Error("The ending year cannot be before the starting year."); } diff --git a/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts b/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts index 09917d1..6b2da27 100644 --- a/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts +++ b/src/collections/ChronologyEras/hooks/beforeValidateNoIntersection.ts @@ -2,11 +2,16 @@ import payload from "payload"; import { CollectionBeforeValidateHook } from "payload/types"; import { Collections } from "../../../constants"; import { ChronologyEra } from "../../../types/collections"; -import { hasIntersection } from "../../../utils/asserts"; +import { hasIntersection, isUndefined } from "../../../utils/asserts"; export const beforeValidateNoIntersection: CollectionBeforeValidateHook = async ({ - data: { startingYear, endingYear }, + data, }) => { + if (isUndefined(data)) throw new Error("The data is undefined"); + const { startingYear, endingYear } = data; + if (isUndefined(endingYear)) throw new Error("Ending year is undefined"); + if (isUndefined(startingYear)) throw new Error("Starting year is undefined"); + const otherEras = await payload.find({ collection: Collections.ChronologyEras, limit: 100, diff --git a/src/collections/ChronologyItems/ChronologyItems.ts b/src/collections/ChronologyItems/ChronologyItems.ts index d9f2cd6..b4f7f4f 100644 --- a/src/collections/ChronologyItems/ChronologyItems.ts +++ b/src/collections/ChronologyItems/ChronologyItems.ts @@ -72,6 +72,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig( if (!DateTime.fromObject({ year, month, day }).isValid) { return `The given date (${stringDate}) is not a valid date.`; } + return true; }, fields: [ { diff --git a/src/collections/ChronologyItems/endpoints/importFromStrapi.ts b/src/collections/ChronologyItems/endpoints/importFromStrapi.ts index a977d36..025de6b 100644 --- a/src/collections/ChronologyItems/endpoints/importFromStrapi.ts +++ b/src/collections/ChronologyItems/endpoints/importFromStrapi.ts @@ -1,8 +1,24 @@ import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { ChronologyItem } from "../../../types/collections"; +import { StrapiLanguage } from "../../../types/strapi"; +import { isUndefined } from "../../../utils/asserts"; -export const importFromStrapi = createStrapiImportEndpoint({ +type StrapiChronologyItem = { + year: number; + month?: number; + day?: number; + events: { + translations: { + title?: string; + description?: string; + note?: string; + language: StrapiLanguage; + }[]; + }[]; +}; + +export const importFromStrapi = createStrapiImportEndpoint({ strapi: { collection: "chronology-items", params: { @@ -14,16 +30,20 @@ export const importFromStrapi = createStrapiImportEndpoint({ convert: ({ year, month, day, events }, user) => ({ date: { year, month, day }, events: events.map((event) => ({ - translations: event.translations.map(({ title, description, note, language }) => ({ - title, - description, - note, - language: language.data.attributes.code, - sourceLanguage: "en", - ...(language.data.attributes.code === "en" - ? { transcribers: [user.id] } - : { translators: [user.id] }), - })), + translations: event.translations.map(({ title, description, note, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a chronology item event translation"); + return { + title, + description, + note, + language: language.data.attributes.code, + sourceLanguage: "en", + ...(language.data.attributes.code === "en" + ? { transcribers: [user.id] } + : { translators: [user.id] }), + }; + }), })), }), }, diff --git a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts b/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts index 6121ed2..e638019 100644 --- a/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts +++ b/src/collections/ChronologyItems/hooks/beforeValidatePopulateNameField.ts @@ -6,8 +6,12 @@ export const beforeValidatePopulateNameField: FieldHook< ChronologyItem, ChronologyItem["name"], ChronologyItem -> = ({ data: { date } }) => { - if (isUndefined(date?.year)) return "????-??-??"; +> = ({ data }) => { + if (isUndefined(data)) { + return "????-??-??"; + } + const { date } = data; + if (isUndefined(date) || isUndefined(date?.year)) return "????-??-??"; const { year, month, day } = date; let result = String(year).padStart(5, " "); if (isDefined(month)) { diff --git a/src/collections/Currencies/Currencies.ts b/src/collections/Currencies/Currencies.ts index 30030d7..12db66e 100644 --- a/src/collections/Currencies/Currencies.ts +++ b/src/collections/Currencies/Currencies.ts @@ -1,5 +1,5 @@ import { text } from "payload/dist/fields/validations"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections } from "../../constants"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; diff --git a/src/collections/Currencies/endpoints/importFromStrapi.ts b/src/collections/Currencies/endpoints/importFromStrapi.ts index d488070..8727102 100644 --- a/src/collections/Currencies/endpoints/importFromStrapi.ts +++ b/src/collections/Currencies/endpoints/importFromStrapi.ts @@ -2,7 +2,12 @@ import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { Language } from "../../../types/collections"; -export const importFromStrapi = createStrapiImportEndpoint({ +type StrapiLanguage = { + code: string; + name: string; +}; + +export const importFromStrapi = createStrapiImportEndpoint({ strapi: { collection: "currencies", params: {}, diff --git a/src/collections/Keys/Keys.ts b/src/collections/Keys/Keys.ts index 6060e1a..72e4268 100644 --- a/src/collections/Keys/Keys.ts +++ b/src/collections/Keys/Keys.ts @@ -1,11 +1,11 @@ import payload from "payload"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { QuickFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { Key } from "../../types/collections"; -import { isDefined } from "../../utils/asserts"; +import { isDefined, isUndefined } from "../../utils/asserts"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; @@ -58,15 +58,15 @@ export const Keys = buildCollectionConfig({ }, hooks: { beforeValidate: [ - async ({ data: { name, type } }) => { + async ({ data }) => { + if (isUndefined(data)) return; + const { name, type } = data; const result = await payload.find({ collection: Collections.Keys, where: { name: { equals: name }, type: { equals: type } }, }); if (result.docs.length > 0) { - throw new Error( - `A Key of type "${KeysTypes[type]}" already exists with the name "${name}"` - ); + throw new Error(`A Key of type "${type}" already exists with the name "${name}"`); } }, ], diff --git a/src/collections/Keys/endpoints/importFromStrapi.ts b/src/collections/Keys/endpoints/importFromStrapi.ts index b08556a..5fbac7a 100644 --- a/src/collections/Keys/endpoints/importFromStrapi.ts +++ b/src/collections/Keys/endpoints/importFromStrapi.ts @@ -1,14 +1,14 @@ import payload from "payload"; -import { CollectionConfig } from "payload/types"; import { Collections } from "../../../constants"; import { getAllStrapiEntries, importStrapiEntries, } from "../../../endpoints/createStrapiImportEndpoint"; import { Key } from "../../../types/collections"; -import { isDefined } from "../../../utils/asserts"; +import { CollectionEndpoint, PayloadCreateData } from "../../../types/payload"; +import { StrapiLanguage } from "../../../types/strapi"; +import { isDefined, isUndefined } from "../../../utils/asserts"; import { formatToCamelCase } from "../../../utils/string"; -import { PayloadCreateData } from "../../../utils/types"; const importStrapiWordings: typeof importStrapiEntries = async ({ payload: payloadParams, @@ -30,7 +30,7 @@ const importStrapiWordings: typeof importStrapiEntries = async ({ .filter(({ name }) => isDefined(name) && name !== ""), })); - const errors = []; + const errors: string[] = []; await Promise.all( entries.map(async (entry) => { @@ -42,7 +42,9 @@ const importStrapiWordings: typeof importStrapiEntries = async ({ }); } catch (e) { console.warn(e); - errors.push(`${e.name} with ${entry.name}`); + if (typeof e === "object" && isDefined(e) && "name" in e) { + errors.push(`${e.name} with ${entry.name}`); + } } }) ); @@ -50,7 +52,7 @@ const importStrapiWordings: typeof importStrapiEntries = async ({ return { count: entries.length, errors }; }; -export const importFromStrapi: CollectionConfig["endpoints"][number] = { +export const importFromStrapi: CollectionEndpoint = { method: "get", path: "/strapi", handler: async (req, res) => { @@ -64,7 +66,15 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = { }); } - const { count: categoriesCount, errors: categoriesErrors } = await importStrapiEntries({ + type StrapiCategories = { + slug: string; + titles: { title?: string; short?: string; language: StrapiLanguage }[]; + }; + + const { count: categoriesCount, errors: categoriesErrors } = await importStrapiEntries< + Key, + StrapiCategories + >({ strapi: { collection: "categories", params: { populate: { titles: { populate: "language" } } }, @@ -74,59 +84,96 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = { convert: ({ slug, titles }) => ({ name: slug, type: "Categories", - translations: titles.map(({ title, short, language }) => ({ - name: title, - short, - language: language.data.attributes.code, - })), + translations: titles.map(({ title, short, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + if (isUndefined(title)) + throw new Error("A title is required for a Keys title translation"); + return { + name: title, + short, + language: language.data.attributes.code, + }; + }), }), }, user: req.user, }); - const { count: contentTypesCount, errors: contentTypesErrors } = await importStrapiEntries( - { - strapi: { - collection: "content-types", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "Contents", - translations: titles.map(({ title, language }) => ({ + type StrapiContentType = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: contentTypesCount, errors: contentTypesErrors } = await importStrapiEntries< + Key, + StrapiContentType + >({ + strapi: { + collection: "content-types", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "Contents", + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { name: title, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - } - ); + }), + }, + user: req.user, + }); - const { count: gamePlatformsCount, errors: gamePlatformsErrors } = - await importStrapiEntries({ - strapi: { - collection: "game-platforms", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "GamePlatforms", - translations: titles.map(({ title, short, language }) => ({ + type StrapiGamePlatform = { + slug: string; + titles: { title?: string; short?: string; language: StrapiLanguage }[]; + }; + + const { count: gamePlatformsCount, errors: gamePlatformsErrors } = await importStrapiEntries< + Key, + StrapiGamePlatform + >({ + strapi: { + collection: "game-platforms", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "GamePlatforms", + translations: titles.map(({ title, short, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + if (isUndefined(title)) + throw new Error("A title is required for a Keys title translation"); + return { name: title, short, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - }); + }), + }, + user: req.user, + }); - const { count: libraryCount, errors: libraryErrors } = await importStrapiEntries({ + type StrapiMetadataTypes = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: libraryCount, errors: libraryErrors } = await importStrapiEntries< + Key, + StrapiMetadataTypes + >({ strapi: { collection: "metadata-types", params: { populate: { titles: { populate: "language" } } }, @@ -136,99 +183,152 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = { convert: ({ slug, titles }) => ({ name: slug, type: "Library", - translations: titles.map(({ title, language }) => ({ - name: title, - language: language.data.attributes.code, - })), + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { + name: title, + language: language.data.attributes.code, + }; + }), }), }, user: req.user, }); - const { count: libraryAudioCount, errors: libraryAudioErrors } = await importStrapiEntries( - { - strapi: { - collection: "audio-subtypes", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "LibraryAudio", - translations: titles.map(({ title, language }) => ({ + type StrapiAudioSubtypes = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: libraryAudioCount, errors: libraryAudioErrors } = await importStrapiEntries< + Key, + StrapiAudioSubtypes + >({ + strapi: { + collection: "audio-subtypes", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "LibraryAudio", + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { name: title, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - } - ); + }), + }, + user: req.user, + }); - const { count: libraryGroupCount, errors: libraryGroupErrors } = await importStrapiEntries( - { - strapi: { - collection: "group-subtypes", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "LibraryGroup", - translations: titles.map(({ title, language }) => ({ + type StrapiGroupSubtypes = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: libraryGroupCount, errors: libraryGroupErrors } = await importStrapiEntries< + Key, + StrapiGroupSubtypes + >({ + strapi: { + collection: "group-subtypes", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "LibraryGroup", + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { name: title, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - } - ); + }), + }, + user: req.user, + }); - const { count: libraryTextualCount, errors: libraryTextualErrors } = - await importStrapiEntries({ - strapi: { - collection: "textual-subtypes", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "LibraryTextual", - translations: titles.map(({ title, language }) => ({ + type StrapiTextualSubtypes = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: libraryTextualCount, errors: libraryTextualErrors } = await importStrapiEntries< + Key, + StrapiTextualSubtypes + >({ + strapi: { + collection: "textual-subtypes", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "LibraryTextual", + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { name: title, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - }); + }), + }, + user: req.user, + }); - const { count: libraryVideoCount, errors: libraryVideoErrors } = await importStrapiEntries( - { - strapi: { - collection: "video-subtypes", - params: { populate: { titles: { populate: "language" } } }, - }, - payload: { - collection: Collections.Keys, - convert: ({ slug, titles }) => ({ - name: slug, - type: "LibraryVideo", - translations: titles.map(({ title, language }) => ({ + type StrapiVideoSubtypes = { + slug: string; + titles: { title: string; language: StrapiLanguage }[]; + }; + + const { count: libraryVideoCount, errors: libraryVideoErrors } = await importStrapiEntries< + Key, + StrapiVideoSubtypes + >({ + strapi: { + collection: "video-subtypes", + params: { populate: { titles: { populate: "language" } } }, + }, + payload: { + collection: Collections.Keys, + convert: ({ slug, titles }) => ({ + name: slug, + type: "LibraryVideo", + translations: titles.map(({ title, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + return { name: title, language: language.data.attributes.code, - })), + }; }), - }, - user: req.user, - } - ); + }), + }, + user: req.user, + }); - const { count: weaponsCount, errors: weaponsErrors } = await importStrapiEntries({ + type StrapiWeaponTypes = { + slug: string; + translations: { name?: string; language: StrapiLanguage }[]; + }; + + const { count: weaponsCount, errors: weaponsErrors } = await importStrapiEntries< + Key, + StrapiWeaponTypes + >({ strapi: { collection: "weapon-story-types", params: { populate: { translations: { populate: "language" } } }, @@ -238,16 +338,22 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = { convert: ({ slug, translations }) => ({ name: slug, type: "Weapons", - translations: translations.map(({ name, language }) => ({ - name, - language: language.data.attributes.code, - })), + translations: translations.map(({ name, language }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Keys title translation"); + if (isUndefined(name)) + throw new Error("A name is required for a Keys title translation"); + return { + name, + language: language.data.attributes.code, + }; + }), }), }, user: req.user, }); - const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings({ + const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings({ strapi: { collection: "website-interfaces", params: { populate: "ui_language" } }, payload: { collection: Collections.Keys, convert: (strapiObject) => strapiObject }, user: req.user, diff --git a/src/collections/Languages/Languages.ts b/src/collections/Languages/Languages.ts index 06c9d2f..7371120 100644 --- a/src/collections/Languages/Languages.ts +++ b/src/collections/Languages/Languages.ts @@ -1,5 +1,5 @@ import { text } from "payload/dist/fields/validations"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { publicAccess } from "../../accesses/publicAccess"; import { CollectionGroups, Collections } from "../../constants"; import { buildCollectionConfig } from "../../utils/collectionConfig"; diff --git a/src/collections/Languages/endpoints/importFromStrapi.ts b/src/collections/Languages/endpoints/importFromStrapi.ts index 0864fba..ac8a934 100644 --- a/src/collections/Languages/endpoints/importFromStrapi.ts +++ b/src/collections/Languages/endpoints/importFromStrapi.ts @@ -2,7 +2,12 @@ import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { Language } from "../../../types/collections"; -export const importFromStrapi = createStrapiImportEndpoint({ +type StrapiLanguage = { + name: string; + code: string; +}; + +export const importFromStrapi = createStrapiImportEndpoint({ strapi: { collection: "languages", params: {}, diff --git a/src/collections/LibraryItems/LibraryItems.ts b/src/collections/LibraryItems/LibraryItems.ts index 331a4ec..e73024c 100644 --- a/src/collections/LibraryItems/LibraryItems.ts +++ b/src/collections/LibraryItems/LibraryItems.ts @@ -1,3 +1,4 @@ +import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types"; import { CollectionGroups, Collections, @@ -18,7 +19,6 @@ import { LibraryItem } from "../../types/collections"; import { isDefined } from "../../utils/asserts"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { RowLabel } from "./components/RowLabel"; -import { getBySlug } from "./endpoints/getBySlug"; const fields = { status: "_status", @@ -139,7 +139,6 @@ export const LibraryItems = buildVersionedCollectionConfig({ }, preview: (doc) => `https://accords-library.com/library/${doc.slug}`, }, - endpoints: [getBySlug], fields: [ { name: fields.itemType, @@ -480,7 +479,7 @@ export const LibraryItems = buildVersionedCollectionConfig({ "Make sure the page number corresponds to the page number written on \ the scan. You can use negative page numbers if necessary.", components: { - RowLabel: ({ data }) => RowLabel(data), + RowLabel: ({ data }: RowLabelArgs) => RowLabel(data), }, }, fields: [ diff --git a/src/collections/LibraryItems/endpoints/getBySlug.ts b/src/collections/LibraryItems/endpoints/getBySlug.ts deleted file mode 100644 index b540b33..0000000 --- a/src/collections/LibraryItems/endpoints/getBySlug.ts +++ /dev/null @@ -1,59 +0,0 @@ -import cleanDeep from "clean-deep"; -import { Collections } from "../../../constants"; -import { createGetByEndpoint } from "../../../endpoints/createByEndpoint"; -import { LibraryItem } from "../../../types/collections"; - -type ProcessedLibraryItem = Omit & { - size?: Omit; - price?: Omit & { currency: string }; - scans?: Omit & { - obi: Omit; - cover: Omit; - dustjacket: Omit; - }; -}; - -export const getBySlug = createGetByEndpoint>( - Collections.LibraryItems, - "slug", - async ({ id, size, price, scans, ...otherProps }) => { - const processedLibraryItem: ProcessedLibraryItem = { - size: processOptionalGroup(size), - price: processPrice(price), - scans: processScans(scans), - ...otherProps, - }; - - return cleanDeep(processedLibraryItem, { - emptyStrings: false, - emptyArrays: false, - emptyObjects: false, - nullValues: true, - undefinedValues: true, - NaNValues: false, - }); - } -); - -const processScans = (scans: LibraryItem["scans"]): ProcessedLibraryItem["scans"] => { - if (!scans || scans.length === 0) return undefined; - const { cover, dustjacket, id, obi, ...otherProps } = scans[0]; - return { - cover: processOptionalGroup(cover), - dustjacket: processOptionalGroup(dustjacket), - obi: processOptionalGroup(obi), - ...otherProps, - }; -}; - -const processPrice = (price: LibraryItem["price"]): ProcessedLibraryItem["price"] => { - if (!price || price.length === 0) return undefined; - const { currency, ...otherProps } = processOptionalGroup(price); - return { ...otherProps, currency: typeof currency === "string" ? currency : currency.id }; -}; - -const processOptionalGroup = (group: T[] | null | undefined) => { - if (!group || group.length === 0) return undefined; - const { id, ...otherProps } = group[0]; - return otherProps; -}; diff --git a/src/collections/Posts/Posts.ts b/src/collections/Posts/Posts.ts index 94fced0..58e7c85 100644 --- a/src/collections/Posts/Posts.ts +++ b/src/collections/Posts/Posts.ts @@ -8,7 +8,6 @@ import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping"; import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; import { isDefined, isUndefined } from "../../utils/asserts"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; -import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate"; const fields = { slug: "slug", @@ -57,9 +56,6 @@ export const Posts = buildVersionedCollectionConfig({ }, preview: (doc) => `https://accords-library.com/news/${doc.slug}`, }, - hooks: { - beforeValidate: [removeTranslatorsForTranscripts], - }, fields: [ { type: "row", @@ -110,6 +106,15 @@ export const Posts = buildVersionedCollectionConfig({ type: "relationship", relationTo: Collections.Recorders, hasMany: true, + hooks: { + beforeChange: [ + ({ siblingData }) => { + if (siblingData.language === siblingData.sourceLanguage) { + delete siblingData.translators; + } + }, + ], + }, admin: { condition: (_, siblingData) => { if ( diff --git a/src/collections/Posts/hooks/beforeValidate.ts b/src/collections/Posts/hooks/beforeValidate.ts deleted file mode 100644 index 9a6d1d9..0000000 --- a/src/collections/Posts/hooks/beforeValidate.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CollectionBeforeValidateHook } from "payload/types"; -import { Post } from "../../../types/collections"; - -export const removeTranslatorsForTranscripts: CollectionBeforeValidateHook = async ({ - data: { translations, ...data }, -}) => ({ - ...data, - translations: translations?.map(({ translators, ...translation }) => { - if (translation.language === translation.sourceLanguage) { - return { ...translation, translators: [] }; - } - return { ...translation, translators }; - }), -}); diff --git a/src/collections/Recorders/Recorders.ts b/src/collections/Recorders/Recorders.ts index 8e97dc2..9a08b9d 100644 --- a/src/collections/Recorders/Recorders.ts +++ b/src/collections/Recorders/Recorders.ts @@ -1,5 +1,6 @@ +import { mustBeAdmin as mustBeAdminForCollections } from "../../accesses/collections/mustBeAdmin"; import { mustBeAdminOrSelf } from "../../accesses/collections/mustBeAdminOrSelf"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin as mustBeAdminForFields } from "../../accesses/fields/mustBeAdmin"; import { QuickFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections, RecordersRoles } from "../../constants"; import { imageField } from "../../fields/imageField/imageField"; @@ -55,7 +56,6 @@ export const Recorders = buildCollectionConfig({ label: "∅ Role", filter: { where: { role: { not_in: Object.keys(RecordersRoles).join(",") } } }, }, - , ], [{ label: "Anonymized", filter: { where: { anonymize: { equals: true } } } }], ], @@ -65,10 +65,10 @@ export const Recorders = buildCollectionConfig({ }, auth: true, access: { - unlock: mustBeAdmin, + unlock: mustBeAdminForCollections, update: mustBeAdminOrSelf, - delete: mustBeAdmin, - create: mustBeAdmin, + delete: mustBeAdminForCollections, + create: mustBeAdminForCollections, }, hooks: { beforeLogin: [beforeLoginMustHaveAtLeastOneRole], @@ -111,14 +111,14 @@ export const Recorders = buildCollectionConfig({ description: "A short personal description about you or your involvement with this project or the franchise", }, - fields: [{ name: fields.biography, type: "textarea" }], + fields: [{ name: fields.biography, required: true, type: "textarea" }], }), { name: fields.role, type: "select", access: { - update: mustBeAdmin, - create: mustBeAdmin, + update: mustBeAdminForFields, + create: mustBeAdminForFields, }, hasMany: true, options: Object.entries(RecordersRoles).map(([value, label]) => ({ diff --git a/src/collections/Recorders/endpoints/importFromStrapi.ts b/src/collections/Recorders/endpoints/importFromStrapi.ts index 96c7fcb..c445045 100644 --- a/src/collections/Recorders/endpoints/importFromStrapi.ts +++ b/src/collections/Recorders/endpoints/importFromStrapi.ts @@ -2,10 +2,21 @@ import payload from "payload"; import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { Recorder } from "../../../types/collections"; +import { PayloadCreateData } from "../../../types/payload"; +import { StrapiImage, StrapiLanguage } from "../../../types/strapi"; +import { isUndefined } from "../../../utils/asserts"; import { uploadStrapiImage } from "../../../utils/localApi"; -import { PayloadCreateData } from "../../../utils/types"; -export const importFromStrapi = createStrapiImportEndpoint({ +type StrapiRecorder = { + username: string; + anonymize: boolean; + anonymous_code: number; + languages: { data: { attributes: { code: string } }[] }; + avatar: StrapiImage; + bio: { language: StrapiLanguage; bio?: string }[]; +}; + +export const importFromStrapi = createStrapiImportEndpoint({ strapi: { collection: "recorders", params: { @@ -27,10 +38,15 @@ export const importFromStrapi = createStrapiImportEndpoint({ anonymize, languages: languages.data?.map((language) => language.attributes.code), avatar: avatarId, - biographies: bios?.map(({ language, bio }) => ({ - language: language.data.attributes.code, - biography: bio, - })), + biographies: bios?.map(({ language, bio }) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a Recorder biography"); + if (isUndefined(bio)) throw new Error("A bio is required for a Recorder biography"); + return { + language: language.data.attributes.code, + biography: bio, + }; + }), }; await payload.create({ collection: Collections.Recorders, data, user }); diff --git a/src/collections/Videos/Videos.ts b/src/collections/Videos/Videos.ts index 7800ff7..16a165e 100644 --- a/src/collections/Videos/Videos.ts +++ b/src/collections/Videos/Videos.ts @@ -1,5 +1,5 @@ import { CollectionConfig } from "payload/types"; -import { mustBeAdmin } from "../../accesses/mustBeAdmin"; +import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections, VideoSources } from "../../constants"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; diff --git a/src/collections/Videos/endpoints/importFromStrapi.ts b/src/collections/Videos/endpoints/importFromStrapi.ts index 13b073f..9485a38 100644 --- a/src/collections/Videos/endpoints/importFromStrapi.ts +++ b/src/collections/Videos/endpoints/importFromStrapi.ts @@ -2,9 +2,26 @@ import payload from "payload"; import { Collections } from "../../../constants"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { Video, VideosChannel } from "../../../types/collections"; -import { PayloadCreateData } from "../../../utils/types"; +import { PayloadCreateData } from "../../../types/payload"; +import { isUndefined } from "../../../utils/asserts"; -export const importFromStrapi = createStrapiImportEndpoint