KeystoneJs 用户定义的关系顺序

KeystoneJs user-defined order for Relationship

我使用 KeystoneJS 和 PostgreSQL 作为我的后端,并在我的应用程序的前端使用 Apollo。 我有一个模式,其中有一个链接到另一个列表的列表。 我希望能够允许用户更改第二个列表的顺序。

这是我的架构的简化版本

keystone.createList(
    'forms',
    {
      fields: {
        name: {
          type: Text,
          isRequired: true,
        },
        buttons: {
          type: Relationship,
          ref: 'buttons.attached_forms',
          many: true,
        },
      },
    }
);

keystone.createList(
    'buttons',
    {
      fields: {
        name: {
          type: Text,
          isRequired: true,
        },
        attached_forms: {
          type: Relationship,
          ref: 'forms.buttons',
          many: true,
        },
      },
    }
);

所以我想做的是允许用户更改按钮的顺序,这样当我将来从表单中获取它们时:

const QUERY = gql`
  query getForms($formId: ID!) {
    allforms(where: {
      id: $formId,
    }) {
      id
      name
      buttons {
        id
        name
      }
    }
  }
`;

按钮应该按照预定义的顺序从后端返回。

{
    id: 1,
    name: 'Form 1',
    buttons: [
        {
            id: 1,
            name: 'Button 1',
        },
        {
            id: 3,
            name: 'Button 3',
        },
        {
            id: 2,
            name: 'Button 2',
        }
    ]
}

或者甚至只是在 returns 上使用允许根据前端用户定义的排序顺序进行排序的查询的一些数据。

要注意的是这种关系是多对多的。

因此,仅向按钮架构添加一列是不够的,因为排序需要特定于关系。换句话说,如果用户将特定按钮放在特定表单的最后,则不应更改同一按钮在其他表单上的顺序。

在我自己创建的后端中,我会向联接 table 添加一些内容,例如 sortOrder 字段或类似字段,然后更改这些值以更改顺序,甚至顺序他们在前端使用该信息。 类似于 this answer here。 多对多联接 table 将包含 formId、buttonId、sortOrder 等列。

我一直在深入研究 KeystoneJS 的文档,但我想不出一种方法来完成这项工作,而不会陷入覆盖我们正在使用的 KnexAdapter 的杂草中。

我正在使用:

{
    "@keystonejs/adapter-knex": "^11.0.7",
    "@keystonejs/app-admin-ui": "^7.3.11",
    "@keystonejs/app-graphql": "^6.2.1",
    "@keystonejs/fields": "^20.1.2",
    "@keystonejs/keystone": "^17.1.2",
    "@keystonejs/server-side-graphql-client": "^1.1.2",
}

有什么想法可以实现吗?

我们想到的一个选项是将订单添加到表单 table。

keystone.createList(
    'forms',
    {
      fields: {
        name: {
          type: Text,
          isRequired: true,
        },
        buttonOrder: {
          type: Text,
        },
        buttons: {
          type: Relationship,
          ref: 'buttons.attached_forms',
          many: true,
        },
      },
    }
);

这个新字段 buttonOrder 可以包含按钮 ID 顺序的字符串表示形式,就像在 JSON 字符串化数组中一样。 这样做的主要问题是很难使该字段与实际链接的按钮保持同步。

我曾经遇到过类似的挑战,所以经过一些研究并发现了这个 answer,我使用 PostgreSQL TRIGGER 实现了一个项目的解决方案。

因此您可以在更新时添加触发器,它应该移动 buttonOrder

这是我身上的 SQL,这是测试代码,我用正则表达式替换了术语以适合您的问题:)


    // Assign order
    await knex.raw(`
        do $$
        DECLARE form_id text;
        begin
            CREATE SEQUENCE buttons_order_seq;
            CREATE VIEW buttons_view AS SELECT * FROM "buttons" ORDER BY "createdAt" ASC, "formId";
            CREATE RULE buttons_rule AS ON UPDATE TO buttons_view DO INSTEAD UPDATE buttons SET order = NEW.order WHERE id = NEW.id;
        
            FOR form_id IN SELECT id FROM form LOOP
                ALTER SEQUENCE buttons_order_seq RESTART;
        
                UPDATE buttons_view SET order = nextval('buttons_order_seq') WHERE "formId" = form_id;
            END LOOP;
        
            DROP SEQUENCE buttons_order_seq;
            DROP RULE buttons_rule ON buttons_view;
            DROP VIEW buttons_view;
        END; $$`);

    // Create function that shifts orders
    await knex.raw(`
        CREATE FUNCTION shift_buttons_order()
            RETURNS trigger AS
            $$
            BEGIN
                IF NEW.order < OLD.order THEN
                    UPDATE buttons SET order = order + 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
                        WHERE order >= NEW.order AND order < OLD.order AND "formId" = OLD."formId";
                ELSE 
                    UPDATE buttons SET order = order - 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
                        WHERE order <= NEW.order AND order > OLD.order AND "formId" = OLD."formId";
                END IF;
            RETURN NEW;
            END;
            $$
            LANGUAGE 'plpgsql'`);

    // Create trigger to shift orders on update
    await knex.raw(`
        CREATE TRIGGER shift_buttons_order BEFORE UPDATE OF order ON buttons FOR EACH ROW
            WHEN (OLD."shiftOrderFlag" = NEW."shiftOrderFlag" AND OLD.order <> NEW.order)
            EXECUTE PROCEDURE shift_buttons_order()`);

一种方法是有两个“按钮”列表,一个带有按钮模板(下方buttonTemplate),其中包含名称等常用数据,另一个(下方button)其中引用了一个 buttonTemplate 和一个 form。这允许您为每个 button 分配一个 formIndex 属性,这决定了它在相应表格上的位置。

(未测试)示例代码:

keystone.createList(
  'Form',
  {
    fields: {
      name: {
        type: Text,
        isRequired: true,
      },
      buttons: {
        type: Relationship,
        ref: 'Button.form',
        many: true,
      },
    },
  }
);

keystone.createList(
  'Button',
  {
    fields: {
      buttonTemplate: {
        type: Relationship,
        ref: 'ButtonTemplate.buttons',
        many: false,
      },
      form: {
        type: Relationship,
        ref: 'Form.buttons',
        many: false,
      },
      formIndex: {
        type: Integer,
        isRequired: true,
      },
    },
  }
);

keystone.createList(
  'ButtonTemplate',
  {
    fields: {
      name: {
        type: Text,
        isRequired: true,
      },
      buttons: {
        type: Relationship,
        ref: 'Button.buttonTemplate',
        many: true,
      },
    },
  }
);

我认为这比您的 buttonOrder 解决方案更不容易引起您的头痛(我相信您会看到),例如用户删除此字段引用的按钮。

如果您决定采用这种方法,您可以使用 hook functionality in Keystone. E.g. before a button is deleted 来防止此类问题,检查所有表格并重写 buttonOrder 字段,删除对已删除的任何引用按钮。