Much stricter ts

This commit is contained in:
DrMint 2023-08-14 22:09:29 +02:00
parent f56ba4675f
commit f28a928442
44 changed files with 656 additions and 355 deletions

View File

@ -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<unknown, Recorder> = ({ req: { user } }): boolean => {
if (isUndefined(user)) return false;
return isDefined(user.role) && user.role.includes(RecordersRoles.Admin);
};

View File

@ -1,11 +1,10 @@
import { Access } from "payload/config"; import { Access } from "payload/config";
import { Recorder } from "../../types/collections";
import { RecordersRoles } from "../../constants"; import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { isUndefined } from "../../utils/asserts"; import { isUndefined } from "../../utils/asserts";
export const mustBeAdminOrSelf: Access = ({ req }) => { export const mustBeAdminOrSelf: Access<unknown, Recorder> = ({ req: { user } }) => {
const user = req.user as Recorder | undefined;
if (isUndefined(user)) return false; 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 } }; return { id: { equals: user.id } };
}; };

View File

@ -1,8 +1,8 @@
import { Access } from "payload/config";
import { Recorder } from "../../types/collections"; import { Recorder } from "../../types/collections";
import { isUndefined } from "../../utils/asserts"; import { isDefined, isUndefined } from "../../utils/asserts";
export const mustHaveAtLeastOneRole = ({ req }): boolean => { export const mustHaveAtLeastOneRole: Access<unknown, Recorder> = ({ req: { user } }): boolean => {
const user = req.user as Recorder | undefined;
if (isUndefined(user)) return false; if (isUndefined(user)) return false;
return user.role.length > 0; return isDefined(user.role) && user.role.length > 0;
}; };

View File

@ -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<any, any, Recorder> = ({ req: { user } }): boolean => {
if (isUndefined(user)) return false;
return isDefined(user.role) && user.role.includes(RecordersRoles.Admin);
};

View File

@ -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);
};

View File

@ -1,5 +1,5 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { slugField } from "../../fields/slugField/slugField"; import { slugField } from "../../fields/slugField/slugField";

View File

@ -1,8 +1,17 @@
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { ChronologyEra } from "../../../types/collections"; import { ChronologyEra } from "../../../types/collections";
import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts";
export const importFromStrapi = createStrapiImportEndpoint<ChronologyEra>({ type StrapiChronologyEra = {
slug: string;
starting_year: number;
ending_year: number;
title: { title: string; language: StrapiLanguage; description?: string }[];
};
export const importFromStrapi = createStrapiImportEndpoint<ChronologyEra, StrapiChronologyEra>({
strapi: { strapi: {
collection: "chronology-eras", collection: "chronology-eras",
params: { params: {
@ -15,11 +24,15 @@ export const importFromStrapi = createStrapiImportEndpoint<ChronologyEra>({
slug, slug,
startingYear: starting_year, startingYear: starting_year,
endingYear: ending_year, endingYear: ending_year,
translations: titles.map(({ language, title, description }) => ({ translations: titles.map(({ language, title, description }) => {
language: language.data.attributes.code, if (isUndefined(language.data))
title, throw new Error("Language is undefined for one of the translations");
description, return {
})), language: language.data?.attributes.code,
title,
description,
};
}),
}), }),
}, },
}); });

View File

@ -1,9 +1,14 @@
import { CollectionBeforeValidateHook } from "payload/types"; import { CollectionBeforeValidateHook } from "payload/types";
import { ChronologyEra } from "../../../types/collections"; import { ChronologyEra } from "../../../types/collections";
import { isUndefined } from "../../../utils/asserts";
export const beforeValidateEndingGreaterThanStarting: CollectionBeforeValidateHook< export const beforeValidateEndingGreaterThanStarting: CollectionBeforeValidateHook<
ChronologyEra 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) { if (endingYear < startingYear) {
throw new Error("The ending year cannot be before the starting year."); throw new Error("The ending year cannot be before the starting year.");
} }

View File

@ -2,11 +2,16 @@ import payload from "payload";
import { CollectionBeforeValidateHook } from "payload/types"; import { CollectionBeforeValidateHook } from "payload/types";
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { ChronologyEra } from "../../../types/collections"; import { ChronologyEra } from "../../../types/collections";
import { hasIntersection } from "../../../utils/asserts"; import { hasIntersection, isUndefined } from "../../../utils/asserts";
export const beforeValidateNoIntersection: CollectionBeforeValidateHook<ChronologyEra> = async ({ export const beforeValidateNoIntersection: CollectionBeforeValidateHook<ChronologyEra> = 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({ const otherEras = await payload.find({
collection: Collections.ChronologyEras, collection: Collections.ChronologyEras,
limit: 100, limit: 100,

View File

@ -72,6 +72,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
if (!DateTime.fromObject({ year, month, day }).isValid) { if (!DateTime.fromObject({ year, month, day }).isValid) {
return `The given date (${stringDate}) is not a valid date.`; return `The given date (${stringDate}) is not a valid date.`;
} }
return true;
}, },
fields: [ fields: [
{ {

View File

@ -1,8 +1,24 @@
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { ChronologyItem } from "../../../types/collections"; import { ChronologyItem } from "../../../types/collections";
import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts";
export const importFromStrapi = createStrapiImportEndpoint<ChronologyItem>({ type StrapiChronologyItem = {
year: number;
month?: number;
day?: number;
events: {
translations: {
title?: string;
description?: string;
note?: string;
language: StrapiLanguage;
}[];
}[];
};
export const importFromStrapi = createStrapiImportEndpoint<ChronologyItem, StrapiChronologyItem>({
strapi: { strapi: {
collection: "chronology-items", collection: "chronology-items",
params: { params: {
@ -14,16 +30,20 @@ export const importFromStrapi = createStrapiImportEndpoint<ChronologyItem>({
convert: ({ year, month, day, events }, user) => ({ convert: ({ year, month, day, events }, user) => ({
date: { year, month, day }, date: { year, month, day },
events: events.map((event) => ({ events: events.map((event) => ({
translations: event.translations.map(({ title, description, note, language }) => ({ translations: event.translations.map(({ title, description, note, language }) => {
title, if (isUndefined(language.data))
description, throw new Error("A language is required for a chronology item event translation");
note, return {
language: language.data.attributes.code, title,
sourceLanguage: "en", description,
...(language.data.attributes.code === "en" note,
? { transcribers: [user.id] } language: language.data.attributes.code,
: { translators: [user.id] }), sourceLanguage: "en",
})), ...(language.data.attributes.code === "en"
? { transcribers: [user.id] }
: { translators: [user.id] }),
};
}),
})), })),
}), }),
}, },

View File

@ -6,8 +6,12 @@ export const beforeValidatePopulateNameField: FieldHook<
ChronologyItem, ChronologyItem,
ChronologyItem["name"], ChronologyItem["name"],
ChronologyItem ChronologyItem
> = ({ data: { date } }) => { > = ({ data }) => {
if (isUndefined(date?.year)) return "????-??-??"; if (isUndefined(data)) {
return "????-??-??";
}
const { date } = data;
if (isUndefined(date) || isUndefined(date?.year)) return "????-??-??";
const { year, month, day } = date; const { year, month, day } = date;
let result = String(year).padStart(5, " "); let result = String(year).padStart(5, " ");
if (isDefined(month)) { if (isDefined(month)) {

View File

@ -1,5 +1,5 @@
import { text } from "payload/dist/fields/validations"; import { text } from "payload/dist/fields/validations";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";

View File

@ -2,7 +2,12 @@ import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Language } from "../../../types/collections"; import { Language } from "../../../types/collections";
export const importFromStrapi = createStrapiImportEndpoint<Language>({ type StrapiLanguage = {
code: string;
name: string;
};
export const importFromStrapi = createStrapiImportEndpoint<Language, StrapiLanguage>({
strapi: { strapi: {
collection: "currencies", collection: "currencies",
params: {}, params: {},

View File

@ -1,11 +1,11 @@
import payload from "payload"; import payload from "payload";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { QuickFilters } from "../../components/QuickFilters"; import { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants"; import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants";
import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { Key } from "../../types/collections"; import { Key } from "../../types/collections";
import { isDefined } from "../../utils/asserts"; import { isDefined, isUndefined } from "../../utils/asserts";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";
@ -58,15 +58,15 @@ export const Keys = buildCollectionConfig({
}, },
hooks: { hooks: {
beforeValidate: [ beforeValidate: [
async ({ data: { name, type } }) => { async ({ data }) => {
if (isUndefined(data)) return;
const { name, type } = data;
const result = await payload.find({ const result = await payload.find({
collection: Collections.Keys, collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: type } }, where: { name: { equals: name }, type: { equals: type } },
}); });
if (result.docs.length > 0) { if (result.docs.length > 0) {
throw new Error( throw new Error(`A Key of type "${type}" already exists with the name "${name}"`);
`A Key of type "${KeysTypes[type]}" already exists with the name "${name}"`
);
} }
}, },
], ],

View File

@ -1,14 +1,14 @@
import payload from "payload"; import payload from "payload";
import { CollectionConfig } from "payload/types";
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { import {
getAllStrapiEntries, getAllStrapiEntries,
importStrapiEntries, importStrapiEntries,
} from "../../../endpoints/createStrapiImportEndpoint"; } from "../../../endpoints/createStrapiImportEndpoint";
import { Key } from "../../../types/collections"; 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 { formatToCamelCase } from "../../../utils/string";
import { PayloadCreateData } from "../../../utils/types";
const importStrapiWordings: typeof importStrapiEntries = async ({ const importStrapiWordings: typeof importStrapiEntries = async ({
payload: payloadParams, payload: payloadParams,
@ -30,7 +30,7 @@ const importStrapiWordings: typeof importStrapiEntries = async ({
.filter(({ name }) => isDefined(name) && name !== ""), .filter(({ name }) => isDefined(name) && name !== ""),
})); }));
const errors = []; const errors: string[] = [];
await Promise.all( await Promise.all(
entries.map(async (entry) => { entries.map(async (entry) => {
@ -42,7 +42,9 @@ const importStrapiWordings: typeof importStrapiEntries = async ({
}); });
} catch (e) { } catch (e) {
console.warn(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 }; return { count: entries.length, errors };
}; };
export const importFromStrapi: CollectionConfig["endpoints"][number] = { export const importFromStrapi: CollectionEndpoint = {
method: "get", method: "get",
path: "/strapi", path: "/strapi",
handler: async (req, res) => { handler: async (req, res) => {
@ -64,7 +66,15 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = {
}); });
} }
const { count: categoriesCount, errors: categoriesErrors } = await importStrapiEntries<Key>({ type StrapiCategories = {
slug: string;
titles: { title?: string; short?: string; language: StrapiLanguage }[];
};
const { count: categoriesCount, errors: categoriesErrors } = await importStrapiEntries<
Key,
StrapiCategories
>({
strapi: { strapi: {
collection: "categories", collection: "categories",
params: { populate: { titles: { populate: "language" } } }, params: { populate: { titles: { populate: "language" } } },
@ -74,59 +84,96 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = {
convert: ({ slug, titles }) => ({ convert: ({ slug, titles }) => ({
name: slug, name: slug,
type: "Categories", type: "Categories",
translations: titles.map(({ title, short, language }) => ({ translations: titles.map(({ title, short, language }) => {
name: title, if (isUndefined(language.data))
short, throw new Error("A language is required for a Keys title translation");
language: language.data.attributes.code, 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: contentTypesCount, errors: contentTypesErrors } = await importStrapiEntries<Key>( type StrapiContentType = {
{ slug: string;
strapi: { titles: { title: string; language: StrapiLanguage }[];
collection: "content-types", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: contentTypesCount, errors: contentTypesErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiContentType
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "Contents", collection: "content-types",
translations: titles.map(({ title, language }) => ({ 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, name: title,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
} user: req.user,
); });
const { count: gamePlatformsCount, errors: gamePlatformsErrors } = type StrapiGamePlatform = {
await importStrapiEntries<Key>({ slug: string;
strapi: { titles: { title?: string; short?: string; language: StrapiLanguage }[];
collection: "game-platforms", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: gamePlatformsCount, errors: gamePlatformsErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiGamePlatform
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "GamePlatforms", collection: "game-platforms",
translations: titles.map(({ title, short, language }) => ({ 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, name: title,
short, short,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
}); user: req.user,
});
const { count: libraryCount, errors: libraryErrors } = await importStrapiEntries<Key>({ type StrapiMetadataTypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryCount, errors: libraryErrors } = await importStrapiEntries<
Key,
StrapiMetadataTypes
>({
strapi: { strapi: {
collection: "metadata-types", collection: "metadata-types",
params: { populate: { titles: { populate: "language" } } }, params: { populate: { titles: { populate: "language" } } },
@ -136,99 +183,152 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = {
convert: ({ slug, titles }) => ({ convert: ({ slug, titles }) => ({
name: slug, name: slug,
type: "Library", type: "Library",
translations: titles.map(({ title, language }) => ({ translations: titles.map(({ title, language }) => {
name: title, if (isUndefined(language.data))
language: language.data.attributes.code, 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: libraryAudioCount, errors: libraryAudioErrors } = await importStrapiEntries<Key>( type StrapiAudioSubtypes = {
{ slug: string;
strapi: { titles: { title: string; language: StrapiLanguage }[];
collection: "audio-subtypes", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: libraryAudioCount, errors: libraryAudioErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiAudioSubtypes
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "LibraryAudio", collection: "audio-subtypes",
translations: titles.map(({ title, language }) => ({ 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, name: title,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
} user: req.user,
); });
const { count: libraryGroupCount, errors: libraryGroupErrors } = await importStrapiEntries<Key>( type StrapiGroupSubtypes = {
{ slug: string;
strapi: { titles: { title: string; language: StrapiLanguage }[];
collection: "group-subtypes", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: libraryGroupCount, errors: libraryGroupErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiGroupSubtypes
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "LibraryGroup", collection: "group-subtypes",
translations: titles.map(({ title, language }) => ({ 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, name: title,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
} user: req.user,
); });
const { count: libraryTextualCount, errors: libraryTextualErrors } = type StrapiTextualSubtypes = {
await importStrapiEntries<Key>({ slug: string;
strapi: { titles: { title: string; language: StrapiLanguage }[];
collection: "textual-subtypes", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: libraryTextualCount, errors: libraryTextualErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiTextualSubtypes
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "LibraryTextual", collection: "textual-subtypes",
translations: titles.map(({ title, language }) => ({ 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, name: title,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
}); user: req.user,
});
const { count: libraryVideoCount, errors: libraryVideoErrors } = await importStrapiEntries<Key>( type StrapiVideoSubtypes = {
{ slug: string;
strapi: { titles: { title: string; language: StrapiLanguage }[];
collection: "video-subtypes", };
params: { populate: { titles: { populate: "language" } } },
}, const { count: libraryVideoCount, errors: libraryVideoErrors } = await importStrapiEntries<
payload: { Key,
collection: Collections.Keys, StrapiVideoSubtypes
convert: ({ slug, titles }) => ({ >({
name: slug, strapi: {
type: "LibraryVideo", collection: "video-subtypes",
translations: titles.map(({ title, language }) => ({ 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, name: title,
language: language.data.attributes.code, language: language.data.attributes.code,
})), };
}), }),
}, }),
user: req.user, },
} user: req.user,
); });
const { count: weaponsCount, errors: weaponsErrors } = await importStrapiEntries<Key>({ type StrapiWeaponTypes = {
slug: string;
translations: { name?: string; language: StrapiLanguage }[];
};
const { count: weaponsCount, errors: weaponsErrors } = await importStrapiEntries<
Key,
StrapiWeaponTypes
>({
strapi: { strapi: {
collection: "weapon-story-types", collection: "weapon-story-types",
params: { populate: { translations: { populate: "language" } } }, params: { populate: { translations: { populate: "language" } } },
@ -238,16 +338,22 @@ export const importFromStrapi: CollectionConfig["endpoints"][number] = {
convert: ({ slug, translations }) => ({ convert: ({ slug, translations }) => ({
name: slug, name: slug,
type: "Weapons", type: "Weapons",
translations: translations.map(({ name, language }) => ({ translations: translations.map(({ name, language }) => {
name, if (isUndefined(language.data))
language: language.data.attributes.code, 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, user: req.user,
}); });
const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings({ const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings<Key, Key>({
strapi: { collection: "website-interfaces", params: { populate: "ui_language" } }, strapi: { collection: "website-interfaces", params: { populate: "ui_language" } },
payload: { collection: Collections.Keys, convert: (strapiObject) => strapiObject }, payload: { collection: Collections.Keys, convert: (strapiObject) => strapiObject },
user: req.user, user: req.user,

View File

@ -1,5 +1,5 @@
import { text } from "payload/dist/fields/validations"; import { text } from "payload/dist/fields/validations";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { publicAccess } from "../../accesses/publicAccess"; import { publicAccess } from "../../accesses/publicAccess";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";

View File

@ -2,7 +2,12 @@ import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Language } from "../../../types/collections"; import { Language } from "../../../types/collections";
export const importFromStrapi = createStrapiImportEndpoint<Language>({ type StrapiLanguage = {
name: string;
code: string;
};
export const importFromStrapi = createStrapiImportEndpoint<Language, StrapiLanguage>({
strapi: { strapi: {
collection: "languages", collection: "languages",
params: {}, params: {},

View File

@ -1,3 +1,4 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { import {
CollectionGroups, CollectionGroups,
Collections, Collections,
@ -18,7 +19,6 @@ import { LibraryItem } from "../../types/collections";
import { isDefined } from "../../utils/asserts"; import { isDefined } from "../../utils/asserts";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { RowLabel } from "./components/RowLabel"; import { RowLabel } from "./components/RowLabel";
import { getBySlug } from "./endpoints/getBySlug";
const fields = { const fields = {
status: "_status", status: "_status",
@ -139,7 +139,6 @@ export const LibraryItems = buildVersionedCollectionConfig({
}, },
preview: (doc) => `https://accords-library.com/library/${doc.slug}`, preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
}, },
endpoints: [getBySlug],
fields: [ fields: [
{ {
name: fields.itemType, name: fields.itemType,
@ -480,7 +479,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
"Make sure the page number corresponds to the page number written on \ "Make sure the page number corresponds to the page number written on \
the scan. You can use negative page numbers if necessary.", the scan. You can use negative page numbers if necessary.",
components: { components: {
RowLabel: ({ data }) => RowLabel(data), RowLabel: ({ data }: RowLabelArgs) => RowLabel(data),
}, },
}, },
fields: [ fields: [

View File

@ -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<LibraryItem, "size" | "price" | "scans" | "id"> & {
size?: Omit<LibraryItem["size"][number], "id">;
price?: Omit<LibraryItem["price"][number], "id" | "currency"> & { currency: string };
scans?: Omit<LibraryItem["scans"][number], "id" | "obi" | "cover" | "dustjacket"> & {
obi: Omit<LibraryItem["scans"][number]["obi"][number], "id">;
cover: Omit<LibraryItem["scans"][number]["obi"][number], "id">;
dustjacket: Omit<LibraryItem["scans"][number]["obi"][number], "id">;
};
};
export const getBySlug = createGetByEndpoint<LibraryItem, Partial<ProcessedLibraryItem>>(
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 = <T extends { id?: string }>(group: T[] | null | undefined) => {
if (!group || group.length === 0) return undefined;
const { id, ...otherProps } = group[0];
return otherProps;
};

View File

@ -8,7 +8,6 @@ import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { isDefined, isUndefined } from "../../utils/asserts"; import { isDefined, isUndefined } from "../../utils/asserts";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -57,9 +56,6 @@ export const Posts = buildVersionedCollectionConfig({
}, },
preview: (doc) => `https://accords-library.com/news/${doc.slug}`, preview: (doc) => `https://accords-library.com/news/${doc.slug}`,
}, },
hooks: {
beforeValidate: [removeTranslatorsForTranscripts],
},
fields: [ fields: [
{ {
type: "row", type: "row",
@ -110,6 +106,15 @@ export const Posts = buildVersionedCollectionConfig({
type: "relationship", type: "relationship",
relationTo: Collections.Recorders, relationTo: Collections.Recorders,
hasMany: true, hasMany: true,
hooks: {
beforeChange: [
({ siblingData }) => {
if (siblingData.language === siblingData.sourceLanguage) {
delete siblingData.translators;
}
},
],
},
admin: { admin: {
condition: (_, siblingData) => { condition: (_, siblingData) => {
if ( if (

View File

@ -1,14 +0,0 @@
import { CollectionBeforeValidateHook } from "payload/types";
import { Post } from "../../../types/collections";
export const removeTranslatorsForTranscripts: CollectionBeforeValidateHook<Post> = async ({
data: { translations, ...data },
}) => ({
...data,
translations: translations?.map(({ translators, ...translation }) => {
if (translation.language === translation.sourceLanguage) {
return { ...translation, translators: [] };
}
return { ...translation, translators };
}),
});

View File

@ -1,5 +1,6 @@
import { mustBeAdmin as mustBeAdminForCollections } from "../../accesses/collections/mustBeAdmin";
import { mustBeAdminOrSelf } from "../../accesses/collections/mustBeAdminOrSelf"; 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 { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, RecordersRoles } from "../../constants"; import { CollectionGroups, Collections, RecordersRoles } from "../../constants";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
@ -55,7 +56,6 @@ export const Recorders = buildCollectionConfig({
label: "∅ Role", label: "∅ Role",
filter: { where: { role: { not_in: Object.keys(RecordersRoles).join(",") } } }, filter: { where: { role: { not_in: Object.keys(RecordersRoles).join(",") } } },
}, },
,
], ],
[{ label: "Anonymized", filter: { where: { anonymize: { equals: true } } } }], [{ label: "Anonymized", filter: { where: { anonymize: { equals: true } } } }],
], ],
@ -65,10 +65,10 @@ export const Recorders = buildCollectionConfig({
}, },
auth: true, auth: true,
access: { access: {
unlock: mustBeAdmin, unlock: mustBeAdminForCollections,
update: mustBeAdminOrSelf, update: mustBeAdminOrSelf,
delete: mustBeAdmin, delete: mustBeAdminForCollections,
create: mustBeAdmin, create: mustBeAdminForCollections,
}, },
hooks: { hooks: {
beforeLogin: [beforeLoginMustHaveAtLeastOneRole], beforeLogin: [beforeLoginMustHaveAtLeastOneRole],
@ -111,14 +111,14 @@ export const Recorders = buildCollectionConfig({
description: description:
"A short personal description about you or your involvement with this project or the franchise", "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, name: fields.role,
type: "select", type: "select",
access: { access: {
update: mustBeAdmin, update: mustBeAdminForFields,
create: mustBeAdmin, create: mustBeAdminForFields,
}, },
hasMany: true, hasMany: true,
options: Object.entries(RecordersRoles).map(([value, label]) => ({ options: Object.entries(RecordersRoles).map(([value, label]) => ({

View File

@ -2,10 +2,21 @@ import payload from "payload";
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Recorder } from "../../../types/collections"; 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 { uploadStrapiImage } from "../../../utils/localApi";
import { PayloadCreateData } from "../../../utils/types";
export const importFromStrapi = createStrapiImportEndpoint<Recorder>({ 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<Recorder, StrapiRecorder>({
strapi: { strapi: {
collection: "recorders", collection: "recorders",
params: { params: {
@ -27,10 +38,15 @@ export const importFromStrapi = createStrapiImportEndpoint<Recorder>({
anonymize, anonymize,
languages: languages.data?.map((language) => language.attributes.code), languages: languages.data?.map((language) => language.attributes.code),
avatar: avatarId, avatar: avatarId,
biographies: bios?.map(({ language, bio }) => ({ biographies: bios?.map(({ language, bio }) => {
language: language.data.attributes.code, if (isUndefined(language.data))
biography: bio, 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 }); await payload.create({ collection: Collections.Recorders, data, user });

View File

@ -1,5 +1,5 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections, VideoSources } from "../../constants"; import { CollectionGroups, Collections, VideoSources } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";

View File

@ -2,9 +2,26 @@ import payload from "payload";
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Video, VideosChannel } from "../../../types/collections"; 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<Video>({ type StapiVideo = {
uid: string;
title: string;
description: string;
published_date: {
year?: number;
month?: number;
day?: number;
};
views: number;
likes: number;
source?: "YouTube" | "NicoNico" | "Tumblr";
gone: boolean;
channel: { data?: { attributes: { uid: string; title: string; subscribers: number } } };
};
export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
strapi: { strapi: {
collection: "videos", collection: "videos",
params: { populate: "published_date,channel" }, params: { populate: "published_date,channel" },
@ -25,6 +42,9 @@ export const importFromStrapi = createStrapiImportEndpoint<Video>({
}, },
user 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");
try { try {
const videoChannel: PayloadCreateData<VideosChannel> = { const videoChannel: PayloadCreateData<VideosChannel> = {
uid: channel.data.attributes.uid, uid: channel.data.attributes.uid,

View File

@ -1,5 +1,5 @@
import { CollectionConfig } from "payload/types"; import { CollectionConfig } from "payload/types";
import { mustBeAdmin } from "../../accesses/mustBeAdmin"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
import { CollectionGroups, Collections } from "../../constants"; import { CollectionGroups, Collections } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { importFromStrapi } from "./endpoints/importFromStrapi"; import { importFromStrapi } from "./endpoints/importFromStrapi";

View File

@ -2,7 +2,13 @@ import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { VideosChannel } from "../../../types/collections"; import { VideosChannel } from "../../../types/collections";
export const importFromStrapi = createStrapiImportEndpoint<VideosChannel>({ type StrapiVideoChannel = {
uid: string;
title: string;
subscribers: number;
};
export const importFromStrapi = createStrapiImportEndpoint<VideosChannel, StrapiVideoChannel>({
strapi: { strapi: {
collection: "video-channels", collection: "video-channels",
params: {}, params: {},

View File

@ -1,3 +1,4 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { CollectionGroups, Collections, KeysTypes } from "../../constants"; import { CollectionGroups, Collections, KeysTypes } from "../../constants";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField"; import { keysField } from "../../fields/keysField/keysField";
@ -78,7 +79,7 @@ export const Weapons = buildVersionedCollectionConfig({
admin: { admin: {
initCollapsed: true, initCollapsed: true,
components: { components: {
RowLabel: ({ data }) => RowLabel: ({ data }: RowLabelArgs) =>
AppearanceRowLabel({ keyIds: data[fields.appearancesCategories] ?? [] }), AppearanceRowLabel({ keyIds: data[fields.appearancesCategories] ?? [] }),
}, },
}, },

View File

@ -2,11 +2,31 @@ import payload from "payload";
import { Collections } from "../../../constants"; import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Weapon, WeaponsGroup } from "../../../types/collections"; import { Weapon, WeaponsGroup } from "../../../types/collections";
import { isDefined } from "../../../utils/asserts"; import { PayloadCreateData } from "../../../types/payload";
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { findCategory, findWeaponType, uploadStrapiImage } from "../../../utils/localApi"; import { findCategory, findWeaponType, uploadStrapiImage } from "../../../utils/localApi";
import { PayloadCreateData } from "../../../utils/types";
export const importFromStrapi = createStrapiImportEndpoint<Weapon>({ type StrapiWeapon = {
slug: string;
name: { name: string; language: StrapiLanguage }[];
weapon_group: { data?: { attributes: { slug: string } } };
thumbnail: StrapiImage;
type: { data?: { attributes: { slug: string } } };
stories: {
categories: { data: { attributes: { slug: string } }[] };
translations: {
language: StrapiLanguage;
description?: string;
level_1?: string;
level_2?: string;
level_3?: string;
level_4?: string;
}[];
}[];
};
export const importFromStrapi = createStrapiImportEndpoint<Weapon, StrapiWeapon>({
strapi: { strapi: {
collection: "weapon-stories", collection: "weapon-stories",
params: { params: {
@ -23,7 +43,7 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon>({
payload: { payload: {
collection: Collections.Weapons, collection: Collections.Weapons,
import: async ({ slug, type, stories, name: names, weapon_group, thumbnail }, user) => { import: async ({ slug, type, stories, name: names, weapon_group, thumbnail }, user) => {
let groupId: string; let groupId: string | undefined;
if (isDefined(weapon_group.data)) { if (isDefined(weapon_group.data)) {
try { try {
const groupData: PayloadCreateData<WeaponsGroup> = { const groupData: PayloadCreateData<WeaponsGroup> = {
@ -51,6 +71,8 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon>({
image: thumbnail, image: thumbnail,
}); });
if (isUndefined(type.data)) throw new Error("A type is required to create a Weapon");
const data: PayloadCreateData<Weapon> = { const data: PayloadCreateData<Weapon> = {
slug, slug,
type: await findWeaponType(type.data.attributes.slug), type: await findWeaponType(type.data.attributes.slug),
@ -62,19 +84,26 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon>({
categories.data.map(({ attributes }) => findCategory(attributes.slug)) categories.data.map(({ attributes }) => findCategory(attributes.slug))
), ),
translations: translations.map( translations: translations.map(
({ language, description, level_1, level_2, level_3, level_4 }) => ({ ({ language, description, level_1, level_2, level_3, level_4 }) => {
language: language.data?.attributes.code, if (isUndefined(language.data))
sourceLanguage: language.data?.attributes.code, throw new Error("A language is required to create a Weapon translation");
name: names.find( const name = names.find(
(name) => name.language.data?.attributes.code === language.data?.attributes.code (name) => name.language.data?.attributes.code === language.data?.attributes.code
)?.name, )?.name;
description, if (isUndefined(name))
level1: level_1, throw new Error("A name is required to create a Weapon translation");
level2: level_2, return {
level3: level_3, language: language.data?.attributes.code,
level4: level_4, sourceLanguage: language.data?.attributes.code,
transcribers: [user.id], name,
}) description,
level1: level_1,
level2: level_2,
level3: level_3,
level4: level_4,
transcribers: [user.id],
};
}
), ),
})) }))
), ),

View File

@ -1,11 +1,11 @@
import payload from "payload"; import payload from "payload";
import { CollectionConfig } from "payload/types"; import { CollectionEndpoint } from "../types/payload";
export const createGetByEndpoint = <T, R>( export const createGetByEndpoint = <T, R>(
collection: string, collection: string,
attribute: string, attribute: string,
handler: (doc: T) => Promise<R> handler: (doc: T) => Promise<R>
): CollectionConfig["endpoints"][number] => ({ ): CollectionEndpoint => ({
path: `/${attribute}/:${attribute}`, path: `/${attribute}/:${attribute}`,
method: "get", method: "get",
handler: async (req, res) => { handler: async (req, res) => {

View File

@ -1,9 +1,8 @@
import payload from "payload"; import payload from "payload";
import { CollectionConfig } from "payload/types";
import QueryString from "qs"; import QueryString from "qs";
import { Recorder } from "../types/collections"; import { Recorder } from "../types/collections";
import { CollectionEndpoint, PayloadCreateData } from "../types/payload";
import { isDefined } from "../utils/asserts"; import { isDefined } from "../utils/asserts";
import { PayloadCreateData } from "../utils/types";
export const getAllStrapiEntries = async <T>( export const getAllStrapiEntries = async <T>(
collectionSlug: string, collectionSlug: string,
@ -31,26 +30,26 @@ export const getAllStrapiEntries = async <T>(
return result; return result;
}; };
type Params<T> = { type Params<T, S> = {
strapi: { strapi: {
collection: string; collection: string;
params: any; params: any;
}; };
payload: { payload: {
collection: string; collection: string;
import?: (strapiObject: any, user: any) => Promise<void>; import?: (strapiObject: S, user: any) => Promise<void>;
convert?: (strapiObject: any, user: any) => PayloadCreateData<T>; convert?: (strapiObject: S, user: any) => PayloadCreateData<T>;
}; };
}; };
export const importStrapiEntries = async <T>({ export const importStrapiEntries = async <T, S>({
strapi: strapiParams, strapi: strapiParams,
payload: payloadParams, payload: payloadParams,
user, user,
}: Params<T> & { user: Recorder }) => { }: Params<T, S> & { user: Recorder }) => {
const entries = await getAllStrapiEntries<any>(strapiParams.collection, strapiParams.params); const entries = await getAllStrapiEntries<any>(strapiParams.collection, strapiParams.params);
const errors = []; const errors: string[] = [];
await Promise.all( await Promise.all(
entries.map(async ({ attributes, id }) => { entries.map(async ({ attributes, id }) => {
@ -68,7 +67,9 @@ export const importStrapiEntries = async <T>({
} }
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
errors.push(`${e.name} with ${id}`); if (typeof e === "object" && isDefined(e) && "name" in e) {
errors.push(`${e.name} with ${id}`);
}
} }
}) })
); );
@ -76,9 +77,7 @@ export const importStrapiEntries = async <T>({
return { count: entries.length, errors }; return { count: entries.length, errors };
}; };
export const createStrapiImportEndpoint = <T>( export const createStrapiImportEndpoint = <T, S>(params: Params<T, S>): CollectionEndpoint => ({
params: Params<T>
): CollectionConfig["endpoints"][number] => ({
method: "get", method: "get",
path: "/strapi", path: "/strapi",
handler: async (req, res) => { handler: async (req, res) => {

View File

@ -30,7 +30,7 @@ export const backPropagationField = ({
afterRead: [ afterRead: [
...afterRead, ...afterRead,
async ({ data }) => { async ({ data }) => {
if (isNotEmpty(data.id)) { if (isNotEmpty(data?.id)) {
const result = await payload.find({ const result = await payload.find({
collection: params.relationTo, collection: params.relationTo,
where: where(data), where: where(data),

View File

@ -19,5 +19,5 @@ export const keysField = ({
filterOptions: { type: { equals: getKeysTypesKey(relationTo) } }, filterOptions: { type: { equals: getKeysTypesKey(relationTo) } },
}); });
const getKeysTypesKey = (keyType: KeysTypes): string => const getKeysTypesKey = (keyType: KeysTypes): string | undefined =>
Object.entries(KeysTypes).find(([, value]) => value === keyType)[0]; Object.entries(KeysTypes).find(([, value]) => value === keyType)?.[0];

View File

@ -1,3 +1,4 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { array } from "payload/dist/fields/validations"; import { array } from "payload/dist/fields/validations";
import { ArrayField, Field } from "payload/types"; import { ArrayField, Field } from "payload/types";
import { Collections } from "../../constants"; import { Collections } from "../../constants";
@ -118,6 +119,8 @@ const creditFields: Field = {
], ],
}; };
type FieldData = Record<string, any> & { [fieldsNames.language]: string };
export const translatedFields = ({ export const translatedFields = ({
fields, fields,
validate, validate,
@ -129,7 +132,7 @@ export const translatedFields = ({
admin: { admin: {
initCollapsed: true, initCollapsed: true,
components: { components: {
Cell: ({ cellData }) => Cell: ({ cellData }: { cellData: FieldData[] }) =>
Cell({ Cell({
cellData: cellData:
cellData?.map((row) => ({ cellData?.map((row) => ({
@ -137,7 +140,7 @@ export const translatedFields = ({
title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, title: isDefined(useAsTitle) ? row[useAsTitle] : undefined,
})) ?? [], })) ?? [],
}), }),
RowLabel: ({ data }) => RowLabel: ({ data }: RowLabelArgs) =>
RowLabel({ RowLabel({
language: data[fieldsNames.language], language: data[fieldsNames.language],
title: isDefined(useAsTitle) ? data[useAsTitle] : undefined, title: isDefined(useAsTitle) ? data[useAsTitle] : undefined,
@ -162,6 +165,8 @@ export const translatedFields = ({
if (hasDuplicates(languages)) { if (hasDuplicates(languages)) {
return `There cannot be multiple ${otherProps.name} with the same ${fieldsNames.language}`; return `There cannot be multiple ${otherProps.name} with the same ${fieldsNames.language}`;
} }
return true;
}, },
fields: [ fields: [
hasSourceLanguage hasSourceLanguage

View File

@ -4,7 +4,8 @@ import path from "path";
import payload from "payload"; import payload from "payload";
import { Collections, RecordersRoles } from "./constants"; import { Collections, RecordersRoles } from "./constants";
import { Recorder } from "./types/collections"; import { Recorder } from "./types/collections";
import { PayloadCreateData } from "./utils/types"; import { PayloadCreateData } from "./types/payload";
import { isDefined, isUndefined } from "./utils/asserts";
const app = express(); const app = express();
@ -15,6 +16,15 @@ app.get("/", (_, res) => {
const start = async () => { const start = async () => {
// Initialize Payload // Initialize Payload
if (isUndefined(process.env.PAYLOAD_SECRET)) {
throw new Error("Missing required env variable: PAYLOAD_SECRET");
}
if (isUndefined(process.env.MONGODB_URI)) {
throw new Error("Missing required env variable: MONGODB_URI");
}
await payload.init({ await payload.init({
secret: process.env.PAYLOAD_SECRET, secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI, mongoURL: process.env.MONGODB_URI,
@ -24,19 +34,26 @@ const start = async () => {
const recorders = await payload.find({ collection: Collections.Recorders }); const recorders = await payload.find({ collection: Collections.Recorders });
// If no recorders, we seed some initial data // If no recorders, we seed some initial data
if (recorders.docs.length === 0) { if (
payload.logger.info("Seeding some initial data"); isDefined(process.env.SEEDING_ADMIN_EMAIL) &&
const recorder: PayloadCreateData<Recorder> = { isDefined(process.env.SEEDING_ADMIN_PASSWORD) &&
email: process.env.SEEDING_ADMIN_EMAIL, isDefined(process.env.SEEDING_ADMIN_USERNAME)
password: process.env.SEEDING_ADMIN_PASSWORD, ) {
username: process.env.SEEDING_ADMIN_USERNAME, if (recorders.docs.length === 0) {
role: [RecordersRoles.Admin], payload.logger.info("Seeding some initial data");
anonymize: false,
}; const recorder: PayloadCreateData<Recorder> = {
await payload.create({ email: process.env.SEEDING_ADMIN_EMAIL,
collection: Collections.Recorders, password: process.env.SEEDING_ADMIN_PASSWORD,
data: recorder, username: process.env.SEEDING_ADMIN_USERNAME,
}); role: [RecordersRoles.Admin],
anonymize: false,
};
await payload.create({
collection: Collections.Recorders,
data: recorder,
});
}
} }
}, },
}); });

View File

@ -14,7 +14,7 @@ export type CategoryTranslations = {
}[]; }[];
export type RecorderBiographies = { export type RecorderBiographies = {
language: string | Language; language: string | Language;
biography?: string; biography: string;
id?: string; id?: string;
}[]; }[];
export type ContentFoldersTranslation = { export type ContentFoldersTranslation = {

8
src/types/payload.ts Normal file
View File

@ -0,0 +1,8 @@
import { CollectionConfig } from "payload/types";
export type PayloadCreateData<T> = Omit<
T,
"id" | "updatedAt" | "createdAt" | "sizes" | "updatedBy"
>;
export type CollectionEndpoint = NonNullable<CollectionConfig["endpoints"]>[number];

14
src/types/strapi.ts Normal file
View File

@ -0,0 +1,14 @@
export type StrapiLanguage = {
data?: { attributes: { code: string } };
};
export type StrapiImage = {
data?: {
attributes: {
url: string;
mime: string;
name: string;
size: number;
};
};
};

View File

@ -1,5 +1,6 @@
import payload from "payload"; import payload from "payload";
import { Collections, KeysTypes } from "../constants"; import { Collections, KeysTypes } from "../constants";
import { StrapiImage } from "../types/strapi";
import { isDefined } from "./asserts"; import { isDefined } from "./asserts";
export const findWeaponType = async (name: string): Promise<string> => { export const findWeaponType = async (name: string): Promise<string> => {
@ -23,20 +24,10 @@ type UploadStrapiImage = {
collection: Collections; collection: Collections;
}; };
type StrapiImage = {
data?: {
attributes: {
url: string;
mime: string;
name: string;
size: number;
};
};
};
export const uploadStrapiImage = async ({ export const uploadStrapiImage = async ({
collection, collection,
image, image,
}: UploadStrapiImage): Promise<string> => { }: UploadStrapiImage): Promise<string | undefined> => {
if (isDefined(image.data)) { if (isDefined(image.data)) {
const url = `${process.env.STRAPI_URI}${image.data.attributes.url}`; const url = `${process.env.STRAPI_URI}${image.data.attributes.url}`;

View File

@ -1,4 +1,5 @@
import { Block, BlockField } from "payload/types"; import { Block, BlockField } from "payload/types";
import { capitalize } from "./string";
const isDefined = <T>(value: T | null | undefined): value is T => const isDefined = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined; value !== null && value !== undefined;
@ -26,11 +27,6 @@ export const generateBlocks = <T extends string>(blocksConfig: BlocksConfig<T>):
recursionFieldName in block; recursionFieldName in block;
const getInterfaceName = (parents: T[], currentBlockName: T): string => { const getInterfaceName = (parents: T[], currentBlockName: T): string => {
const capitalize = (text: string): string => {
if (text.length === 0) return text;
const [firstLetter, ...rest] = text;
return [firstLetter.toUpperCase(), ...rest].join("");
};
return [...parents, currentBlockName] return [...parents, currentBlockName]
.map((blockName) => blocksConfig[blockName].block.slug) .map((blockName) => blocksConfig[blockName].block.slug)
.map(capitalize) .map(capitalize)

View File

@ -1,13 +1,15 @@
import tags from "language-tags"; import tags from "language-tags";
import { isUndefined } from "./asserts";
export const shortenEllipsis = (text: string, length: number): string => export const shortenEllipsis = (text: string, length: number): string =>
text.length - 3 > length ? `${text.substring(0, length)}...` : text; text.length - 3 > length ? `${text.substring(0, length)}...` : text;
export const formatLanguageCode = (code: string): string => export const formatLanguageCode = (code: string): string =>
tags(code).valid() ? tags(code).language().descriptions()[0] : code; tags(code).valid() ? tags(code).language()?.descriptions()[0] ?? code : code;
export const capitalize = (string: string): string => { export const capitalize = (string: string): string => {
const [firstLetter, ...otherLetters] = string; const [firstLetter, ...otherLetters] = string;
if (isUndefined(firstLetter)) return "";
return [firstLetter.toUpperCase(), ...otherLetters].join(""); return [firstLetter.toUpperCase(), ...otherLetters].join("");
}; };

View File

@ -1,4 +0,0 @@
export type PayloadCreateData<T> = Omit<
T,
"id" | "updatedAt" | "createdAt" | "sizes" | "updatedBy"
>;

View File

@ -1,18 +1,116 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", /* Visit https://aka.ms/tsconfig to read more about this file */
"moduleResolution": "NodeNext",
"lib": ["dom", "dom.iterable", "esnext"], /* Projects */
"allowJs": true, // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
"strict": false, // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
"esModuleInterop": true, // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
"skipLibCheck": true, // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
"outDir": "./dist", // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
"rootDir": "./src", // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
"jsx": "react",
/* Language and Environment */
"target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"dom",
"dom.iterable",
"esnext"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
"jsx": "react" /* Specify what JSX code is generated. */,
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"paths": { "paths": {
"payload/generated-types": ["./src/payload-types.ts"] "payload/generated-types": ["./src/payload-types.ts"]
} } /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
"noUncheckedIndexedAccess": true /* Add 'undefined' to a type when accessed using an index. */,
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}, },
"include": ["src"], "include": ["src"],
"exclude": ["node_modules", "dist", "build"], "exclude": ["node_modules", "dist", "build"],