Files
payloadcms/packages/graphql/src/schema/initCollections.ts
Dan Ribbens ed01ee1e2d feat: duplicate doc moved from frontend to backend concern (#5342)
BREAKING CHANGE: collection.admin.hooks.beforeDuplicate removed and instead should be handled using field beforeDuplicate hooks which take the full field hook arguments.

* feat: duplicate doc moved from frontend to backend concern

* feat: default beforeDuplicate hook functions on unique fields

* docs: beforeDuplicate field hook

* test: duplicate doc local api

* chore: fix build errors

* chore: add access.create call to duplicate operation

* chore: perfectionist reorder imports
2024-03-19 11:25:19 -04:00

474 lines
15 KiB
TypeScript

/* eslint-disable no-param-reassign */
import type { GraphQLInfo } from 'payload/config'
import type { Collection, Field, SanitizedCollectionConfig, SanitizedConfig } from 'payload/types'
import {
GraphQLBoolean,
GraphQLInt,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString,
} from 'graphql'
import { fieldAffectsData } from 'payload/types'
import { flattenTopLevelFields, formatNames, toWords } from 'payload/utilities'
import { buildVersionCollectionFields } from 'payload/versions'
import type { ObjectTypeConfig } from './buildObjectType.js'
import forgotPassword from '../resolvers/auth/forgotPassword.js'
import init from '../resolvers/auth/init.js'
import login from '../resolvers/auth/login.js'
import logout from '../resolvers/auth/logout.js'
import me from '../resolvers/auth/me.js'
import refresh from '../resolvers/auth/refresh.js'
import resetPassword from '../resolvers/auth/resetPassword.js'
import unlock from '../resolvers/auth/unlock.js'
import verifyEmail from '../resolvers/auth/verifyEmail.js'
import createResolver from '../resolvers/collections/create.js'
import getDeleteResolver from '../resolvers/collections/delete.js'
import { docAccessResolver } from '../resolvers/collections/docAccess.js'
import duplicateResolver from '../resolvers/collections/duplicate.js'
import findResolver from '../resolvers/collections/find.js'
import findByIDResolver from '../resolvers/collections/findByID.js'
import findVersionByIDResolver from '../resolvers/collections/findVersionByID.js'
import findVersionsResolver from '../resolvers/collections/findVersions.js'
import restoreVersionResolver from '../resolvers/collections/restoreVersion.js'
import updateResolver from '../resolvers/collections/update.js'
import formatName from '../utilities/formatName.js'
import { buildMutationInputType, getCollectionIDType } from './buildMutationInputType.js'
import buildObjectType from './buildObjectType.js'
import buildPaginatedListType from './buildPaginatedListType.js'
import { buildPolicyType } from './buildPoliciesType.js'
import buildWhereInputType from './buildWhereInputType.js'
type InitCollectionsGraphQLArgs = {
config: SanitizedConfig
graphqlResult: GraphQLInfo
}
function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQLArgs): void {
Object.keys(graphqlResult.collections).forEach((slug) => {
const collection: Collection = graphqlResult.collections[slug]
const {
config: collectionConfig,
config: { fields, graphQL = {} as SanitizedCollectionConfig['graphQL'], versions },
} = collection
if (!graphQL) return
let singularName
let pluralName
const fromSlug = formatNames(collection.config.slug)
if (graphQL.singularName) {
singularName = toWords(graphQL.singularName, true)
} else {
singularName = fromSlug.singular
}
if (graphQL.pluralName) {
pluralName = toWords(graphQL.pluralName, true)
} else {
pluralName = fromSlug.plural
}
// For collections named 'Media' or similar,
// there is a possibility that the singular name
// will equal the plural name. Append `all` to the beginning
// of potential conflicts
if (singularName === pluralName) {
pluralName = `all${singularName}`
}
collection.graphQL = {} as Collection['graphQL']
const hasIDField =
flattenTopLevelFields(fields).findIndex(
(field) => fieldAffectsData(field) && field.name === 'id',
) > -1
const idType = getCollectionIDType(config.db.defaultIDType, collectionConfig)
const baseFields: ObjectTypeConfig = {}
const whereInputFields = [...fields]
if (!hasIDField) {
baseFields.id = { type: idType }
whereInputFields.push({
name: 'id',
type: config.db.defaultIDType as 'text',
})
}
const forceNullableObjectType = Boolean(versions?.drafts)
collection.graphQL.type = buildObjectType({
name: singularName,
baseFields,
config,
fields,
forceNullable: forceNullableObjectType,
graphqlResult,
parentName: singularName,
})
collection.graphQL.paginatedType = buildPaginatedListType(pluralName, collection.graphQL.type)
collection.graphQL.whereInputType = buildWhereInputType({
name: singularName,
fields: whereInputFields,
parentName: singularName,
})
if (collectionConfig.auth && !collectionConfig.auth.disableLocalStrategy) {
fields.push({
name: 'password',
type: 'text',
label: 'Password',
required: true,
})
}
const createMutationInputType = buildMutationInputType({
name: singularName,
config,
fields,
graphqlResult,
parentName: singularName,
})
if (createMutationInputType) {
collection.graphQL.mutationInputType = new GraphQLNonNull(createMutationInputType)
}
const updateMutationInputType = buildMutationInputType({
name: `${singularName}Update`,
config,
fields: fields.filter((field) => !(fieldAffectsData(field) && field.name === 'id')),
forceNullable: true,
graphqlResult,
parentName: `${singularName}Update`,
})
if (updateMutationInputType) {
collection.graphQL.updateMutationInputType = new GraphQLNonNull(updateMutationInputType)
}
graphqlResult.Query.fields[singularName] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(idType) },
draft: { type: GraphQLBoolean },
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
},
resolve: findByIDResolver(collection),
}
graphqlResult.Query.fields[pluralName] = {
type: buildPaginatedListType(pluralName, collection.graphQL.type),
args: {
draft: { type: GraphQLBoolean },
where: { type: collection.graphQL.whereInputType },
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
limit: { type: GraphQLInt },
page: { type: GraphQLInt },
sort: { type: GraphQLString },
},
resolve: findResolver(collection),
}
graphqlResult.Query.fields[`docAccess${singularName}`] = {
type: buildPolicyType({
type: 'collection',
entity: collectionConfig,
scope: 'docAccess',
typeSuffix: 'DocAccess',
}),
args: {
id: { type: new GraphQLNonNull(idType) },
},
resolve: docAccessResolver(collection),
}
graphqlResult.Mutation.fields[`create${singularName}`] = {
type: collection.graphQL.type,
args: {
...(createMutationInputType
? { data: { type: collection.graphQL.mutationInputType } }
: {}),
draft: { type: GraphQLBoolean },
...(config.localization
? {
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
},
resolve: createResolver(collection),
}
graphqlResult.Mutation.fields[`update${singularName}`] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(idType) },
autosave: { type: GraphQLBoolean },
...(updateMutationInputType
? { data: { type: collection.graphQL.updateMutationInputType } }
: {}),
draft: { type: GraphQLBoolean },
...(config.localization
? {
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
},
resolve: updateResolver(collection),
}
graphqlResult.Mutation.fields[`delete${singularName}`] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(idType) },
},
resolve: getDeleteResolver(collection),
}
graphqlResult.Mutation.fields[`duplicate${singularName}`] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(idType) },
},
resolve: duplicateResolver(collection),
}
if (collectionConfig.versions) {
const versionIDType = config.db.defaultIDType === 'text' ? GraphQLString : GraphQLInt
const versionCollectionFields: Field[] = [
...buildVersionCollectionFields(collectionConfig),
{
name: 'id',
type: config.db.defaultIDType as 'text',
},
{
name: 'createdAt',
type: 'date',
label: 'Created At',
},
{
name: 'updatedAt',
type: 'date',
label: 'Updated At',
},
]
collection.graphQL.versionType = buildObjectType({
name: `${singularName}Version`,
config,
fields: versionCollectionFields,
forceNullable: forceNullableObjectType,
graphqlResult,
parentName: `${singularName}Version`,
})
graphqlResult.Query.fields[`version${formatName(singularName)}`] = {
type: collection.graphQL.versionType,
args: {
id: { type: versionIDType },
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
},
resolve: findVersionByIDResolver(collection),
}
graphqlResult.Query.fields[`versions${pluralName}`] = {
type: buildPaginatedListType(
`versions${formatName(pluralName)}`,
collection.graphQL.versionType,
),
args: {
where: {
type: buildWhereInputType({
name: `versions${singularName}`,
fields: versionCollectionFields,
parentName: `versions${singularName}`,
}),
},
...(config.localization
? {
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
locale: { type: graphqlResult.types.localeInputType },
}
: {}),
limit: { type: GraphQLInt },
page: { type: GraphQLInt },
sort: { type: GraphQLString },
},
resolve: findVersionsResolver(collection),
}
graphqlResult.Mutation.fields[`restoreVersion${formatName(singularName)}`] = {
type: collection.graphQL.type,
args: {
id: { type: versionIDType },
},
resolve: restoreVersionResolver(collection),
}
}
if (collectionConfig.auth) {
const authFields: Field[] = collectionConfig.auth.disableLocalStrategy
? []
: [
{
name: 'email',
type: 'email',
required: true,
},
]
collection.graphQL.JWT = buildObjectType({
name: formatName(`${slug}JWT`),
config,
fields: [
...collectionConfig.fields.filter((field) => fieldAffectsData(field) && field.saveToJWT),
...authFields,
{
name: 'collection',
type: 'text',
required: true,
},
],
graphqlResult,
parentName: formatName(`${slug}JWT`),
})
graphqlResult.Query.fields[`me${singularName}`] = {
type: new GraphQLObjectType({
name: formatName(`${slug}Me`),
fields: {
collection: {
type: GraphQLString,
},
exp: {
type: GraphQLInt,
},
token: {
type: GraphQLString,
},
user: {
type: collection.graphQL.type,
},
},
}),
resolve: me(collection),
}
graphqlResult.Query.fields[`initialized${singularName}`] = {
type: GraphQLBoolean,
resolve: init(collection.config.slug),
}
graphqlResult.Mutation.fields[`refreshToken${singularName}`] = {
type: new GraphQLObjectType({
name: formatName(`${slug}Refreshed${singularName}`),
fields: {
exp: {
type: GraphQLInt,
},
refreshedToken: {
type: GraphQLString,
},
user: {
type: collection.graphQL.JWT,
},
},
}),
args: {
token: { type: GraphQLString },
},
resolve: refresh(collection),
}
graphqlResult.Mutation.fields[`logout${singularName}`] = {
type: GraphQLString,
resolve: logout(collection),
}
if (!collectionConfig.auth.disableLocalStrategy) {
if (collectionConfig.auth.maxLoginAttempts > 0) {
graphqlResult.Mutation.fields[`unlock${singularName}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: {
email: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: unlock(collection),
}
}
graphqlResult.Mutation.fields[`login${singularName}`] = {
type: new GraphQLObjectType({
name: formatName(`${slug}LoginResult`),
fields: {
exp: {
type: GraphQLInt,
},
token: {
type: GraphQLString,
},
user: {
type: collection.graphQL.type,
},
},
}),
args: {
email: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: login(collection),
}
graphqlResult.Mutation.fields[`forgotPassword${singularName}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: {
disableEmail: { type: GraphQLBoolean },
email: { type: new GraphQLNonNull(GraphQLString) },
expiration: { type: GraphQLInt },
},
resolve: forgotPassword(collection),
}
graphqlResult.Mutation.fields[`resetPassword${singularName}`] = {
type: new GraphQLObjectType({
name: formatName(`${slug}ResetPassword`),
fields: {
token: { type: GraphQLString },
user: { type: collection.graphQL.type },
},
}),
args: {
password: { type: GraphQLString },
token: { type: GraphQLString },
},
resolve: resetPassword(collection),
}
graphqlResult.Mutation.fields[`verifyEmail${singularName}`] = {
type: GraphQLBoolean,
args: {
token: { type: GraphQLString },
},
resolve: verifyEmail(collection),
}
}
}
})
}
export default initCollectionsGraphQL