Access control

This commit is contained in:
DrMint 2023-07-26 01:14:10 +02:00
parent 1cda674782
commit e98fabfd0e
32 changed files with 571 additions and 206 deletions

28
package-lock.json generated
View File

@ -21,6 +21,7 @@
"devDependencies": { "devDependencies": {
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.0",
"@types/express": "^4.17.9", "@types/express": "^4.17.9",
"@types/react-router-dom": "^5.3.3",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"nodemon": "^2.0.6", "nodemon": "^2.0.6",
"prettier": "^3.0.0", "prettier": "^3.0.0",
@ -2998,6 +2999,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"dev": true
},
"node_modules/@types/html-minifier-terser": { "node_modules/@types/html-minifier-terser": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@ -3098,6 +3105,27 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
"version": "4.4.6", "version": "4.4.6",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",

View File

@ -30,6 +30,7 @@
"devDependencies": { "devDependencies": {
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.0",
"@types/express": "^4.17.9", "@types/express": "^4.17.9",
"@types/react-router-dom": "^5.3.3",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"nodemon": "^2.0.6", "nodemon": "^2.0.6",
"prettier": "^3.0.0", "prettier": "^3.0.0",

View File

@ -0,0 +1,11 @@
import { Access } from "payload/config";
import { Recorder } from "../../types/collections";
import { RecordersRoles } from "../../constants";
import { isUndefined } from "../../utils/asserts";
export const mustBeAdminOrSelf: Access = ({ req }) => {
const user = req.user as Recorder | undefined;
if (isUndefined(user)) return false;
if (user.role.includes(RecordersRoles.Admin)) return true;
return { id: { equals: user.id } };
};

View File

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

View File

@ -0,0 +1,9 @@
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

@ -22,6 +22,7 @@ export const ContentFolders = buildCollectionConfig(
admin: { admin: {
useAsTitle: fields.slug, useAsTitle: fields.slug,
defaultColumns: [fields.slug, fields.translations], defaultColumns: [fields.slug, fields.translations],
disableDuplicate: true,
group: CollectionGroup.Collections, group: CollectionGroup.Collections,
}, },
timestamps: false, timestamps: false,

View File

@ -16,6 +16,7 @@ export const ContentThumbnails = buildCollectionConfig(
defaultSort: fields.filename, defaultSort: fields.filename,
admin: { admin: {
useAsTitle: fields.filename, useAsTitle: fields.filename,
disableDuplicate: true,
group: CollectionGroup.Media, group: CollectionGroup.Media,
}, },
upload: { upload: {

View File

@ -9,6 +9,9 @@ import { fileField } from "../../fields/fileField/fileField";
import { contentBlocks } from "./Blocks/blocks"; import { contentBlocks } from "./Blocks/blocks";
import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails"; import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -53,6 +56,12 @@ export const Contents = buildVersionedCollectionConfig(
fields.status, fields.status,
], ],
group: CollectionGroup.Collections, group: CollectionGroup.Collections,
hooks: {
beforeDuplicate: beforeDuplicatePiping([
beforeDuplicateUnpublish,
beforeDuplicateAddCopyTo(fields.slug),
]),
},
preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, preview: (doc) => `https://accords-library.com/contents/${doc.slug}`,
}, },
fields: [ fields: [

View File

@ -0,0 +1,39 @@
import { mustBeAdmin } from "../../accesses/mustBeAdmin";
import { CollectionGroup } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
id: "id",
} as const satisfies Record<string, string>;
export const Currencies = buildCollectionConfig(
{
singular: "Currency",
plural: "Currencies",
},
() => ({
defaultSort: fields.id,
admin: {
useAsTitle: fields.id,
defaultColumns: [fields.id],
disableDuplicate: true,
group: CollectionGroup.Meta,
},
access: { create: mustBeAdmin, update: mustBeAdmin },
timestamps: false,
fields: [
{
name: fields.id,
type: "text",
unique: true,
required: true,
validate: (value) => {
if (/^[A-Z]{3}$/g.test(value)) {
return true;
}
return "The code must be a valid ISO 4217 currency code (e.g: EUR, CAD...)";
},
},
],
})
);

View File

@ -15,6 +15,7 @@ export const Files = buildCollectionConfig(
defaultSort: fields.filename, defaultSort: fields.filename,
admin: { admin: {
useAsTitle: fields.filename, useAsTitle: fields.filename,
disableDuplicate: true,
group: CollectionGroup.Media, group: CollectionGroup.Media,
}, },
fields: [ fields: [

View File

@ -5,6 +5,9 @@ import { localizedFields } from "../../fields/translatedFields/translatedFields"
import { Key } from "../../types/collections"; import { Key } from "../../types/collections";
import { isDefined } from "../../utils/asserts"; import { isDefined } from "../../utils/asserts";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { mustBeAdmin } from "../../accesses/mustBeAdmin";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { QuickFilters } from "../../components/QuickFilters";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -27,6 +30,26 @@ export const Keys: CollectionConfig = buildCollectionConfig(
useAsTitle: fields.slug, useAsTitle: fields.slug,
defaultColumns: [fields.slug, fields.type, fields.translations], defaultColumns: [fields.slug, fields.type, fields.translations],
group: CollectionGroup.Meta, group: CollectionGroup.Meta,
components: {
BeforeListTable: [
() =>
QuickFilters({
route: "/admin/collections/keys",
filters: [
{ label: "Wordings", filter: "where[type][equals]=Wordings" },
{ label: "∅ English", filter: "where[translations.language][not_equals]=en" },
{ label: "∅ French", filter: "where[translations.language][not_equals]=fr" },
],
}),
],
},
hooks: {
beforeDuplicate: beforeDuplicateAddCopyTo(fields.slug),
},
},
access: {
create: mustBeAdmin,
delete: mustBeAdmin,
}, },
timestamps: false, timestamps: false,
versions: false, versions: false,

View File

@ -1,5 +1,6 @@
import { CollectionGroup } from "../constants"; import { mustBeAdmin } from "../../accesses/mustBeAdmin";
import { buildCollectionConfig } from "../utils/collectionConfig"; import { CollectionGroup } from "../../constants";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = { const fields = {
id: "id", id: "id",
@ -16,8 +17,10 @@ export const Languages = buildCollectionConfig(
admin: { admin: {
useAsTitle: fields.name, useAsTitle: fields.name,
defaultColumns: [fields.name, fields.id], defaultColumns: [fields.name, fields.id],
disableDuplicate: true,
group: CollectionGroup.Meta, group: CollectionGroup.Meta,
}, },
access: { create: mustBeAdmin, update: mustBeAdmin },
timestamps: false, timestamps: false,
fields: [ fields: [
{ {

View File

@ -16,6 +16,7 @@ export const LibraryItemThumbnails = buildCollectionConfig(
defaultSort: fields.filename, defaultSort: fields.filename,
admin: { admin: {
useAsTitle: fields.filename, useAsTitle: fields.filename,
disableDuplicate: true,
group: CollectionGroup.Media, group: CollectionGroup.Media,
}, },
upload: { upload: {

View File

@ -11,8 +11,13 @@ import { isDefined, isUndefined } from "../../utils/asserts";
import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails"; import { LibraryItemThumbnails } from "../LibraryItemThumbnails/LibraryItemThumbnails";
import { LibraryItem } from "../../types/collections"; import { LibraryItem } from "../../types/collections";
import { Keys } from "../Keys/Keys"; import { Keys } from "../Keys/Keys";
import { Languages } from "../Languages"; import { Languages } from "../Languages/Languages";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { Currencies } from "../Currencies/Currencies";
import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField";
const fields = { const fields = {
status: "status", status: "status",
@ -29,6 +34,9 @@ const fields = {
width: "width", width: "width",
height: "height", height: "height",
thickness: "thickness", thickness: "thickness",
price: "price",
priceAmount: "amount",
priceCurrency: "currency",
releaseDate: "releaseDate", releaseDate: "releaseDate",
itemType: "itemType", itemType: "itemType",
textual: "textual", textual: "textual",
@ -39,19 +47,24 @@ const fields = {
textualLanguages: "languages", textualLanguages: "languages",
audio: "audio", audio: "audio",
audioSubtype: "audioSubtype", audioSubtype: "audioSubtype",
scans: "scans",
scansCover: "cover",
scansCoverFront: "front",
scansCoverSpine: "spine",
scansCoverBack: "back",
scansDustjacket: "dustjacket",
scansDustjacketFront: "front",
scansDustjacketSpine: "spine",
scansDustjacketBack: "back",
scansObibelt: "obibelt",
scansObibeltFront: "front",
scansObibeltSpine: "spine",
scansObibeltBack: "back",
scansPages: "pages",
scansPagesPage: "page",
scansPagesImage: "image",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
const validateSizeValue = (value?: number) => {
if (isDefined(value) && value <= 0) return "This value must be greater than 0";
return true;
};
const validateRequiredSizeValue = (value?: number) => {
if (isUndefined(value)) return "This field is required.";
if (value <= 0) return "This value must be greater than 0.";
return true;
};
export const LibraryItems = buildVersionedCollectionConfig( export const LibraryItems = buildVersionedCollectionConfig(
{ {
singular: "Library Item", singular: "Library Item",
@ -63,9 +76,15 @@ export const LibraryItems = buildVersionedCollectionConfig(
useAsTitle: fields.slug, useAsTitle: fields.slug,
description: description:
"A comprehensive list of all Yokoverses side materials (books, novellas, artbooks, \ "A comprehensive list of all Yokoverses side materials (books, novellas, artbooks, \
stage plays, manga, drama CDs, and comics).", stage plays, manga, drama CDs, and comics).",
defaultColumns: [fields.slug, fields.thumbnail, fields.status], defaultColumns: [fields.slug, fields.thumbnail, fields.status],
group: CollectionGroup.Collections, group: CollectionGroup.Collections,
hooks: {
beforeDuplicate: beforeDuplicatePiping([
beforeDuplicateUnpublish,
beforeDuplicateAddCopyTo(fields.slug),
]),
},
preview: (doc) => `https://accords-library.com/library/${doc.slug}`, preview: (doc) => `https://accords-library.com/library/${doc.slug}`,
}, },
fields: [ fields: [
@ -135,9 +154,115 @@ stage plays, manga, drama CDs, and comics).",
}, },
], ],
}, },
optionalGroupField({
name: fields.scans,
fields: [
optionalGroupField({
name: fields.scansCover,
fields: [
{ {
name: "size", type: "row",
type: "group", fields: [
imageField({
name: fields.scansCoverFront,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansCoverSpine,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansCoverBack,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
],
},
],
}),
optionalGroupField({
name: fields.scansDustjacket,
label: "Dust Jacket",
labels: { singular: "Dust Jacket", plural: "Dust Jackets" },
fields: [
{
type: "row",
fields: [
imageField({
name: fields.scansDustjacketFront,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansDustjacketSpine,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansDustjacketBack,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
],
},
],
}),
optionalGroupField({
name: fields.scansObibelt,
label: "Obi Belt",
labels: { singular: "Obi Belt", plural: "Obi Belts" },
fields: [
{
type: "row",
fields: [
imageField({
name: fields.scansObibeltFront,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansObibeltSpine,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
imageField({
name: fields.scansObibeltBack,
relationTo: LibraryItemThumbnails.slug,
admin: { width: "33%" },
}),
],
},
],
}),
{
name: fields.scansPages,
type: "array",
fields: [
{
type: "row",
fields: [
{
name: fields.scansPagesPage,
type: "number",
required: true,
admin: { width: "33%" },
},
imageField({
name: fields.scansPagesImage,
relationTo: LibraryItemThumbnails.slug,
required: true,
admin: { width: "66%" },
}),
],
},
],
},
],
}),
optionalGroupField({
name: fields.size,
admin: { condition: (data) => !data.digital }, admin: { condition: (data) => !data.digital },
fields: [ fields: [
{ {
@ -146,25 +271,49 @@ stage plays, manga, drama CDs, and comics).",
{ {
name: fields.width, name: fields.width,
type: "number", type: "number",
validate: validateRequiredSizeValue, required: true,
admin: { step: 1, width: "33%", description: "in mm." }, admin: { step: 1, width: "33%", description: "in mm." },
}, },
{ {
name: fields.height, name: fields.height,
type: "number", type: "number",
validate: validateRequiredSizeValue, required: true,
admin: { step: 1, width: "33%", description: "in mm." }, admin: { step: 1, width: "33%", description: "in mm." },
}, },
{ {
name: fields.thickness, name: fields.thickness,
type: "number", type: "number",
validate: validateSizeValue,
admin: { step: 1, width: "33%", description: "in mm." }, admin: { step: 1, width: "33%", description: "in mm." },
}, },
], ],
}, },
], ],
}),
optionalGroupField({
name: fields.price,
admin: { className: "group-array" },
fields: [
{
type: "row",
fields: [
{
name: fields.priceAmount,
type: "number",
required: true,
min: 0,
admin: { width: "50%" },
}, },
{
name: fields.priceCurrency,
type: "relationship",
relationTo: Currencies.slug,
required: true,
admin: { allowCreate: false, width: "50%" },
},
],
},
],
}),
{ {
name: fields.itemType, name: fields.itemType,
type: "radio", type: "radio",

View File

@ -16,6 +16,7 @@ export const PostThumbnails = buildCollectionConfig(
defaultSort: fields.filename, defaultSort: fields.filename,
admin: { admin: {
useAsTitle: fields.filename, useAsTitle: fields.filename,
disableDuplicate: true,
group: CollectionGroup.Media, group: CollectionGroup.Media,
}, },
upload: { upload: {

View File

@ -8,6 +8,9 @@ import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate";
import { Keys } from "../Keys/Keys"; import { Keys } from "../Keys/Keys";
import { PostThumbnails } from "../PostThumbnails/PostThumbnails"; import { PostThumbnails } from "../PostThumbnails/PostThumbnails";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
const fields = { const fields = {
slug: "slug", slug: "slug",
@ -36,9 +39,15 @@ export const Posts = buildVersionedCollectionConfig(
useAsTitle: fields.slug, useAsTitle: fields.slug,
description: description:
"News articles written by our Recorders! Here you will find announcements about \ "News articles written by our Recorders! Here you will find announcements about \
new merch/items releases, guides, theories, unboxings, showcases...", new merch/items releases, guides, theories, unboxings, showcases...",
defaultColumns: [fields.slug, fields.thumbnail, fields.categories], defaultColumns: [fields.slug, fields.thumbnail, fields.categories],
group: CollectionGroup.Collections, group: CollectionGroup.Collections,
hooks: {
beforeDuplicate: beforeDuplicatePiping([
beforeDuplicateUnpublish,
beforeDuplicateAddCopyTo(fields.slug),
]),
},
preview: (doc) => `https://accords-library.com/news/${doc.slug}`, preview: (doc) => `https://accords-library.com/news/${doc.slug}`,
}, },
hooks: { hooks: {

View File

@ -16,6 +16,7 @@ export const RecorderThumbnails = buildCollectionConfig(
defaultSort: fields.filename, defaultSort: fields.filename,
admin: { admin: {
useAsTitle: fields.filename, useAsTitle: fields.filename,
disableDuplicate: true,
group: CollectionGroup.Media, group: CollectionGroup.Media,
}, },
upload: { upload: {

View File

@ -1,10 +1,13 @@
import { localizedFields } from "../../fields/translatedFields/translatedFields"; import { localizedFields } from "../../fields/translatedFields/translatedFields";
import { Languages } from "../Languages"; import { Languages } from "../Languages/Languages";
import { beforeDuplicate } from "./hooks/beforeDuplicate"; import { CollectionGroup, RecordersRoles } from "../../constants";
import { CollectionGroup } from "../../constants";
import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails"; import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails";
import { imageField } from "../../fields/imageField/imageField"; import { imageField } from "../../fields/imageField/imageField";
import { buildCollectionConfig } from "../../utils/collectionConfig"; import { buildCollectionConfig } from "../../utils/collectionConfig";
import { mustBeAdmin } from "../../accesses/mustBeAdmin";
import { mustBeAdminOrSelf } from "../../accesses/collections/mustBeAdminOrSelf";
import { beforeLoginMustHaveAtLeastOneRole } from "./hooks/beforeLoginMustHaveAtLeastOneRole";
import { QuickFilters } from "../../components/QuickFilters";
const fields = { const fields = {
username: "username", username: "username",
@ -13,6 +16,7 @@ const fields = {
biographies: "biographies", biographies: "biographies",
biography: "biography", biography: "biography",
avatar: "avatar", avatar: "avatar",
role: "role",
} as const satisfies Record<string, string>; } as const satisfies Record<string, string>;
export const Recorders = buildCollectionConfig( export const Recorders = buildCollectionConfig(
@ -24,17 +28,43 @@ export const Recorders = buildCollectionConfig(
defaultSort: fields.username, defaultSort: fields.username,
admin: { admin: {
useAsTitle: fields.username, useAsTitle: fields.username,
hooks: { beforeDuplicate },
description: description:
"Recorders are contributors of the Accord's Library project. Create a Recorder here to be able to credit them in other collections", "Recorders are contributors of the Accord's Library project. Ask an admin to create a \
Recorder here to be able to credit them in other collections.",
defaultColumns: [ defaultColumns: [
fields.username, fields.username,
fields.avatar, fields.avatar,
fields.anonymize, fields.anonymize,
fields.biographies, fields.biographies,
fields.languages, fields.languages,
fields.role,
], ],
disableDuplicate: true,
group: CollectionGroup.Meta, group: CollectionGroup.Meta,
components: {
BeforeListTable: [
() =>
QuickFilters({
route: "/admin/collections/recorders",
filters: [
{ label: "Admins", filter: "where[role][equals]=Admin" },
{ label: "Recorders", filter: "where[role][equals]=Recorder" },
{ label: "∅ Role", filter: "where[role][not_in]=Admin,Recorder" },
{ label: "Anonymized", filter: "where[anonymize][equals]=true" },
],
}),
],
},
},
auth: true,
access: {
unlock: mustBeAdmin,
update: mustBeAdminOrSelf,
delete: mustBeAdmin,
create: mustBeAdmin,
},
hooks: {
beforeLogin: [beforeLoginMustHaveAtLeastOneRole],
}, },
timestamps: false, timestamps: false,
fields: [ fields: [
@ -75,6 +105,20 @@ export const Recorders = buildCollectionConfig(
}, },
fields: [{ name: fields.biography, type: "textarea" }], fields: [{ name: fields.biography, type: "textarea" }],
}), }),
{
name: fields.role,
type: "select",
access: {
update: mustBeAdmin,
create: mustBeAdmin,
},
hasMany: true,
options: Object.entries(RecordersRoles).map(([value, label]) => ({
label,
value,
})),
admin: { position: "sidebar" },
},
{ {
name: fields.anonymize, name: fields.anonymize,
type: "checkbox", type: "checkbox",

View File

@ -1,9 +0,0 @@
import { BeforeDuplicate } from "payload/types";
import { Recorder } from "../../../types/collections";
export const beforeDuplicate: BeforeDuplicate<Recorder> = ({ data }) => {
return {
...data,
id: `${data.id}-copy`,
};
};

View File

@ -0,0 +1,7 @@
import { BeforeLoginHook } from "payload/dist/collections/config/types";
export const beforeLoginMustHaveAtLeastOneRole: BeforeLoginHook = ({ user }) => {
if (user.role.length === 0) {
throw new Error("User is not authorized to log-in.");
}
};

View File

@ -1,59 +0,0 @@
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>;
export const Users = buildCollectionConfig(
{
singular: "User",
plural: "Users",
},
() => ({
auth: true,
defaultSort: fields.recorder,
admin: {
useAsTitle: fields.name,
defaultColumns: [fields.recorder, fields.name, fields.email, fields.role],
group: CollectionGroup.Administration,
},
timestamps: false,
fields: [
{
type: "row",
fields: [
{
name: fields.recorder,
type: "relationship",
relationTo: Recorders.slug,
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

@ -0,0 +1,47 @@
import React from "react";
import { styled } from "styled-components";
import { Link } from "react-router-dom";
type Props = {
route: string;
filters: { label: string; filter: string }[];
};
export const QuickFilters = ({ route, filters }: Props) => {
return (
<Container>
<div>Quick Filters:</div>
<FilterContainer>
<FilterCell label="None" to={route} />
{filters.map(({ label, filter }, index) => (
<FilterCell key={index} label={label} to={`${route}?${filter}`} />
))}
</FilterContainer>
</Container>
);
};
type FilterProps = {
label: string;
to: string;
};
const FilterCell = ({ label, to }: FilterProps) => (
<Link className="pill pill--has-action" to={to}>
{label}
</Link>
);
const Container = styled.div`
display: flex;
place-items: center;
gap: 1rem;
margin-top: -1rem;
margin-bottom: 2rem;
`;
const FilterContainer = styled.div`
display: flex;
place-items: center;
gap: 0.5rem;
`;

View File

@ -2,7 +2,6 @@ export enum CollectionGroup {
Collections = "Collections", Collections = "Collections",
Media = "Media", Media = "Media",
Meta = "Meta", Meta = "Meta",
Administration = "Administration",
} }
export enum KeysTypes { export enum KeysTypes {
@ -43,7 +42,12 @@ export enum LibraryItemsTextualPageOrders {
RightToLeft = "Right to left", RightToLeft = "Right to left",
} }
export enum UserRoles { export enum RecordersRoles {
Admin = "Admin", Admin = "Admin",
Recorder = "Recorder", Recorder = "Recorder",
} }
export enum CollectionStatus {
Draft = "draft",
Published = "published",
}

View File

@ -0,0 +1,14 @@
import { ArrayField } from "payload/types";
type Props = Omit<ArrayField, "type" | "maxRows" | "minRows">;
export const optionalGroupField = ({
admin: { className = "", ...otherAdmin } = {},
...otherProps
}: Props): ArrayField => ({
...otherProps,
type: "array",
minRows: 0,
maxRows: 1,
admin: { ...otherAdmin, className: `${className} group-array` },
});

View File

@ -1,7 +1,7 @@
import { ArrayField, Field } from "payload/types"; import { ArrayField, Field } from "payload/types";
import { hasDuplicates } from "../../utils/validation"; import { hasDuplicates } from "../../utils/validation";
import { isDefined, isUndefined } from "../../utils/asserts"; import { isDefined, isUndefined } from "../../utils/asserts";
import { Languages } from "../../collections/Languages"; import { Languages } from "../../collections/Languages/Languages";
import { RowLabel } from "./RowLabel"; import { RowLabel } from "./RowLabel";
import { Cell } from "./Cell"; import { Cell } from "./Cell";

View File

@ -0,0 +1,5 @@
import { BeforeDuplicate } from "payload/types";
export const beforeDuplicateAddCopyTo =
(fieldName: string): BeforeDuplicate =>
({ data }) => ({ ...data, [fieldName]: `${data[fieldName]}-copy` });

View File

@ -0,0 +1,5 @@
import { BeforeDuplicate } from "payload/types";
export const beforeDuplicatePiping = (hooks: BeforeDuplicate[]): BeforeDuplicate => {
return ({ data: initialData }) => hooks.reduce((data, hook) => hook({ data }), initialData);
};

View File

@ -0,0 +1,7 @@
import { BeforeDuplicate } from "payload/types";
import { CollectionStatus } from "../constants";
export const beforeDuplicateUnpublish: BeforeDuplicate = ({ data }) => ({
...data,
_status: CollectionStatus.Draft,
});

View File

@ -1,7 +1,6 @@
import { buildConfig } from "payload/config"; import { buildConfig } from "payload/config";
import path from "path"; import path from "path";
import { Users } from "./collections/Users"; import { Languages } from "./collections/Languages/Languages";
import { Languages } from "./collections/Languages";
import { Recorders } from "./collections/Recorders/Recorders"; import { Recorders } from "./collections/Recorders/Recorders";
import { Posts } from "./collections/Posts/Posts"; import { Posts } from "./collections/Posts/Posts";
import { Keys } from "./collections/Keys/Keys"; import { Keys } from "./collections/Keys/Keys";
@ -15,11 +14,12 @@ import { ContentThumbnails } from "./collections/ContentThumbnails/ContentThumbn
import { ContentFolders } from "./collections/ContentFolders/ContentFolders"; import { ContentFolders } from "./collections/ContentFolders/ContentFolders";
import { Logo } from "./components/Logo"; import { Logo } from "./components/Logo";
import { Icon } from "./components/Icon"; import { Icon } from "./components/Icon";
import { Currencies } from "./collections/Currencies/Currencies";
export default buildConfig({ export default buildConfig({
serverURL: "https://dashboard.accords-library.com", serverURL: "https://dashboard.accords-library.com",
admin: { admin: {
user: Users.slug, user: Recorders.slug,
components: { graphics: { Logo, Icon } }, components: { graphics: { Logo, Icon } },
meta: { meta: {
favicon: "/public/favicon.ico", favicon: "/public/favicon.ico",
@ -39,9 +39,9 @@ export default buildConfig({
PostThumbnails, PostThumbnails,
Files, Files,
Languages, Languages,
Currencies,
Recorders, Recorders,
Keys, Keys,
Users,
], ],
globals: [], globals: [],
telemetry: false, telemetry: false,

View File

@ -45,3 +45,18 @@ html[data-theme="light"] {
--color-base-950: #11100b; --color-base-950: #11100b;
--color-base-1000: #000000; --color-base-1000: #000000;
} }
.field-type.array-field.group-array {
.array-field__header-actions,
.collapsible__drag,
.collapsible__toggle,
.collapsible__header-wrap,
.collapsible__indicator,
.array-actions__add,
.array-actions__duplicate {
display: none;
}
.collapsible__actions-wrap {
z-index: 1;
}
}

View File

@ -25,19 +25,18 @@ export type ContentFoldersTranslation = {
export interface Config { export interface Config {
collections: { collections: {
"library-items": LibraryItem; 'library-items': LibraryItem;
contents: Content; contents: Content;
"content-folders": ContentFolder; 'content-folders': ContentFolder;
posts: Post; posts: Post;
"content-thumbnails": ContentThumbnail; 'content-thumbnails': ContentThumbnail;
"library-item-thumbnails": LibraryItemThumbnail; 'library-item-thumbnails': LibraryItemThumbnail;
"recorder-thumbnails": RecorderThumbnail; 'recorder-thumbnails': RecorderThumbnail;
"post-thumbnails": PostThumbnail; 'post-thumbnails': PostThumbnail;
files: File; files: File;
languages: Language; languages: Language;
recorders: Recorder; recorders: Recorder;
keys: Key; keys: Key;
users: User;
}; };
globals: {}; globals: {};
} }
@ -53,50 +52,55 @@ export interface LibraryItem {
digital: boolean; digital: boolean;
downloadable: boolean; downloadable: boolean;
size?: { size?: {
width?: number; width: number;
height?: number; height: number;
thickness?: number; thickness?: number;
}; id?: string;
itemType?: "Textual" | "Audio" | "Video" | "Game" | "Other"; }[];
price?: {
priceAmount: number;
id?: string;
}[];
itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other';
textual?: { textual?: {
subtype?: subtype?:
| { | {
value: string; value: string;
relationTo: "keys"; relationTo: 'keys';
}[] }[]
| { | {
value: Key; value: Key;
relationTo: "keys"; relationTo: 'keys';
}[]; }[];
languages?: languages?:
| { | {
value: string; value: string;
relationTo: "languages"; relationTo: 'languages';
}[] }[]
| { | {
value: Language; value: Language;
relationTo: "languages"; relationTo: 'languages';
}[]; }[];
pageCount?: number; pageCount?: number;
bindingType?: "Paperback" | "Hardcover"; bindingType?: 'Paperback' | 'Hardcover';
pageOrder?: "LeftToRight" | "RightToLeft"; pageOrder?: 'LeftToRight' | 'RightToLeft';
}; };
audio?: { audio?: {
audioSubtype?: audioSubtype?:
| { | {
value: string; value: string;
relationTo: "keys"; relationTo: 'keys';
}[] }[]
| { | {
value: Key; value: Key;
relationTo: "keys"; relationTo: 'keys';
}[]; }[];
}; };
releaseDate?: string; releaseDate?: string;
lastModifiedBy: string | User; lastModifiedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: "draft" | "published"; _status?: 'draft' | 'published';
} }
export interface LibraryItemThumbnail { export interface LibraryItemThumbnail {
id: string; id: string;
@ -139,27 +143,30 @@ export interface Key {
id: string; id: string;
slug: string; slug: string;
type: type:
| "Contents" | 'Contents'
| "LibraryAudio" | 'LibraryAudio'
| "LibraryVideo" | 'LibraryVideo'
| "LibraryTextual" | 'LibraryTextual'
| "LibraryGroup" | 'LibraryGroup'
| "Library" | 'Library'
| "Weapons" | 'Weapons'
| "GamePlatforms" | 'GamePlatforms'
| "Categories" | 'Categories'
| "Wordings"; | 'Wordings';
translations?: CategoryTranslations; translations?: CategoryTranslations;
} }
export interface Language { export interface Language {
id: string; id: string;
name: string; name: string;
} }
export interface User { export interface Recorder {
id: string; id: string;
recorder: string | Recorder; username: string;
name: string; avatar?: string | RecorderThumbnail;
role: ("Admin" | "Recorder")[]; languages?: string[] | Language[];
biographies?: RecorderBiographies;
role?: ('Admin' | 'Recorder')[];
anonymize: boolean;
email: string; email: string;
resetPasswordToken?: string; resetPasswordToken?: string;
resetPasswordExpiration?: string; resetPasswordExpiration?: string;
@ -169,14 +176,6 @@ export interface User {
lockUntil?: string; lockUntil?: string;
password?: string; password?: string;
} }
export interface Recorder {
id: string;
username: string;
avatar?: string | RecorderThumbnail;
languages?: string[] | Language[];
biographies?: RecorderBiographies;
anonymize: boolean;
}
export interface RecorderThumbnail { export interface RecorderThumbnail {
id: string; id: string;
updatedAt: string; updatedAt: string;
@ -213,15 +212,15 @@ export interface Content {
categories?: categories?:
| { | {
value: string; value: string;
relationTo: "keys"; relationTo: 'keys';
}[] }[]
| { | {
value: Key; value: Key;
relationTo: "keys"; relationTo: 'keys';
}[]; }[];
type?: { type?: {
value: string | Key; value: string | Key;
relationTo: "keys"; relationTo: 'keys';
}; };
translations: { translations: {
language: string | Language; language: string | Language;
@ -240,10 +239,10 @@ export interface Content {
audio?: string | File; audio?: string | File;
id?: string; id?: string;
}[]; }[];
lastModifiedBy: string | User; lastModifiedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: "draft" | "published"; _status?: 'draft' | 'published';
} }
export interface ContentThumbnail { export interface ContentThumbnail {
id: string; id: string;
@ -280,25 +279,19 @@ export interface TextBlock {
}[]; }[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "textBlock"; blockType: 'textBlock';
} }
export interface Section { export interface Section {
content?: (Section_Section | Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Section | Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section { export interface Section_Section {
content?: ( content?: (Section_Section_Section | Section_Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[];
| Section_Section_Section
| Section_Section_Tabs
| TranscriptBlock
| QuoteBlock
| TextBlock
)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section_Section { export interface Section_Section_Section {
content?: ( content?: (
@ -310,25 +303,25 @@ export interface Section_Section_Section {
)[]; )[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section_Section_Section { export interface Section_Section_Section_Section {
content?: (Section_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section_Section_Section_Section { export interface Section_Section_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface TranscriptBlock { export interface TranscriptBlock {
lines: (LineBlock | CueBlock)[]; lines: (LineBlock | CueBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "transcriptBlock"; blockType: 'transcriptBlock';
} }
export interface LineBlock { export interface LineBlock {
content: { content: {
@ -336,13 +329,13 @@ export interface LineBlock {
}[]; }[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "lineBlock"; blockType: 'lineBlock';
} }
export interface CueBlock { export interface CueBlock {
content: string; content: string;
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "cueBlock"; blockType: 'cueBlock';
} }
export interface QuoteBlock { export interface QuoteBlock {
from: string; from: string;
@ -351,120 +344,120 @@ export interface QuoteBlock {
}[]; }[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "quoteBlock"; blockType: 'quoteBlock';
} }
export interface Section_Section_Section_Tabs { export interface Section_Section_Section_Tabs {
tabs?: Section_Section_Section_Tabs_Tab[]; tabs?: Section_Section_Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tabs"; blockType: 'tabs';
} }
export interface Section_Section_Section_Tabs_Tab { export interface Section_Section_Section_Tabs_Tab {
content?: (Section_Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tab"; blockType: 'tab';
} }
export interface Section_Section_Section_Tabs_Tab_Section { export interface Section_Section_Section_Tabs_Tab_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section_Tabs { export interface Section_Section_Tabs {
tabs?: Section_Section_Tabs_Tab[]; tabs?: Section_Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tabs"; blockType: 'tabs';
} }
export interface Section_Section_Tabs_Tab { export interface Section_Section_Tabs_Tab {
content?: (Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tab"; blockType: 'tab';
} }
export interface Section_Section_Tabs_Tab_Section { export interface Section_Section_Tabs_Tab_Section {
content?: (Section_Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Section_Tabs_Tab_Section_Section { export interface Section_Section_Tabs_Tab_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Tabs { export interface Section_Tabs {
tabs?: Section_Tabs_Tab[]; tabs?: Section_Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tabs"; blockType: 'tabs';
} }
export interface Section_Tabs_Tab { export interface Section_Tabs_Tab {
content?: (Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tab"; blockType: 'tab';
} }
export interface Section_Tabs_Tab_Section { export interface Section_Tabs_Tab_Section {
content?: (Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Tabs_Tab_Section_Section { export interface Section_Tabs_Tab_Section_Section {
content?: (Section_Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Section_Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Section_Tabs_Tab_Section_Section_Section { export interface Section_Tabs_Tab_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Tabs { export interface Tabs {
tabs?: Tabs_Tab[]; tabs?: Tabs_Tab[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tabs"; blockType: 'tabs';
} }
export interface Tabs_Tab { export interface Tabs_Tab {
content?: (Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "tab"; blockType: 'tab';
} }
export interface Tabs_Tab_Section { export interface Tabs_Tab_Section {
content?: (Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Tabs_Tab_Section_Section { export interface Tabs_Tab_Section_Section {
content?: (Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Tabs_Tab_Section_Section_Section { export interface Tabs_Tab_Section_Section_Section {
content?: (Tabs_Tab_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (Tabs_Tab_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface Tabs_Tab_Section_Section_Section_Section { export interface Tabs_Tab_Section_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string; id?: string;
blockName?: string; blockName?: string;
blockType: "section"; blockType: 'section';
} }
export interface File { export interface File {
id: string; id: string;
filename: string; filename: string;
type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio"; type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio';
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@ -475,20 +468,20 @@ export interface ContentFolder {
subfolders?: subfolders?:
| { | {
value: string; value: string;
relationTo: "content-folders"; relationTo: 'content-folders';
}[] }[]
| { | {
value: ContentFolder; value: ContentFolder;
relationTo: "content-folders"; relationTo: 'content-folders';
}[]; }[];
contents?: contents?:
| { | {
value: string; value: string;
relationTo: "contents"; relationTo: 'contents';
}[] }[]
| { | {
value: Content; value: Content;
relationTo: "contents"; relationTo: 'contents';
}[]; }[];
} }
export interface Post { export interface Post {
@ -498,20 +491,20 @@ export interface Post {
authors: authors:
| { | {
value: string; value: string;
relationTo: "recorders"; relationTo: 'recorders';
}[] }[]
| { | {
value: Recorder; value: Recorder;
relationTo: "recorders"; relationTo: 'recorders';
}[]; }[];
categories?: categories?:
| { | {
value: string; value: string;
relationTo: "keys"; relationTo: 'keys';
}[] }[]
| { | {
value: Key; value: Key;
relationTo: "keys"; relationTo: 'keys';
}[]; }[];
translations: { translations: {
language: string | Language; language: string | Language;
@ -527,10 +520,10 @@ export interface Post {
}[]; }[];
publishedDate: string; publishedDate: string;
hidden?: boolean; hidden?: boolean;
lastModifiedBy: string | User; lastModifiedBy: string | Recorder;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: "draft" | "published"; _status?: 'draft' | 'published';
} }
export interface PostThumbnail { export interface PostThumbnail {
id: string; id: string;

View File

@ -1,29 +1,26 @@
import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types"; import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types";
import { Users } from "../collections/Users";
import { import {
BuildCollectionConfig, BuildCollectionConfig,
GenerationFunctionProps, GenerationFunctionProps,
buildCollectionConfig, buildCollectionConfig,
} from "./collectionConfig"; } from "./collectionConfig";
import { Recorders } from "../collections/Recorders/Recorders";
const fields = { lastModifiedBy: "lastModifiedBy" }; const fields = { lastModifiedBy: "lastModifiedBy" };
const beforeChangeLastModifiedBy: CollectionBeforeChangeHook = async ({ const beforeChangeLastModifiedBy: CollectionBeforeChangeHook = async ({
data: { updatedBy, ...data }, data: { updatedBy, ...data },
req, req,
}) => { }) => ({
console.log(data, req.user);
return {
...data, ...data,
[fields.lastModifiedBy]: req.user.id, [fields.lastModifiedBy]: req.user.id,
}; });
};
const lastModifiedByField = (): RelationshipField => ({ const lastModifiedByField = (): RelationshipField => ({
name: fields.lastModifiedBy, name: fields.lastModifiedBy,
type: "relationship", type: "relationship",
required: true, required: true,
relationTo: Users.slug, relationTo: Recorders.slug,
admin: { readOnly: true, position: "sidebar" }, admin: { readOnly: true, position: "sidebar" },
}); });