Access control
This commit is contained in:
		
							parent
							
								
									1cda674782
								
							
						
					
					
						commit
						e98fabfd0e
					
				
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -21,6 +21,7 @@ | ||||
|       "devDependencies": { | ||||
|         "@types/dotenv": "^8.2.0", | ||||
|         "@types/express": "^4.17.9", | ||||
|         "@types/react-router-dom": "^5.3.3", | ||||
|         "copyfiles": "^2.4.1", | ||||
|         "nodemon": "^2.0.6", | ||||
|         "prettier": "^3.0.0", | ||||
| @ -2998,6 +2999,12 @@ | ||||
|         "@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": { | ||||
|       "version": "6.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", | ||||
| @ -3098,6 +3105,27 @@ | ||||
|         "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": { | ||||
|       "version": "4.4.6", | ||||
|       "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", | ||||
|  | ||||
| @ -30,6 +30,7 @@ | ||||
|   "devDependencies": { | ||||
|     "@types/dotenv": "^8.2.0", | ||||
|     "@types/express": "^4.17.9", | ||||
|     "@types/react-router-dom": "^5.3.3", | ||||
|     "copyfiles": "^2.4.1", | ||||
|     "nodemon": "^2.0.6", | ||||
|     "prettier": "^3.0.0", | ||||
|  | ||||
							
								
								
									
										11
									
								
								src/accesses/collections/mustBeAdminOrSelf.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/accesses/collections/mustBeAdminOrSelf.ts
									
									
									
									
									
										Normal 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 } }; | ||||
| }; | ||||
							
								
								
									
										8
									
								
								src/accesses/collections/mustHaveAtLeastOneRole.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/accesses/collections/mustHaveAtLeastOneRole.ts
									
									
									
									
									
										Normal 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; | ||||
| }; | ||||
							
								
								
									
										9
									
								
								src/accesses/mustBeAdmin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/accesses/mustBeAdmin.ts
									
									
									
									
									
										Normal 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); | ||||
| }; | ||||
| @ -22,6 +22,7 @@ export const ContentFolders = buildCollectionConfig( | ||||
|     admin: { | ||||
|       useAsTitle: fields.slug, | ||||
|       defaultColumns: [fields.slug, fields.translations], | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Collections, | ||||
|     }, | ||||
|     timestamps: false, | ||||
|  | ||||
| @ -16,6 +16,7 @@ export const ContentThumbnails = buildCollectionConfig( | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|  | ||||
| @ -9,6 +9,9 @@ import { fileField } from "../../fields/fileField/fileField"; | ||||
| import { contentBlocks } from "./Blocks/blocks"; | ||||
| import { ContentThumbnails } from "../ContentThumbnails/ContentThumbnails"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping"; | ||||
| import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; | ||||
| import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -53,6 +56,12 @@ export const Contents = buildVersionedCollectionConfig( | ||||
|         fields.status, | ||||
|       ], | ||||
|       group: CollectionGroup.Collections, | ||||
|       hooks: { | ||||
|         beforeDuplicate: beforeDuplicatePiping([ | ||||
|           beforeDuplicateUnpublish, | ||||
|           beforeDuplicateAddCopyTo(fields.slug), | ||||
|         ]), | ||||
|       }, | ||||
|       preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, | ||||
|     }, | ||||
|     fields: [ | ||||
|  | ||||
							
								
								
									
										39
									
								
								src/collections/Currencies/Currencies.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/collections/Currencies/Currencies.ts
									
									
									
									
									
										Normal 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...)"; | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|   }) | ||||
| ); | ||||
| @ -15,6 +15,7 @@ export const Files = buildCollectionConfig( | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     fields: [ | ||||
|  | ||||
| @ -5,6 +5,9 @@ import { localizedFields } from "../../fields/translatedFields/translatedFields" | ||||
| import { Key } from "../../types/collections"; | ||||
| import { isDefined } from "../../utils/asserts"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| import { mustBeAdmin } from "../../accesses/mustBeAdmin"; | ||||
| import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; | ||||
| import { QuickFilters } from "../../components/QuickFilters"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -27,6 +30,26 @@ export const Keys: CollectionConfig = buildCollectionConfig( | ||||
|       useAsTitle: fields.slug, | ||||
|       defaultColumns: [fields.slug, fields.type, fields.translations], | ||||
|       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, | ||||
|     versions: false, | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { CollectionGroup } from "../constants"; | ||||
| import { buildCollectionConfig } from "../utils/collectionConfig"; | ||||
| import { mustBeAdmin } from "../../accesses/mustBeAdmin"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { buildCollectionConfig } from "../../utils/collectionConfig"; | ||||
| 
 | ||||
| const fields = { | ||||
|   id: "id", | ||||
| @ -16,8 +17,10 @@ export const Languages = buildCollectionConfig( | ||||
|     admin: { | ||||
|       useAsTitle: fields.name, | ||||
|       defaultColumns: [fields.name, fields.id], | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Meta, | ||||
|     }, | ||||
|     access: { create: mustBeAdmin, update: mustBeAdmin }, | ||||
|     timestamps: false, | ||||
|     fields: [ | ||||
|       { | ||||
| @ -16,6 +16,7 @@ export const LibraryItemThumbnails = buildCollectionConfig( | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|  | ||||
| @ -11,8 +11,13 @@ 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 { Languages } from "../Languages/Languages"; | ||||
| 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 = { | ||||
|   status: "status", | ||||
| @ -29,6 +34,9 @@ const fields = { | ||||
|   width: "width", | ||||
|   height: "height", | ||||
|   thickness: "thickness", | ||||
|   price: "price", | ||||
|   priceAmount: "amount", | ||||
|   priceCurrency: "currency", | ||||
|   releaseDate: "releaseDate", | ||||
|   itemType: "itemType", | ||||
|   textual: "textual", | ||||
| @ -39,19 +47,24 @@ const fields = { | ||||
|   textualLanguages: "languages", | ||||
|   audio: "audio", | ||||
|   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>; | ||||
| 
 | ||||
| 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( | ||||
|   { | ||||
|     singular: "Library Item", | ||||
| @ -63,9 +76,15 @@ export const LibraryItems = buildVersionedCollectionConfig( | ||||
|       useAsTitle: fields.slug, | ||||
|       description: | ||||
|         "A comprehensive list of all Yokoverse’s 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], | ||||
|       group: CollectionGroup.Collections, | ||||
|       hooks: { | ||||
|         beforeDuplicate: beforeDuplicatePiping([ | ||||
|           beforeDuplicateUnpublish, | ||||
|           beforeDuplicateAddCopyTo(fields.slug), | ||||
|         ]), | ||||
|       }, | ||||
|       preview: (doc) => `https://accords-library.com/library/${doc.slug}`, | ||||
|     }, | ||||
|     fields: [ | ||||
| @ -135,9 +154,115 @@ stage plays, manga, drama CDs, and comics).", | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         name: "size", | ||||
|         type: "group", | ||||
|       optionalGroupField({ | ||||
|         name: fields.scans, | ||||
|         fields: [ | ||||
|           optionalGroupField({ | ||||
|             name: fields.scansCover, | ||||
|             fields: [ | ||||
|               { | ||||
|                 type: "row", | ||||
|                 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 }, | ||||
|         fields: [ | ||||
|           { | ||||
| @ -146,25 +271,49 @@ stage plays, manga, drama CDs, and comics).", | ||||
|               { | ||||
|                 name: fields.width, | ||||
|                 type: "number", | ||||
|                 validate: validateRequiredSizeValue, | ||||
|                 required: true, | ||||
|                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||
|               }, | ||||
|               { | ||||
|                 name: fields.height, | ||||
|                 type: "number", | ||||
|                 validate: validateRequiredSizeValue, | ||||
|                 required: true, | ||||
|                 admin: { step: 1, width: "33%", description: "in mm." }, | ||||
|               }, | ||||
|               { | ||||
|                 name: fields.thickness, | ||||
|                 type: "number", | ||||
|                 validate: validateSizeValue, | ||||
|                 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, | ||||
|         type: "radio", | ||||
|  | ||||
| @ -16,6 +16,7 @@ export const PostThumbnails = buildCollectionConfig( | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|  | ||||
| @ -8,6 +8,9 @@ import { removeTranslatorsForTranscripts } from "./hooks/beforeValidate"; | ||||
| import { Keys } from "../Keys/Keys"; | ||||
| import { PostThumbnails } from "../PostThumbnails/PostThumbnails"; | ||||
| import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; | ||||
| import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping"; | ||||
| import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish"; | ||||
| import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; | ||||
| 
 | ||||
| const fields = { | ||||
|   slug: "slug", | ||||
| @ -36,9 +39,15 @@ export const Posts = buildVersionedCollectionConfig( | ||||
|       useAsTitle: fields.slug, | ||||
|       description: | ||||
|         "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], | ||||
|       group: CollectionGroup.Collections, | ||||
|       hooks: { | ||||
|         beforeDuplicate: beforeDuplicatePiping([ | ||||
|           beforeDuplicateUnpublish, | ||||
|           beforeDuplicateAddCopyTo(fields.slug), | ||||
|         ]), | ||||
|       }, | ||||
|       preview: (doc) => `https://accords-library.com/news/${doc.slug}`, | ||||
|     }, | ||||
|     hooks: { | ||||
|  | ||||
| @ -16,6 +16,7 @@ export const RecorderThumbnails = buildCollectionConfig( | ||||
|     defaultSort: fields.filename, | ||||
|     admin: { | ||||
|       useAsTitle: fields.filename, | ||||
|       disableDuplicate: true, | ||||
|       group: CollectionGroup.Media, | ||||
|     }, | ||||
|     upload: { | ||||
|  | ||||
| @ -1,10 +1,13 @@ | ||||
| import { localizedFields } from "../../fields/translatedFields/translatedFields"; | ||||
| import { Languages } from "../Languages"; | ||||
| import { beforeDuplicate } from "./hooks/beforeDuplicate"; | ||||
| import { CollectionGroup } from "../../constants"; | ||||
| import { Languages } from "../Languages/Languages"; | ||||
| import { CollectionGroup, RecordersRoles } from "../../constants"; | ||||
| import { RecorderThumbnails } from "../RecorderThumbnails/RecorderThumbnails"; | ||||
| import { imageField } from "../../fields/imageField/imageField"; | ||||
| 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 = { | ||||
|   username: "username", | ||||
| @ -13,6 +16,7 @@ const fields = { | ||||
|   biographies: "biographies", | ||||
|   biography: "biography", | ||||
|   avatar: "avatar", | ||||
|   role: "role", | ||||
| } as const satisfies Record<string, string>; | ||||
| 
 | ||||
| export const Recorders = buildCollectionConfig( | ||||
| @ -24,17 +28,43 @@ export const Recorders = buildCollectionConfig( | ||||
|     defaultSort: fields.username, | ||||
|     admin: { | ||||
|       useAsTitle: fields.username, | ||||
|       hooks: { beforeDuplicate }, | ||||
|       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: [ | ||||
|         fields.username, | ||||
|         fields.avatar, | ||||
|         fields.anonymize, | ||||
|         fields.biographies, | ||||
|         fields.languages, | ||||
|         fields.role, | ||||
|       ], | ||||
|       disableDuplicate: true, | ||||
|       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, | ||||
|     fields: [ | ||||
| @ -75,6 +105,20 @@ export const Recorders = buildCollectionConfig( | ||||
|         }, | ||||
|         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, | ||||
|         type: "checkbox", | ||||
|  | ||||
| @ -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`, | ||||
|   }; | ||||
| }; | ||||
| @ -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."); | ||||
|   } | ||||
| }; | ||||
| @ -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%" }, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|     ], | ||||
|   }) | ||||
| ); | ||||
							
								
								
									
										47
									
								
								src/components/QuickFilters.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/QuickFilters.tsx
									
									
									
									
									
										Normal 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; | ||||
| `;
 | ||||
| @ -2,7 +2,6 @@ export enum CollectionGroup { | ||||
|   Collections = "Collections", | ||||
|   Media = "Media", | ||||
|   Meta = "Meta", | ||||
|   Administration = "Administration", | ||||
| } | ||||
| 
 | ||||
| export enum KeysTypes { | ||||
| @ -43,7 +42,12 @@ export enum LibraryItemsTextualPageOrders { | ||||
|   RightToLeft = "Right to left", | ||||
| } | ||||
| 
 | ||||
| export enum UserRoles { | ||||
| export enum RecordersRoles { | ||||
|   Admin = "Admin", | ||||
|   Recorder = "Recorder", | ||||
| } | ||||
| 
 | ||||
| export enum CollectionStatus { | ||||
|   Draft = "draft", | ||||
|   Published = "published", | ||||
| } | ||||
|  | ||||
							
								
								
									
										14
									
								
								src/fields/optionalGroupField/optionalGroupField.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/fields/optionalGroupField/optionalGroupField.ts
									
									
									
									
									
										Normal 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` }, | ||||
| }); | ||||
| @ -1,7 +1,7 @@ | ||||
| import { ArrayField, Field } from "payload/types"; | ||||
| import { hasDuplicates } from "../../utils/validation"; | ||||
| import { isDefined, isUndefined } from "../../utils/asserts"; | ||||
| import { Languages } from "../../collections/Languages"; | ||||
| import { Languages } from "../../collections/Languages/Languages"; | ||||
| import { RowLabel } from "./RowLabel"; | ||||
| import { Cell } from "./Cell"; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										5
									
								
								src/hooks/beforeDuplicateAddCopyTo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/hooks/beforeDuplicateAddCopyTo.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| import { BeforeDuplicate } from "payload/types"; | ||||
| 
 | ||||
| export const beforeDuplicateAddCopyTo = | ||||
|   (fieldName: string): BeforeDuplicate => | ||||
|   ({ data }) => ({ ...data, [fieldName]: `${data[fieldName]}-copy` }); | ||||
							
								
								
									
										5
									
								
								src/hooks/beforeDuplicatePiping.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/hooks/beforeDuplicatePiping.ts
									
									
									
									
									
										Normal 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); | ||||
| }; | ||||
							
								
								
									
										7
									
								
								src/hooks/beforeDuplicateUnpublish.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/hooks/beforeDuplicateUnpublish.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| import { BeforeDuplicate } from "payload/types"; | ||||
| import { CollectionStatus } from "../constants"; | ||||
| 
 | ||||
| export const beforeDuplicateUnpublish: BeforeDuplicate = ({ data }) => ({ | ||||
|   ...data, | ||||
|   _status: CollectionStatus.Draft, | ||||
| }); | ||||
| @ -1,7 +1,6 @@ | ||||
| import { buildConfig } from "payload/config"; | ||||
| import path from "path"; | ||||
| import { Users } from "./collections/Users"; | ||||
| import { Languages } from "./collections/Languages"; | ||||
| import { Languages } from "./collections/Languages/Languages"; | ||||
| import { Recorders } from "./collections/Recorders/Recorders"; | ||||
| import { Posts } from "./collections/Posts/Posts"; | ||||
| import { Keys } from "./collections/Keys/Keys"; | ||||
| @ -15,11 +14,12 @@ import { ContentThumbnails } from "./collections/ContentThumbnails/ContentThumbn | ||||
| import { ContentFolders } from "./collections/ContentFolders/ContentFolders"; | ||||
| import { Logo } from "./components/Logo"; | ||||
| import { Icon } from "./components/Icon"; | ||||
| import { Currencies } from "./collections/Currencies/Currencies"; | ||||
| 
 | ||||
| export default buildConfig({ | ||||
|   serverURL: "https://dashboard.accords-library.com", | ||||
|   admin: { | ||||
|     user: Users.slug, | ||||
|     user: Recorders.slug, | ||||
|     components: { graphics: { Logo, Icon } }, | ||||
|     meta: { | ||||
|       favicon: "/public/favicon.ico", | ||||
| @ -39,9 +39,9 @@ export default buildConfig({ | ||||
|     PostThumbnails, | ||||
|     Files, | ||||
|     Languages, | ||||
|     Currencies, | ||||
|     Recorders, | ||||
|     Keys, | ||||
|     Users, | ||||
|   ], | ||||
|   globals: [], | ||||
|   telemetry: false, | ||||
|  | ||||
| @ -45,3 +45,18 @@ html[data-theme="light"] { | ||||
|   --color-base-950: #11100b; | ||||
|   --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; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -25,19 +25,18 @@ export type ContentFoldersTranslation = { | ||||
| 
 | ||||
| export interface Config { | ||||
|   collections: { | ||||
|     "library-items": LibraryItem; | ||||
|     'library-items': LibraryItem; | ||||
|     contents: Content; | ||||
|     "content-folders": ContentFolder; | ||||
|     'content-folders': ContentFolder; | ||||
|     posts: Post; | ||||
|     "content-thumbnails": ContentThumbnail; | ||||
|     "library-item-thumbnails": LibraryItemThumbnail; | ||||
|     "recorder-thumbnails": RecorderThumbnail; | ||||
|     "post-thumbnails": PostThumbnail; | ||||
|     'content-thumbnails': ContentThumbnail; | ||||
|     'library-item-thumbnails': LibraryItemThumbnail; | ||||
|     'recorder-thumbnails': RecorderThumbnail; | ||||
|     'post-thumbnails': PostThumbnail; | ||||
|     files: File; | ||||
|     languages: Language; | ||||
|     recorders: Recorder; | ||||
|     keys: Key; | ||||
|     users: User; | ||||
|   }; | ||||
|   globals: {}; | ||||
| } | ||||
| @ -53,50 +52,55 @@ export interface LibraryItem { | ||||
|   digital: boolean; | ||||
|   downloadable: boolean; | ||||
|   size?: { | ||||
|     width?: number; | ||||
|     height?: number; | ||||
|     width: number; | ||||
|     height: number; | ||||
|     thickness?: number; | ||||
|   }; | ||||
|   itemType?: "Textual" | "Audio" | "Video" | "Game" | "Other"; | ||||
|     id?: string; | ||||
|   }[]; | ||||
|   price?: { | ||||
|     priceAmount: number; | ||||
|     id?: string; | ||||
|   }[]; | ||||
|   itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other'; | ||||
|   textual?: { | ||||
|     subtype?: | ||||
|       | { | ||||
|           value: string; | ||||
|           relationTo: "keys"; | ||||
|           relationTo: 'keys'; | ||||
|         }[] | ||||
|       | { | ||||
|           value: Key; | ||||
|           relationTo: "keys"; | ||||
|           relationTo: 'keys'; | ||||
|         }[]; | ||||
|     languages?: | ||||
|       | { | ||||
|           value: string; | ||||
|           relationTo: "languages"; | ||||
|           relationTo: 'languages'; | ||||
|         }[] | ||||
|       | { | ||||
|           value: Language; | ||||
|           relationTo: "languages"; | ||||
|           relationTo: 'languages'; | ||||
|         }[]; | ||||
|     pageCount?: number; | ||||
|     bindingType?: "Paperback" | "Hardcover"; | ||||
|     pageOrder?: "LeftToRight" | "RightToLeft"; | ||||
|     bindingType?: 'Paperback' | 'Hardcover'; | ||||
|     pageOrder?: 'LeftToRight' | 'RightToLeft'; | ||||
|   }; | ||||
|   audio?: { | ||||
|     audioSubtype?: | ||||
|       | { | ||||
|           value: string; | ||||
|           relationTo: "keys"; | ||||
|           relationTo: 'keys'; | ||||
|         }[] | ||||
|       | { | ||||
|           value: Key; | ||||
|           relationTo: "keys"; | ||||
|           relationTo: 'keys'; | ||||
|         }[]; | ||||
|   }; | ||||
|   releaseDate?: string; | ||||
|   lastModifiedBy: string | User; | ||||
|   lastModifiedBy: string | Recorder; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _status?: "draft" | "published"; | ||||
|   _status?: 'draft' | 'published'; | ||||
| } | ||||
| export interface LibraryItemThumbnail { | ||||
|   id: string; | ||||
| @ -139,27 +143,30 @@ export interface Key { | ||||
|   id: string; | ||||
|   slug: string; | ||||
|   type: | ||||
|     | "Contents" | ||||
|     | "LibraryAudio" | ||||
|     | "LibraryVideo" | ||||
|     | "LibraryTextual" | ||||
|     | "LibraryGroup" | ||||
|     | "Library" | ||||
|     | "Weapons" | ||||
|     | "GamePlatforms" | ||||
|     | "Categories" | ||||
|     | "Wordings"; | ||||
|     | 'Contents' | ||||
|     | 'LibraryAudio' | ||||
|     | 'LibraryVideo' | ||||
|     | 'LibraryTextual' | ||||
|     | 'LibraryGroup' | ||||
|     | 'Library' | ||||
|     | 'Weapons' | ||||
|     | 'GamePlatforms' | ||||
|     | 'Categories' | ||||
|     | 'Wordings'; | ||||
|   translations?: CategoryTranslations; | ||||
| } | ||||
| export interface Language { | ||||
|   id: string; | ||||
|   name: string; | ||||
| } | ||||
| export interface User { | ||||
| export interface Recorder { | ||||
|   id: string; | ||||
|   recorder: string | Recorder; | ||||
|   name: string; | ||||
|   role: ("Admin" | "Recorder")[]; | ||||
|   username: string; | ||||
|   avatar?: string | RecorderThumbnail; | ||||
|   languages?: string[] | Language[]; | ||||
|   biographies?: RecorderBiographies; | ||||
|   role?: ('Admin' | 'Recorder')[]; | ||||
|   anonymize: boolean; | ||||
|   email: string; | ||||
|   resetPasswordToken?: string; | ||||
|   resetPasswordExpiration?: string; | ||||
| @ -169,14 +176,6 @@ export interface User { | ||||
|   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; | ||||
| @ -213,15 +212,15 @@ export interface Content { | ||||
|   categories?: | ||||
|     | { | ||||
|         value: string; | ||||
|         relationTo: "keys"; | ||||
|         relationTo: 'keys'; | ||||
|       }[] | ||||
|     | { | ||||
|         value: Key; | ||||
|         relationTo: "keys"; | ||||
|         relationTo: 'keys'; | ||||
|       }[]; | ||||
|   type?: { | ||||
|     value: string | Key; | ||||
|     relationTo: "keys"; | ||||
|     relationTo: 'keys'; | ||||
|   }; | ||||
|   translations: { | ||||
|     language: string | Language; | ||||
| @ -240,10 +239,10 @@ export interface Content { | ||||
|     audio?: string | File; | ||||
|     id?: string; | ||||
|   }[]; | ||||
|   lastModifiedBy: string | User; | ||||
|   lastModifiedBy: string | Recorder; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _status?: "draft" | "published"; | ||||
|   _status?: 'draft' | 'published'; | ||||
| } | ||||
| export interface ContentThumbnail { | ||||
|   id: string; | ||||
| @ -280,25 +279,19 @@ export interface TextBlock { | ||||
|   }[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "textBlock"; | ||||
|   blockType: 'textBlock'; | ||||
| } | ||||
| export interface Section { | ||||
|   content?: (Section_Section | Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section { | ||||
|   content?: ( | ||||
|     | Section_Section_Section | ||||
|     | Section_Section_Tabs | ||||
|     | TranscriptBlock | ||||
|     | QuoteBlock | ||||
|     | TextBlock | ||||
|   )[]; | ||||
|   content?: (Section_Section_Section | Section_Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section_Section { | ||||
|   content?: ( | ||||
| @ -310,25 +303,25 @@ export interface Section_Section_Section { | ||||
|   )[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section_Section_Section { | ||||
|   content?: (Section_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section_Section_Section_Section { | ||||
|   content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface TranscriptBlock { | ||||
|   lines: (LineBlock | CueBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "transcriptBlock"; | ||||
|   blockType: 'transcriptBlock'; | ||||
| } | ||||
| export interface LineBlock { | ||||
|   content: { | ||||
| @ -336,13 +329,13 @@ export interface LineBlock { | ||||
|   }[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "lineBlock"; | ||||
|   blockType: 'lineBlock'; | ||||
| } | ||||
| export interface CueBlock { | ||||
|   content: string; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "cueBlock"; | ||||
|   blockType: 'cueBlock'; | ||||
| } | ||||
| export interface QuoteBlock { | ||||
|   from: string; | ||||
| @ -351,120 +344,120 @@ export interface QuoteBlock { | ||||
|   }[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "quoteBlock"; | ||||
|   blockType: 'quoteBlock'; | ||||
| } | ||||
| export interface Section_Section_Section_Tabs { | ||||
|   tabs?: Section_Section_Section_Tabs_Tab[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tabs"; | ||||
|   blockType: 'tabs'; | ||||
| } | ||||
| export interface Section_Section_Section_Tabs_Tab { | ||||
|   content?: (Section_Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tab"; | ||||
|   blockType: 'tab'; | ||||
| } | ||||
| export interface Section_Section_Section_Tabs_Tab_Section { | ||||
|   content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section_Tabs { | ||||
|   tabs?: Section_Section_Tabs_Tab[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tabs"; | ||||
|   blockType: 'tabs'; | ||||
| } | ||||
| export interface Section_Section_Tabs_Tab { | ||||
|   content?: (Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tab"; | ||||
|   blockType: 'tab'; | ||||
| } | ||||
| export interface Section_Section_Tabs_Tab_Section { | ||||
|   content?: (Section_Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Section_Tabs_Tab_Section_Section { | ||||
|   content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Tabs { | ||||
|   tabs?: Section_Tabs_Tab[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tabs"; | ||||
|   blockType: 'tabs'; | ||||
| } | ||||
| export interface Section_Tabs_Tab { | ||||
|   content?: (Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tab"; | ||||
|   blockType: 'tab'; | ||||
| } | ||||
| export interface Section_Tabs_Tab_Section { | ||||
|   content?: (Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Tabs_Tab_Section_Section { | ||||
|   content?: (Section_Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Section_Tabs_Tab_Section_Section_Section { | ||||
|   content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Tabs { | ||||
|   tabs?: Tabs_Tab[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tabs"; | ||||
|   blockType: 'tabs'; | ||||
| } | ||||
| export interface Tabs_Tab { | ||||
|   content?: (Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "tab"; | ||||
|   blockType: 'tab'; | ||||
| } | ||||
| export interface Tabs_Tab_Section { | ||||
|   content?: (Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Tabs_Tab_Section_Section { | ||||
|   content?: (Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Tabs_Tab_Section_Section_Section { | ||||
|   content?: (Tabs_Tab_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface Tabs_Tab_Section_Section_Section_Section { | ||||
|   content?: (TranscriptBlock | QuoteBlock | TextBlock)[]; | ||||
|   id?: string; | ||||
|   blockName?: string; | ||||
|   blockType: "section"; | ||||
|   blockType: 'section'; | ||||
| } | ||||
| export interface File { | ||||
|   id: string; | ||||
|   filename: string; | ||||
|   type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio"; | ||||
|   type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio'; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
| } | ||||
| @ -475,20 +468,20 @@ export interface ContentFolder { | ||||
|   subfolders?: | ||||
|     | { | ||||
|         value: string; | ||||
|         relationTo: "content-folders"; | ||||
|         relationTo: 'content-folders'; | ||||
|       }[] | ||||
|     | { | ||||
|         value: ContentFolder; | ||||
|         relationTo: "content-folders"; | ||||
|         relationTo: 'content-folders'; | ||||
|       }[]; | ||||
|   contents?: | ||||
|     | { | ||||
|         value: string; | ||||
|         relationTo: "contents"; | ||||
|         relationTo: 'contents'; | ||||
|       }[] | ||||
|     | { | ||||
|         value: Content; | ||||
|         relationTo: "contents"; | ||||
|         relationTo: 'contents'; | ||||
|       }[]; | ||||
| } | ||||
| export interface Post { | ||||
| @ -498,20 +491,20 @@ export interface Post { | ||||
|   authors: | ||||
|     | { | ||||
|         value: string; | ||||
|         relationTo: "recorders"; | ||||
|         relationTo: 'recorders'; | ||||
|       }[] | ||||
|     | { | ||||
|         value: Recorder; | ||||
|         relationTo: "recorders"; | ||||
|         relationTo: 'recorders'; | ||||
|       }[]; | ||||
|   categories?: | ||||
|     | { | ||||
|         value: string; | ||||
|         relationTo: "keys"; | ||||
|         relationTo: 'keys'; | ||||
|       }[] | ||||
|     | { | ||||
|         value: Key; | ||||
|         relationTo: "keys"; | ||||
|         relationTo: 'keys'; | ||||
|       }[]; | ||||
|   translations: { | ||||
|     language: string | Language; | ||||
| @ -527,10 +520,10 @@ export interface Post { | ||||
|   }[]; | ||||
|   publishedDate: string; | ||||
|   hidden?: boolean; | ||||
|   lastModifiedBy: string | User; | ||||
|   lastModifiedBy: string | Recorder; | ||||
|   updatedAt: string; | ||||
|   createdAt: string; | ||||
|   _status?: "draft" | "published"; | ||||
|   _status?: 'draft' | 'published'; | ||||
| } | ||||
| export interface PostThumbnail { | ||||
|   id: string; | ||||
|  | ||||
| @ -1,29 +1,26 @@ | ||||
| import { CollectionBeforeChangeHook, CollectionConfig, RelationshipField } from "payload/types"; | ||||
| import { Users } from "../collections/Users"; | ||||
| import { | ||||
|   BuildCollectionConfig, | ||||
|   GenerationFunctionProps, | ||||
|   buildCollectionConfig, | ||||
| } from "./collectionConfig"; | ||||
| import { Recorders } from "../collections/Recorders/Recorders"; | ||||
| 
 | ||||
| const fields = { lastModifiedBy: "lastModifiedBy" }; | ||||
| 
 | ||||
| const beforeChangeLastModifiedBy: CollectionBeforeChangeHook = async ({ | ||||
|   data: { updatedBy, ...data }, | ||||
|   req, | ||||
| }) => { | ||||
|   console.log(data, req.user); | ||||
|   return { | ||||
|     ...data, | ||||
|     [fields.lastModifiedBy]: req.user.id, | ||||
|   }; | ||||
| }; | ||||
| }) => ({ | ||||
|   ...data, | ||||
|   [fields.lastModifiedBy]: req.user.id, | ||||
| }); | ||||
| 
 | ||||
| const lastModifiedByField = (): RelationshipField => ({ | ||||
|   name: fields.lastModifiedBy, | ||||
|   type: "relationship", | ||||
|   required: true, | ||||
|   relationTo: Users.slug, | ||||
|   relationTo: Recorders.slug, | ||||
|   admin: { readOnly: true, position: "sidebar" }, | ||||
| }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint