为 Keystone 中的字段设置动态默认值(对于 url slug)

Setting a dynamic default value for a field in Keystone (for a url slug)

我对 GraphQL 和无头 CMS 的概念都是全新的,我只是从头开始构建我自己的 REST API。

我已经使用 Keystone v6 构建了一个基本的 API,假设我有一个产品架构,例如:

export const Item = list({
  fields: {
    name: text({ validation: { isRequired: true } }),
    description: text(),
    photos: relationship({
      ref: 'ProductImage.listing',
      many: true,
    }),
    category: relationship({
      ref: 'Category.listings',
      many: false,
    }),
    dateCreated: timestamp({
      defaultValue: { kind: 'now' },
    }),
    brand: text(),
    model: text(),
    size: text(),
    slug: text(),
  },
});

太棒了,有效....现在说我想在使用类似“this-item-name”+“-”+“id”之类的东西创建产品时自动填充 slug,我无法理解出 a/ 如何做到这一点或 b/ 甚至从惯例的角度来看去哪里。

我的假设是我会使用文本字段的 defaultValue 属性?或者这是否与突变有关(我还没有很好地理解这个概念)......如果是这样,我什至无法弄清楚在样板 Keystone 应用程序中的什么地方我什至会写一个突变。

非常感谢任何指点。

你要实现的叫virtual field 使用 resolver 函数,您可以获得所有项目字段并使用字符串模板构造您需要的任何内容,只需将此代码作为另一个 virtual

类型的字段
virtualName: {
      type: Virtual,
      resolver: item => `${item.name}-${item.id}`
      };
    },

我认为我的语法适用于 keystone 5,但它应该与您需要的相似,祝您好运,玩得开心!

defaultValue 选项对您没有帮助,因为 Keystone 6 仅支持静态默认值,在这里我们想要动态默认值。

@Daniel 关于虚拟字段的建议 只管用了一半 – 这是从一个值推导出另一个值的好方法 – 但是虚拟字段 运行 当值是 从数据库读取。 因此,您无法按虚拟字段进行过滤,因此,如果您在此处使用虚拟字段,您将永远无法根据它的 slug 找到项目(这是您要做的主要事情之一).

这里需要的是hook.

挂钩是 Keystone 的一项功能,它为您提供了一种在 GraphQL API 上执行操作前后 运行 代码的方法。它们允许您在突变生命周期的不同点附加函数(例如,beforeOperationafterOperation 等),从而使它们在不同时间 运行。

调用时,挂钩函数会传递有关它在何处以及为何被执行的信息 运行(例如 listKeyfieldKey 以及正在执行的 operation),连同传入数据、现有数据和 Keystone context object,允许您在一个钩子内查询 API。

您还可以在两个不同的级别附加挂钩:列表级别,它们可以访问整个操作,或者字段级别,它们只能影响单个字段的行为。

听起来有点复杂,但是 Hooks Guide and the Hooks API Keystone 文档站点上的页面应该涵盖了您。

具体来说,您可能想要这样的东西:

// A simple function to derive a url-safe value from a string input
// We'll use this from within out hook
function buildSlug(input) {
  return input
    .trim()
    .toLowerCase()
    .replace(/[^\w ]+/g, '')
    .replace(/ +/g, '-');
}

// Define the list schema
export const Item = list({
  fields: {
    name: text({ validation: { isRequired: true } }),
    // ... all the other fields

    // Declare the `slug` as a normal text field
    slug: text({

      // Being a slug, it should be indexed for lookups and unique
      isIndexed: 'unique',

      // Define the hook function itself and attach it to the resolveInput
      // step of the mutation lifecycle
      hooks: {
        resolveInput: ({ operation, resolvedData, inputData }) => {

          // Lets only default the slug value on create and only if 
          // it isn't supplied by the caller.
          // We probably don't want slugs to change automatically if an 
          // item is renamed.
          if (operation === 'create' && !inputData.slug) {
            return buildSlug(inputData.name);
          }

          // Since this hook is a the field level we only return the 
          // value for this field, not the whole item
          return resolvedData.slug;
        },
      },
    }),
  },
});

您可以看到我们在突变生命周期的 resolveInput 步骤附加了我们的钩子。 这是在保存之前修改传入值的好地方:

The resolveInput function is used to modify or augment the data values passed in to a create or update operation. — Hooks API docs

请注意,我们已使用 isIndexed: 'unique' 在数据库级别强制执行唯一性。 如所写,如果创建操作会导致重复值,这将导致创建操作出错。 所有挂钩都通过 Keystone context object 并且可以是异步的,因此您可以根据需要在返回之前通过检查数据库来确保生成的值是唯一的。

在这里,我们将钩子放在现场级别。 我们也可以在列表级别编写它,但我们只修改一个值,这样读起来更清楚一些。