Streamlined collection config

This commit is contained in:
DrMint 2023-07-23 01:32:32 +02:00
parent 0a45ebb134
commit 65286f0c66
21 changed files with 1074 additions and 997 deletions

View File

@ -1,9 +1,8 @@
import { CollectionConfig } from "payload/types";
import { slugField } from "../../fields/slugField/slugField";
import { CollectionGroup, KeysTypes } from "../../constants";
import { CollectionGroup } from "../../constants";
import { localizedFields } from "../../fields/translatedFields/translatedFields";
import { collectionSlug } from "../../utils/string";
import { Contents } from "../Contents/Contents";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
slug: "slug",
@ -13,17 +12,12 @@ const fields = {
contents: "contents",
} as const satisfies Record<string, string>;
const labels = {
export const ContentFolders = buildCollectionConfig(
{
singular: "Content Folder",
plural: "Content Folders",
} as const satisfies { singular: string; plural: string };
const slug = collectionSlug(labels.plural);
export const ContentFolders: CollectionConfig = {
slug,
labels,
typescript: { interface: labels.singular },
},
({ slug }) => ({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
@ -62,4 +56,5 @@ export const ContentFolders: CollectionConfig = {
],
},
],
};
})
);

View File

@ -1,6 +1,5 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
filename: "filename",
@ -8,21 +7,17 @@ const fields = {
filesize: "filesize",
} as const satisfies Record<string, string>;
const labels = {
export const ContentThumbnails = buildCollectionConfig(
{
singular: "Content Thumbnail",
plural: "Content Thumbnails",
} as const satisfies { singular: string; plural: string };
export const ContentThumbnails: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
({ labels }) => ({
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroup.Media,
},
upload: {
staticDir: `../uploads/${labels.plural}`,
mimeTypes: ["image/*"],
@ -47,6 +42,6 @@ export const ContentThumbnails: CollectionConfig = {
},
],
},
fields: [],
};
})
);

View File

@ -1,5 +1,3 @@
import { CollectionConfig } from "payload/types";
import { collectionSlug } from "../../utils/string";
import { CollectionGroup, FileTypes, KeysTypes } from "../../constants";
import { slugField } from "../../fields/slugField/slugField";
import { imageField } from "../../fields/imageField/imageField";
@ -10,6 +8,7 @@ import { isDefined } from "../../utils/asserts";
import { fileField } from "../../fields/fileField/fileField";
import { contentBlocks } from "./Blocks/blocks";
import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
const fields = {
slug: "slug",
@ -31,20 +30,20 @@ const fields = {
audio: "audio",
audioNotes: "videoNotes",
status: "status",
updatedBy: "updatedBy",
} as const satisfies Record<string, string>;
const labels = {
export const Contents = buildVersionedCollectionConfig(
{
singular: "Content",
plural: "Contents",
} as const satisfies { singular: string; plural: string };
export const Contents: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
() => ({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
description:
"All the contents (textual, audio, and video) from the Library or other online sources.",
defaultColumns: [
fields.slug,
fields.thumbnail,
@ -56,8 +55,6 @@ export const Contents: CollectionConfig = {
group: CollectionGroup.Collections,
preview: (doc) => `https://accords-library.com/contents/${doc.slug}`,
},
timestamps: true,
versions: { drafts: { autosave: true } },
fields: [
{
type: "row",
@ -214,4 +211,5 @@ export const Contents: CollectionConfig = {
],
}),
],
};
})
);

View File

@ -1,27 +1,22 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup, FileTypes } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
filename: "filename",
type: "type",
} as const satisfies Record<string, string>;
const labels = {
export const Files = buildCollectionConfig(
{
singular: "File",
plural: "Files",
} as const satisfies { singular: string; plural: string };
export const Files: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
() => ({
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroup.Media,
},
fields: [
{
name: fields.filename,
@ -35,4 +30,5 @@ export const Files: CollectionConfig = {
options: Object.entries(FileTypes).map(([value, label]) => ({ label, value })),
},
],
};
})
);

View File

@ -2,9 +2,9 @@ import { CollectionConfig } from "payload/types";
import { slugField } from "../../fields/slugField/slugField";
import { CollectionGroup, KeysTypes } from "../../constants";
import { localizedFields } from "../../fields/translatedFields/translatedFields";
import { collectionSlug } from "../../utils/string";
import { Key } from "../../types/collections";
import { isDefined } from "../../utils/asserts";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
slug: "slug",
@ -14,17 +14,14 @@ const fields = {
short: "short",
} as const satisfies Record<string, string>;
const labels = {
singular: "Key",
plural: "Keys",
} as const satisfies { singular: string; plural: string };
const keysTypesWithShort: (keyof typeof KeysTypes)[] = ["Categories", "GamePlatforms"];
export const Keys: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
export const Keys: CollectionConfig = buildCollectionConfig(
{
singular: "Key",
plural: "Keys",
},
() => ({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
@ -66,4 +63,5 @@ export const Keys: CollectionConfig = {
],
}),
],
};
})
);

View File

@ -1,21 +1,17 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../constants";
import { collectionSlug } from "../utils/string";
import { buildCollectionConfig } from "../utils/collectionConfig";
const fields = {
id: "id",
name: "name",
} as const satisfies Record<string, string>;
const labels = {
export const Languages = buildCollectionConfig(
{
singular: "Language",
plural: "Languages",
} as const satisfies { singular: string; plural: string };
export const Languages: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
() => ({
defaultSort: fields.name,
admin: {
useAsTitle: fields.name,
@ -43,4 +39,5 @@ export const Languages: CollectionConfig = {
required: true,
},
],
};
})
);

View File

@ -1,6 +1,5 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
filename: "filename",
@ -8,21 +7,17 @@ const fields = {
filesize: "filesize",
} as const satisfies Record<string, string>;
const labels = {
export const LibraryItemThumbnails = buildCollectionConfig(
{
singular: "Library Item Thumbnail",
plural: "Library Item Thumbnails",
} as const satisfies { singular: string; plural: string };
export const LibraryItemThumbnails: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
({ labels }) => ({
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroup.Media,
},
upload: {
staticDir: `../uploads/${labels.plural}`,
mimeTypes: ["image/*"],
@ -59,6 +54,6 @@ export const LibraryItemThumbnails: CollectionConfig = {
},
],
},
fields: [],
};
})
);

View File

@ -1,4 +1,3 @@
import { CollectionConfig } from "payload/types";
import {
CollectionGroup,
KeysTypes,
@ -8,12 +7,12 @@ import {
} from "../../constants";
import { slugField } from "../../fields/slugField/slugField";
import { imageField } from "../../fields/imageField/imageField";
import { collectionSlug } from "../../utils/string";
import { isDefined, isUndefined } from "../../utils/asserts";
import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails";
import { LibraryItem } from "../../types/collections";
import { Keys } from "../Keys/Keys";
import { Languages } from "../Languages";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
const fields = {
status: "status",
@ -42,11 +41,6 @@ const fields = {
audioSubtype: "audioSubtype",
} as const satisfies Record<string, string>;
const labels = {
singular: "Library Item",
plural: "Library Items",
} as const satisfies { singular: string; plural: string };
const validateSizeValue = (value?: number) => {
if (isDefined(value) && value <= 0) return "This value must be greater than 0";
return true;
@ -58,19 +52,22 @@ const validateRequiredSizeValue = (value?: number) => {
return true;
};
export const LibraryItems: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
export const LibraryItems = buildVersionedCollectionConfig(
{
singular: "Library Item",
plural: "Library Items",
},
() => ({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
description:
"A comprehensive list of all Yokoverses side materials (books, novellas, artbooks, \
stage plays, manga, drama CDs, and comics).",
defaultColumns: [fields.slug, fields.thumbnail, fields.status],
group: CollectionGroup.Collections,
preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
},
timestamps: true,
versions: { drafts: { autosave: true } },
fields: [
{
type: "row",
@ -270,4 +267,5 @@ export const LibraryItems: CollectionConfig = {
},
},
],
};
})
);

View File

@ -1,6 +1,5 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
filename: "filename",
@ -8,21 +7,17 @@ const fields = {
filesize: "filesize",
} as const satisfies Record<string, string>;
const labels = {
export const PostThumbnails = buildCollectionConfig(
{
singular: "Post Thumbnail",
plural: "Post Thumbnails",
} as const satisfies { singular: string; plural: string };
export const PostThumbnails: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
({ labels }) => ({
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroup.Media,
},
upload: {
staticDir: `../uploads/${labels.plural}`,
mimeTypes: ["image/*"],
@ -47,6 +42,6 @@ export const PostThumbnails: CollectionConfig = {
},
],
},
fields: [],
};
})
);

View File

@ -1,4 +1,3 @@
import { CollectionConfig } from "payload/types";
import { slugField } from "../../fields/slugField/slugField";
import { imageField } from "../../fields/imageField/imageField";
import { CollectionGroup, KeysTypes } from "../../constants";
@ -7,8 +6,8 @@ import { localizedFields } from "../../fields/translatedFields/translatedFields"
import { isDefined, isUndefined } from "../../utils/asserts";
import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate";
import { Keys } from "../Keys/Keys";
import { collectionSlug } from "../../utils/string";
import { PostThumbnails } from "../PostThumbnails/PostThumbnails";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
const fields = {
slug: "slug",
@ -26,18 +25,18 @@ const fields = {
proofreaders: "proofreaders",
} as const satisfies Record<string, string>;
const labels = {
export const Posts = buildVersionedCollectionConfig(
{
singular: "Post",
plural: "Posts",
} as const satisfies { singular: string; plural: string };
export const Posts: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
() => ({
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
description:
"News articles written by our Recorders! Here you will find announcements about \
new merch/items releases, guides, theories, unboxings, showcases...",
defaultColumns: [fields.slug, fields.thumbnail, fields.categories],
group: CollectionGroup.Collections,
preview: (doc) => `https://accords-library.com/news/${doc.slug}`,
@ -45,8 +44,6 @@ export const Posts: CollectionConfig = {
hooks: {
beforeValidate: [removeTranslatorsForTranscripts],
},
timestamps: true,
versions: { drafts: { autosave: true } },
fields: [
{
type: "row",
@ -110,7 +107,10 @@ export const Posts: CollectionConfig = {
width: "50%",
},
validate: (translators, { siblingData }) => {
if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) {
if (
isUndefined(siblingData.language) ||
isUndefined(siblingData.sourceLanguage)
) {
return true;
}
if (siblingData.language === siblingData.sourceLanguage) {
@ -155,4 +155,5 @@ export const Posts: CollectionConfig = {
},
},
],
};
})
);

View File

@ -5,7 +5,7 @@ export const removeTranslatorsForTranscripts: CollectionBeforeValidateHook<Post>
data: { translations, ...data },
}) => ({
...data,
translations: translations.map(({ translators, ...translation }) => {
translations: translations?.map(({ translators, ...translation }) => {
if (translation.language === translation.sourceLanguage) {
return { ...translation, translators: [] };
}

View File

@ -1,6 +1,5 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
filename: "filename",
@ -8,21 +7,17 @@ const fields = {
filesize: "filesize",
} as const satisfies Record<string, string>;
const labels = {
export const RecorderThumbnails = buildCollectionConfig(
{
singular: "Recorder Thumbnail",
plural: "Recorder Thumbnails",
} as const satisfies { singular: string; plural: string };
export const RecorderThumbnails: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
({ labels }) => ({
defaultSort: fields.filename,
admin: {
useAsTitle: fields.filename,
group: CollectionGroup.Media,
},
upload: {
staticDir: `../uploads/${labels.plural}`,
adminThumbnail: "small",
@ -48,6 +43,6 @@ export const RecorderThumbnails: CollectionConfig = {
},
],
},
fields: [],
};
})
);

View File

@ -1,11 +1,10 @@
import { CollectionConfig } from "payload/types";
import { localizedFields } from "../../fields/translatedFields/translatedFields";
import { Languages } from "../Languages";
import { beforeDuplicate } from "./hooks/beforeDuplicate";
import { CollectionGroup } from "../../constants";
import { collectionSlug } from "../../utils/string";
import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails";
import { imageField } from "../../fields/imageField/imageField";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
username: "username",
@ -16,15 +15,12 @@ const fields = {
avatar: "avatar",
} as const satisfies Record<string, string>;
const labels = {
export const Recorders = buildCollectionConfig(
{
singular: "Recorder",
plural: "Recorders",
} as const satisfies { singular: string; plural: string };
export const Recorders: CollectionConfig = {
slug: collectionSlug(labels.plural),
labels,
typescript: { interface: labels.singular },
},
() => ({
defaultSort: fields.username,
admin: {
useAsTitle: fields.username,
@ -91,4 +87,5 @@ export const Recorders: CollectionConfig = {
},
},
],
};
})
);

View File

@ -1,27 +1,60 @@
import { CollectionConfig } from "payload/types";
import { CollectionGroup } from "../constants";
import { collectionSlug } from "../utils/string";
import { CollectionGroup, UserRoles } from "../constants";
import { Recorders } from "./Recorders/Recorders";
import { buildCollectionConfig } from "../utils/collectionConfig";
const fields = {
recorder: "recorder",
name: "name",
email: "email",
role: "role",
} as const satisfies Record<string, string>;
const labels = {
export const Users = buildCollectionConfig(
{
singular: "User",
plural: "Users",
} as const satisfies { singular: string; plural: string };
export const Users: CollectionConfig = {
slug: collectionSlug(labels.plural),
},
() => ({
auth: true,
labels,
typescript: { interface: labels.singular },
defaultSort: fields.email,
defaultSort: fields.recorder,
admin: {
useAsTitle: fields.email,
defaultColumns: [fields.email],
useAsTitle: fields.name,
defaultColumns: [fields.recorder, fields.name, fields.email, fields.role],
group: CollectionGroup.Administration,
},
timestamps: false,
fields: [],
};
fields: [
{
type: "row",
fields: [
{
name: fields.recorder,
type: "relationship",
relationTo: Recorders.slug,
required: true,
admin: { width: "33%" },
},
{
name: fields.name,
type: "text",
required: true,
unique: true,
admin: { width: "33%" },
},
{
name: fields.role,
required: true,
defaultValue: [UserRoles.Recorder],
type: "select",
hasMany: true,
options: Object.entries(UserRoles).map(([value, label]) => ({
label,
value,
})),
admin: { width: "33%" },
},
],
},
],
})
);

View File

@ -42,3 +42,8 @@ export enum LibraryItemsTextualPageOrders {
LeftToRight = "Left to right",
RightToLeft = "Right to left",
}
export enum UserRoles {
Admin = "Admin",
Recorder = "Recorder",
}

View File

@ -1,11 +1,13 @@
import { Props } from "payload/components/views/Cell";
import { useState, useEffect } from "react";
import React from "react";
import { isUndefined } from "../../utils/asserts";
export const Cell = ({ cellData, field }: Props): JSX.Element => {
const [imageURL, setImageURL] = useState<string>();
useEffect(() => {
const fetchUrl = async () => {
if (isUndefined(cellData)) return;
if (typeof cellData !== "string") return;
if (field.type !== "upload") return;
const result = await (await fetch(`/api/${field.relationTo}/${cellData}`)).json();

View File

@ -44,10 +44,11 @@ export const localizedFields = ({
components: {
Cell: ({ cellData }) =>
Cell({
cellData: cellData.map((row) => ({
cellData:
cellData?.map((row) => ({
language: row.language,
title: isDefined(useAsTitle) ? row[useAsTitle] : undefined,
})),
})) ?? [],
}),
RowLabel: ({ data }) =>
RowLabel({

View File

@ -93,6 +93,7 @@ export interface LibraryItem {
}[];
};
releaseDate?: string;
lastModifiedBy: string | User;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
@ -154,6 +155,57 @@ export interface Language {
id: string;
name: string;
}
export interface User {
id: string;
recorder: string | Recorder;
name: string;
role: ("Admin" | "Recorder")[];
email: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
salt?: string;
hash?: string;
loginAttempts?: number;
lockUntil?: string;
password?: string;
}
export interface Recorder {
id: string;
username: string;
avatar?: string | RecorderThumbnail;
languages?: string[] | Language[];
biographies?: RecorderBiographies;
anonymize: boolean;
}
export interface RecorderThumbnail {
id: string;
updatedAt: string;
createdAt: string;
url?: string;
filename?: string;
mimeType?: string;
filesize?: number;
width?: number;
height?: number;
sizes?: {
og?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
small?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
}
export interface Content {
id: string;
slug: string;
@ -188,6 +240,7 @@ export interface Content {
audio?: string | File;
id?: string;
}[];
lastModifiedBy: string | User;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
@ -221,43 +274,6 @@ export interface ContentThumbnail {
};
};
}
export interface Recorder {
id: string;
username: string;
avatar?: string | RecorderThumbnail;
languages?: string[] | Language[];
biographies?: RecorderBiographies;
anonymize: boolean;
}
export interface RecorderThumbnail {
id: string;
updatedAt: string;
createdAt: string;
url?: string;
filename?: string;
mimeType?: string;
filesize?: number;
width?: number;
height?: number;
sizes?: {
og?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
small?: {
url?: string;
width?: number;
height?: number;
mimeType?: string;
filesize?: number;
filename?: string;
};
};
}
export interface TextBlock {
content: {
[k: string]: unknown;
@ -511,6 +527,7 @@ export interface Post {
}[];
publishedDate: string;
hidden?: boolean;
lastModifiedBy: string | User;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
@ -544,14 +561,3 @@ export interface PostThumbnail {
};
};
}
export interface User {
id: string;
email: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
salt?: string;
hash?: string;
loginAttempts?: number;
lockUntil?: string;
password?: string;
}

View File

@ -0,0 +1,22 @@
import { CollectionConfig } from "payload/types";
import slugify from "slugify";
export type BuildCollectionConfig = Omit<CollectionConfig, "slug" | "typescript" | "labels">;
export type GenerationFunctionProps = {
labels: { singular: string; plural: string };
slug: string;
};
export const buildCollectionConfig = (
labels: { singular: string; plural: string },
generationFunction: (props: GenerationFunctionProps) => BuildCollectionConfig
): CollectionConfig => {
const slug = slugify(labels.plural, { lower: true, strict: true, trim: true });
const config = generationFunction({ labels, slug });
return {
...config,
slug,
typescript: { interface: labels.singular },
};
};

View File

@ -1,11 +1,7 @@
import ISO6391 from "iso-639-1";
import slugify from "slugify";
export const shortenEllipsis = (text: string, length: number): string =>
text.length - 3 > length ? `${text.substring(0, length)}...` : text;
export const formatLanguageCode = (code: string): string =>
ISO6391.validate(code) ? ISO6391.getName(code) : code;
export const collectionSlug = (text: string): string =>
slugify(text, { lower: true, strict: true, trim: true });

View File

@ -0,0 +1,52 @@
import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types";
import { Users } from "../collections/Users";
import {
BuildCollectionConfig,
GenerationFunctionProps,
buildCollectionConfig,
} from "./collectionConfig";
const fields = { lastModifiedBy: "lastModifiedBy" };
const beforeChangeLastModifiedBy: CollectionBeforeChangeHook = async ({
data: { updatedBy, ...data },
req,
}) => {
console.log(data, req.user);
return {
...data,
[fields.lastModifiedBy]: req.user.id,
};
};
const lastModifiedByField = (): RelationshipField => ({
name: fields.lastModifiedBy,
type: "relationship",
required: true,
relationTo: Users.slug,
admin: { readOnly: true, position: "sidebar" },
});
type BuildVersionedCollectionConfig = Omit<BuildCollectionConfig, "timestamps" | "versions">;
export const buildVersionedCollectionConfig = (
labels: { singular: string; plural: string },
generationFunction: (props: GenerationFunctionProps) => BuildVersionedCollectionConfig
): CollectionConfig => {
const {
hooks: { beforeChange, ...otherHooks } = {},
fields,
...otherParams
} = buildCollectionConfig(labels, generationFunction);
return {
...otherParams,
timestamps: true,
versions: { drafts: { autosave: { interval: 2000 } } },
hooks: {
...otherHooks,
beforeChange: [...(beforeChange ?? []), beforeChangeLastModifiedBy],
},
fields: [...fields, lastModifiedByField()],
};
};