Ошибка при попытке вставить строку в таблицу из-за внешнего ключа UUID с sequelize.js

#node.js #postgresql #orm #sequelize.js

#node.js #postgresql #orm #sequelize.js

Вопрос:

Я использую sequelize.js с node.js и postgres. Я получил 2 простых таблицы из примера в качестве своего рода POC. Я изменил идентификатор на UUID, и у меня возникла проблема со вставкой во вторую таблицу (с UUID FK ).

Я использую postman для его тестирования. Я создаю строки задач с UUID без проблем, затем я пытаюсь создать элемент задач, который имеет идентификатор задачи в качестве внешнего ключа, и кажется, что он не распознает этот идентификатор!

Я попробовал ручной скрипт в postgres, и он сработал. Вероятно, я что-то упускаю из виду в коде, но я не могу понять, что именно.

вот ошибка, которая возвращается мне в postman —

 {
    "name": "SequelizeDatabaseError",
    "parent": {
        "name": "error",
        "length": 96,
        "severity": "ERROR",
        "code": "22P02",
        "file": "uuid.c",
        "line": "137",
        "routine": "string_to_uuid",
        "sql": "INSERT INTO "TodoItems" ("id","content","complete","createdAt","updatedAt","todoId") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
    },
    "original": {
        "name": "error",
        "length": 96,
        "severity": "ERROR",
        "code": "22P02",
        "file": "uuid.c",
        "line": "137",
        "routine": "string_to_uuid",
        "sql": "INSERT INTO "TodoItems" ("id","content","complete","createdAt","updatedAt","todoId") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
    },
    "sql": "INSERT INTO "TodoItems" ("id","content","complete","createdAt","updatedAt","todoId") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *;"
}
 

Вот соответствующие файлы js —

todoItems.js контроллер —

 const TodoItem = require('../dal/models').TodoItem;
const uuid = require('uuid/v4');

module.exports = {
  create(req, res) {
    return TodoItem
      .create({
        content: req.body.content,
        todoId: req.params.todoId,
      })
      .then(todoItem => res.status(201).send(todoItem))
      .catch(error => res.status(400).send(error));
  },

  update(req, res) {
    return TodoItem
      .find({
        where: {
          id: req.params.todoItemId,
          todoId: req.params.todoId,
        },
      })
      .then(todoItem => {
        if (!todoItem) {
          return res.status(404).send({
            message: 'TodoItem Not Found',
          });
        }

        return todoItem
          .update({
            content: req.body.content || todoItem.content,
            complete: req.body.complete || todoItem.complete,
          })
          .then(updatedTodoItem => res.status(200).send(updatedTodoItem))
          .catch(error => res.status(400).send(error));
      })
      .catch(error => res.status(400).send(error));
  },

  destroy(req, res) {
    return TodoItem
      .find({
        where: {
          id: req.params.todoItemId,
          todoId: req.params.todoId,
        },
      })
      .then(todoItem => {
        if (!todoItem) {
          return res.status(404).send({
            message: 'TodoItem Not Found',
          });
        }

        return todoItem
          .destroy()
          .then(() => res.status(204).send())
          .catch(error => res.status(400).send(error));
      })
      .catch(error => res.status(400).send(error));
  },
};
 

todos.js контроллер-

 const Todo = require('../dal/models').Todo;
const TodoItem = require('../dal/models').TodoItem;

module.exports = {
  create(req, res) {
    return Todo
      .create({
        title: req.body.title,
      })
      .then((todo) => res.status(201).send(todo))
      .catch((error) => res.status(400).send(error));
  },

  list(req, res) {
    return Todo
      .findAll({
        include: [{
          model: TodoItem,
          as: 'todoItems',
        }],
        order: [
          ['createdAt', 'DESC'],
          [{ model: TodoItem, as: 'todoItems' }, 'createdAt', 'ASC'],
        ],
      })
      .then((todos) => res.status(200).send(todos))
      .catch((error) => res.status(400).send(error));
  },

  retrieve(req, res) {
    return Todo
      .findByPk(req.params.todoId, {
        include: [{
          model: TodoItem,
          as: 'todoItems',
        }],
      })
      .then((todo) => {
        if (!todo) {
          return res.status(404).send({
            message: 'Todo Not Found',
          });
        }
        return res.status(200).send(todo);
      })
      .catch((error) => res.status(400).send(error));
  },

  update(req, res) {
    return Todo
      .findByPk(req.params.todoId, {
        include: [{
          model: TodoItem,
          as: 'todoItems',
        }],
      })
      .then(todo => {
        if (!todo) {
          return res.status(404).send({
            message: 'Todo Not Found',
          });
        }
        return todo
          .update({
            title: req.body.title || todo.title,
          })
          .then(() => res.status(200).send(todo))
          .catch((error) => res.status(400).send(error));
      })
      .catch((error) => res.status(400).send(error));
  },

  destroy(req, res) {
    return Todo
      .findByPk(req.params.todoId)
      .then(todo => {
        if (!todo) {
          return res.status(400).send({
            message: 'Todo Not Found',
          });
        }
        return todo
          .destroy()
          .then(() => res.status(204).send())
          .catch((error) => res.status(400).send(error));
      })
      .catch((error) => res.status(400).send(error));
  },
};
 

todo table create migration —

 module.exports = {
  up: (queryInterface, Sequelize) =>
    queryInterface.createTable('Todos', {
      id: {
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
      },
      title: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
    }),
  down: (queryInterface /* , Sequelize */) => queryInterface.dropTable('Todos'),
};
 

todo-item table create migration —

 module.exports = {
  up: (queryInterface, Sequelize) =>
    queryInterface.createTable('TodoItems', {
      id: {
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
      },
      content: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      complete: {
        type: Sequelize.BOOLEAN,
        defaultValue: false,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
      },
      todoId: {
        type: Sequelize.UUID,
        onDelete: 'CASCADE',
        references: {
          model: 'Todos',
          key: 'id',
          as: 'todoId',
        },
      },
    }),
  down: (queryInterface /* , Sequelize */) =>
    queryInterface.dropTable('TodoItems'),
};
 

модель задач —

 const uuid = require('uuid/v4');

'use strict';

module.exports = (sequelize, DataTypes) => {
  const Todo = sequelize.define('Todo', {
    title: {
      type: DataTypes.STRING,
      allowNull: false,
    }
  });
  Todo.associate = (models) => {
    Todo.hasMany(models.TodoItem, {
      foreignKey: 'todoId',
      as: 'todoItems',
    });
  };
  Todo.beforeCreate((item, _ ) => {
    return item.id = uuid();
  });
  return Todo;
};
 

модель элемента задач —

 const uuid = require('uuid/v4');

'use strict';

module.exports = (sequelize, DataTypes) => {
  const TodoItem = sequelize.define('TodoItem', {
    content: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    complete: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    }
  });
  TodoItem.associate = (models) => {
    TodoItem.belongsTo(models.Todo, {
      foreignKey: 'todoId',
      onDelete: 'CASCADE',
    });
  };
  TodoItem.beforeCreate((item, _ ) => {
    return item.id = uuid();
  });
  return TodoItem;
};
 

Ответ №1:

Как выглядит код вашего маршрутизатора? Используете ли вы правильный параметр path для todoId? Если вы используете экспресс, например. это должно выглядеть так app.post("/todos/:todoId/todo_items", todoItemController.create) . Обратите внимание на todoId camelcase . Это гарантирует, что объект, на который req.params.todoId вы ссылаетесь в контроллере TodoItems, будет иметь правильное значение.

Кроме того, убедитесь, что у вас есть правильный анализатор тела для правильной обработки req.body.content. В express это было бы сделано с помощью библиотеки body body-parser и app.use(bodyParser.json()) . Добавьте точку останова или инструкцию log в код создания контроллера TodoItem и убедитесь, что у вас действительно есть правильные значения параметров.

Комментарии:

1. К сожалению, я удалил код элементов задач и заменил его своими реальными моделями, поэтому я не могу знать наверняка, но сейчас все работает хорошо. Я почти уверен, что проблема действительно была связана с неправильным присвоением имен в путях маршрутизатора.

Ответ №2:

Если у вас возникла ошибка, описанная выше, это может быть связано с тем, что вы вкладываете другие объекты в тело вашего запроса, и поэтому UUID не преобразуется из string в UUID.

Например, если у вас есть тело запроса, например

 {
   "Transaction": {
      "id" : "f2ec9ecf-31e5-458d-847e-5fcca0a90c3e",
      "currency" : "USD",
      "type_id" : "bfa944ea-4ce1-4dad-a74e-aaa449212ebf", 
      "total": 8000.00,
      "fees": 43.23,
      "description":"Description here"
    },
}
 

и поэтому в вашем контроллере вы создаете свою сущность, например

 try {
  await Transaction.create(
    {
      id: req.body.Transaction.id,
      currency: req.body.Transaction.currency,
      type_id: req.body.Transaction.type_id,
      total: req.body.Transaction.total,
      fees: req.body.Transaction.fees,
      description: req.body.Transaction.description,
    }......
 

Ваши id и type_id , скорее всего, не преобразуются из строки в UUID.

Существует несколько способов решения этой проблемы. Самый простой подход — выполнить явное преобразование из string в UUID.

Для этого выполните импорт parse из uuid npm module и выполните явное преобразование, как вы можете видеть в примере кода ниже.

 const { parse: uuidParse } = require("uuid");
try {
  await Transaction.create(
    {
      id: uuidParse(req.body.Transaction.id),
      currency: req.body.Transaction.currency,
      type_id: uuidParse(req.body.Transaction.type_id),
      total: req.body.Transaction.total,
      fees: req.body.Transaction.fees,
      description: req.body.Transaction.description,
    }.....
 

Это явное преобразование из строки в UUID в основном решит проблему.