Node.js、Apollo Server v4、GraphQL、Mongoose - 类别树

问题描述 投票:0回答:1

我正在尝试使用 Node.js、Apollo Server v4、GraphQL、Mongoose 技术创建类别树。

但是当我想获取创建的类别或子类别数据时,输出不是我想要的。类别既作为参考类别又作为普通类别出现在子类别中。我还以我不明白的方式添加了 id 655b105543598f2ef0ba0f69 的类别数据,作为该 id 655b104a43598f2ef0ba0f64 值的类别的 subCategories 数组中的第二个子类别。不过虽然名字不出现null。

GraphQL 查询请求;

query Query {
  getCategories {
    success
    response_code
    message
    categories {
      _id
      name
      attributes {
        name
        values {
          value
        }
      }
      products {
        name
      }
      subCategories {
        _id
        name
        attributes {
          name
          values {
            value
          }
        }
        products {
          name
        }
        subCategories {
          _id
          name
          attributes {
            name
            values {
              value
            }
          }
          products {
            name
          }
          subCategories {
            _id
            name
            attributes {
              name
              values {
                value
              }
            }
            products {
              name
            }
            subCategories {
              _id
              name
              attributes {
                name
                values {
                  value
                }
              }
              products {
                name
              }
            }
          }
        }
      }
    }
  }
}

输出;

{
  "data": {
    "getCategories": {
      "success": true,
      "response_code": "categories-successfully-retrieved",
      "message": "Categories Successfully Retrieved!",
      "categories": [
        {
          "_id": "655b103b43598f2ef0ba0f5e",
          "name": "K 2",
          "attributes": {
            "name": null,
            "values": {
              "value": null
            }
          },
          "products": [],
          "subCategories": []
        },
        {
          "_id": "655b103e43598f2ef0ba0f61",
          "name": "K 1",
          "attributes": {
            "name": null,
            "values": {
              "value": null
            }
          },
          "products": [],
          "subCategories": [
            {
              "_id": "655b104a43598f2ef0ba0f64",
              "name": "AK 1",
              "attributes": {
                "name": null,
                "values": {
                  "value": null
                }
              },
              "products": [],
              "subCategories": [
                {
                  "_id": "655b105543598f2ef0ba0f69",
                  "name": null,
                  "attributes": null,
                  "products": null,
                  "subCategories": null
                }
              ]
            },
            {
              "_id": "655b106643598f2ef0ba0f6f",
              "name": "AK 2",
              "attributes": {
                "name": null,
                "values": {
                  "value": null
                }
              },
              "products": [],
              "subCategories": []
            }
          ]
        },
        {
          "_id": "655b104a43598f2ef0ba0f64",
          "name": "AK 1",
          "attributes": {
            "name": null,
            "values": {
              "value": null
            }
          },
          "products": [],
          "subCategories": [
            {
              "_id": "655b105543598f2ef0ba0f69",
              "name": "AAK 1",
              "attributes": {
                "name": null,
                "values": {
                  "value": null
                }
              },
              "products": [],
              "subCategories": []
            }
          ]
        },
        {
          "_id": "655b105543598f2ef0ba0f69",
          "name": "AAK 1",
          "attributes": {
            "name": null,
            "values": {
              "value": null
            }
          },
          "products": [],
          "subCategories": []
        },
        {
          "_id": "655b106643598f2ef0ba0f6f",
          "name": "AK 2",
          "attributes": {
            "name": null,
            "values": {
              "value": null
            }
          },
          "products": [],
          "subCategories": []
        }
      ]
    }
  }
}

预期输出;

[
  {
  "name": "Category 1",
  "attributes": {
    "name": "Attribute Name",
    "values": [{
      "value": "Attribute Value",
      "products": ["Product Ref", "Product Ref"]
    }]
  },
  "products": ["Product Ref", "Product Ref"],
  "subCategories": [
      {
      "name": "Sub Category 1",
      "attributes": {
        "name": "Attribute Name",
        "values": [{
          "value": "Attribute Value",
          "products": ["Product Ref", "Product Ref"]
        }]
      },
      "products": ["Product Ref", "Product Ref"],
      "subCategories": [
        {
          "name": "Sub Category 2",
          "attributes": {
            "name": "Attribute Name",
            "values": [{
              "value": "Attribute Value",
              "products": ["Product Ref", "Product Ref"]
            }]
          },
          "products": ["Product Ref", "Product Ref"],
          "subCategories": []
        }
      ]
    }
  ]
},
{
  "name": "Category 2",
  "attributes": {
    "name": "Attribute Name",
    "values": [{
      "value": "Attribute Value",
      "products": ["Product Ref", "Product Ref"]
    }]
  },
  "products": ["Product Ref", "Product Ref"],
  "subCategories": [
      {
      "name": "Sub Category 1",
      "attributes": {
        "name": "Attribute Name",
        "values": [{
          "value": "Attribute Value",
          "products": ["Product Ref", "Product Ref"]
        }]
      },
      "products": ["Product Ref", "Product Ref"],
      "subCategories": [
        {
          "name": "Sub Category 2",
          "attributes": {
            "name": "Attribute Name",
            "values": [{
              "value": "Attribute Value",
              "products": ["Product Ref", "Product Ref"]
            }]
          },
          "products": ["Product Ref", "Product Ref"],
          "subCategories": []
        }
      ]
    }
  ]
}
]

类别猫鼬模式;

import mongoose from "mongoose";

const attributeSchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true, unique: true, default: "Default Name" },
  values: [
    {
      value: { type: String, required: true, trim: true, unique: true },
      products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
    },
  ],
});

const categorySchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true },
  subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: "Category" }],
  attributes: [attributeSchema],
  products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
});

export const Category = mongoose.model("Category", categorySchema);

类别 GraphQL 架构;

export const categorySchema = `#graphql
  type Category {
    _id: ID
    name: String
    subCategories: [Category]
    attributes: Attributes
    products: [Product]
  }

  input CategoryInput {
    name: String!
    isMainCategory: Boolean!
    parentCategoryID: ID
  }

  type Attributes {
    name: String
    values: AttributesValue
  }

  type AttributesValue {
    value: String
    products: [Product]
  }

  input AttributesInput {
    name: String!
    categoryID: ID!
  }

  input AttributesValueInput {
    value: String!
    attributesID: ID!
  }

  type QueryCategoryResponse {
    success: Boolean!
    response_code: String!
    message: String!
    category: Category
    categories: [Category]
  }

  type Query {
    getCategories: QueryCategoryResponse
    getCategoryByID(id: ID!): QueryCategoryResponse
    getCategoriesByID(ids: [ID]!): QueryCategoryResponse
  }

  type Mutation {
    createCategory(input: CategoryInput!): Response
  }
`;

类别解析器;

import { Category } from "./model.js";

export const categoryResolver = {
  Query: {
    getCategories: async () => {
      try {
        const categories = await Category.find().populate("subCategories attributes products");

        if (categories) {
          return {
            success: true,
            response_code: "categories-successfully-retrieved",
            message: "Categories Successfully Retrieved!",
            categories: categories,
          };
        } else {
          return { success: false, response_code: "categories-not-found", message: "Categories Not Found!" };
        }
      } catch (error) {
        return { success: false, response_code: "server-error", message: "Server Error!" };
      }
    },
    getCategoryByID: async (_, { id }) => {
      try {
        const category = await Category.findById({ _id: id }).populate("subCategories attributes products");

        if (category) {
          return {
            success: true,
            response_code: "category-successfully-retrieved",
            message: "Category Successfully Retrieved!",
            category: category,
          };
        } else {
          return { success: false, response_code: "category-not-found", message: "Category Not Found!" };
        }
      } catch (error) {
        return { success: false, response_code: "server-error", message: "Server Error!" };
      }
    },
    getCategoriesByID: async (_, { ids }) => {
      try {
        const categories = await Category.find({ _id: { $in: ids } }).populate("subCategories attributes products");

        if (categories.length !== 0) {
          return {
            success: true,
            response_code: "categories-successfully-retrieved",
            message: "Categories Successfully Retrieved!",
            categories: categories,
          };
        } else {
          return { success: false, response_code: "categories-not-found", message: "Categories Not Found!" };
        }
      } catch (error) {
        return { success: false, response_code: "server-error", message: "Server Error!" };
      }
    },
  },
  Mutation: {
    createCategory: async (_, { input }) => {
      try {
        if (input.isMainCategory) {
          const category = await Category.findOne({ name: input.name }).collation({ locale: "en", strength: 2 });

          if (category) {
            return { success: false, response_code: "category-name-exist", message: "Category Name Exist!" };
          } else {
            const newCategory = new Category({
              name: input.name,
            });

            await newCategory.save();

            return {
              success: true,
              response_code: "category-created-successfully",
              message: "Category Created Successfully!",
            };
          }
        } else {
          const category = await Category.findById(input.parentCategoryID).populate("subCategories");

          if (!category) {
            return { success: false, response_code: "sub-category-not-found", message: "Sub Category Not Found!" };
          }

          const categoryNames = category.subCategories.map((category) => category.name.toLowerCase());

          if (categoryNames.includes(input.name.toLowerCase())) {
            return { success: false, response_code: "category-name-exist", message: "Category Name Exists!" };
          }

          const newCategory = new Category({
            name: input.name,
          });

          await newCategory.save();

          category.subCategories.push(newCategory);
          await category.save();

          return {
            success: true,
            response_code: "sub-category-created-successfully",
            message: "Sub Category Created Successfully!",
          };
        }
      } catch (error) {
        console.log(error);
        return { success: false, response_code: "server-error", message: "Server Error!" };
      }
    },
  },
};

我如何改进此代码并解决我的 getCategories 输出问题?

显然,我也尝试过使用 mongoose ref,我还尝试根据类别架构将数据直接发送到子类别中。我在第二种方法中提到的添加子类别等方法让我很头疼。

node.js mongoose graphql backend apollo-server
1个回答
0
投票

您的模型中似乎缺少的主要内容是

parent
类别的概念。定义分层模型时,通常包括对父类别的引用(类似于
parentId
)。

然后要返回所有类别,首先要搜索具有

null
parentId
的所有类别 - 这些是根节点。

您还需要一个子类别的字段解析器,用于查找父类别是当前类别的类别。这允许您递归地遍历树。

GraphQL 的一个限制是您必须明确要在查询中返回多少级别,您无法定义一个递归树来深入到每个可能的叶子。您上面的查询向下钻取了 5 个级别。

首先在 GraphQL 类型中添加一个

parent
字段。

type Category {
  _id: ID
  name: String
  subCategories: [Category]
  parent: Category
  attributes: Attributes
  products: [Product]
}

然后在你的 Mongoose 模型中:

const categorySchema = new mongoose.Schema({
  name: { type: String, required: true, trim: true },
  subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: "Category" }],
  parentId: { type: mongoose.Schema.Types.ObjectId, required: false },
  attributes: [attributeSchema],
  products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
});

然后修改您的

getCategories
解析器以搜索包含 null
parentId

的类别

然后向

Category
类型添加一个字段解析器,以根据
parentId
解析子类别,并添加一个字段解析器,以解析父类别。

© www.soinside.com 2019 - 2024. All rights reserved.