Problem with include model M:N in multitenant app - node.js

I have a nodejs app using sequelize and postgres as a database, the architecture of the app is multitenant with separate schemas in the same database, the problem I present is that when including the permissions model in the user query, sequelize It doesn't take me the schema for the join table and therefore tries to query it in the default schema (public). these are my models:
Users:
export const USERS_TABLE = 'users';
#Table({
timestamps: false,
tableName: USERS_TABLE,
})
export class User extends Model {
#AfterCreate
static deletePassword(instance: User) {
instance.password = null;
}
#Column({
autoIncrement: false,
primaryKey: true,
type: DataType.STRING(36),
defaultValue: UUIDV4(),
field: 'user_id',
allowNull: false,
})
userId: string;
#Column({
type: DataType.STRING(20),
})
provider: string;
#Column({
type: DataType.STRING(50),
allowNull: false,
unique: true,
})
email: string;
#Column({
type: DataType.STRING(100),
field: 'customer_id',
unique: true,
})
customerId: string;
#Column({
type: DataType.STRING(50),
allowNull: false,
unique: true,
})
username: string;
#Column({
type: DataType.STRING(10),
unique: true,
})
phone: string;
#Column({
type: DataType.STRING(255),
field: 'recovery_token',
unique: true,
})
recoveryToken: string;
#Column({
type: DataType.STRING(10),
field: 'verify_code',
})
verifyCode: string;
#Column({
type: DataType.STRING(100),
})
password: string;
#Column({
type: DataType.TEXT,
})
devices: string;
#Column({
type: DataType.BOOLEAN,
defaultValue: false,
})
deleted: boolean;
#Column({
type: DataType.BOOLEAN,
defaultValue: false,
})
verified: boolean;
#Column({
type: DataType.INTEGER,
defaultValue: 1,
})
status: number;
#ForeignKey(() => Role)
#Column({
type: DataType.STRING(36),
field: 'role_id',
allowNull: false,
references: {
model: ROLES_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
roleId: string;
#ForeignKey(() => Profile)
#Column({
type: DataType.STRING,
field: 'profile_id',
allowNull: false,
references: {
model: PROFILES_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
profileId: string;
#ForeignKey(() => Subscription)
#Column({
type: DataType.STRING,
field: 'subscription_id',
references: {
model: SUBSCRIPTION_TABLE,
key: 'subscription_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
subscriptionId: string;
#Column({
allowNull: false,
type: DataType.DATE,
field: 'created_at',
defaultValue: new Date(),
})
createdAt: Date;
#Column({
allowNull: false,
type: DataType.DATE,
field: 'updated_at',
defaultValue: new Date(),
})
updatedAt: Date;
// Prueba tenantId
#Column({
type: DataType.STRING(36),
field: 'tenant_id',
references: {
model: TENANT_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
tenantId?: string;
#ForeignKey(() => Seat)
#Column({
type: DataType.STRING(36),
field: 'seat_id',
references: {
model: SEAT_TABLE,
key: 'seat_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
seatId: string;
// associations
#BelongsToMany(() => Permission, () => UserPermission)
Permissions: Permission[];
#BelongsTo(() => Subscription) Subscription: Subscription;
#BelongsTo(() => Profile) Profile: Profile;
#BelongsTo(() => Role) Role: Role;
#BelongsTo(() => Seat) Seat: Seat;
}
Permissions
export const PERMISSIONS_TABLE = 'permissions';
#Table({
timestamps: false,
tableName: PERMISSIONS_TABLE,
})
export class Permission extends Model {
#Column({
allowNull: false,
field: 'permission_id',
autoIncrement: false,
primaryKey: true,
type: DataType.STRING(36),
defaultValue: UUIDV4(),
})
permissionId: string;
#Column({
type: DataType.STRING(20),
allowNull: false,
})
name: string;
#Column({
type: DataType.STRING(150),
})
description: string;
#Column({
type: DataType.TEXT,
allowNull: false,
})
models: string;
#ForeignKey(() => Subscription)
#Column({
type: DataType.STRING,
allowNull: false,
field: 'subscription_id',
references: {
model: SUBSCRIPTION_TABLE,
key: 'subscription_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
subscriptionId: string;
#Column({
type: DataType.BOOLEAN,
allowNull: false,
defaultValue: true,
})
status: boolean;
#Column({
type: DataType.STRING(36),
field: 'tenant_id',
references: {
model: TENANT_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
tenantId?: string;
#Column({
allowNull: false,
type: DataType.DATE,
field: 'created_at',
defaultValue: new Date(),
})
createdAt: Date;
#Column({
allowNull: false,
type: DataType.DATE,
field: 'updated_at',
defaultValue: new Date(),
})
updatedAt: Date;
// Associations
#BelongsToMany(() => User, () => UserPermission) Users: User[];
}
Join Table
export const USERS_PERMISSIONS_TABLE = 'user_permissions';
#Table({
timestamps: false,
tableName: USERS_PERMISSIONS_TABLE,
})
export class UserPermission extends Model {
#ForeignKey(() => User)
#Column({
type: DataType.STRING,
field: 'user_id',
allowNull: false,
references: {
model: USERS_TABLE,
key: 'user_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
userId: string;
#ForeignKey(() => Permission)
#Column({
type: DataType.STRING,
field: 'permission_id',
allowNull: false,
references: {
model: PERMISSIONS_TABLE,
key: 'permission_id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
})
permissionId: string;
}
I pass the necessary parameters to a base class that performs my query
users = await super.getAll(
tenantId,
[
Role.schema(schemaName),
Profile.schema(schemaName),
Subscription,
Permission.schema(schemaName),
],
body.page,
body.size,
{},
);
findAll method of Super Class
public async getAll(
tenantId: string,
includeModel?: object,
page?: string,
size?: string,
where?: Record<string, unknown>,
) {
try {
// free_id
let records: unknown;
const free = tenantId.split('_');
const tenantName: string = getTenantName(tenantId);
if (free[0] === 'free') {
where.tenantId = free[1];
}
if (!includeModel) {
const { limit, offset } = paginate(page, size);
records = await this.model.schema(tenantName).findAndCountAll({
where,
limit,
offset,
});
return new HttpResponse.getSuccessful(records);
}
const { limit, offset } = paginate(page, size);
records = await this.model.schema(tenantName).findAndCountAll({
where,
limit,
offset,
include: includeModel,
});
return new HttpResponse.getSuccessful(records);
} catch (e) {
throw boom.badRequest(e);
}
}
The generated query And The error:
sql: `SELECT count("User"."user_id") AS "count" FROM "free_trial"."users" AS "User" LEFT OUTER JOIN "free_trial"."roles" AS "Role" ON "User"."role_id" = "Role"."role_id" LEFT OUTER JOIN "free_trial"."profiles" AS "Profile" ON "User"."profile_id" = "Profile"."profile_id" LEFT OUTER JOIN "subscriptions" AS "Subscription" ON "User"."subscription_id" = "Subscription"."subscription_id" LEFT OUTER JOIN ( "user_permissions" AS "Permissions->UserPermission" INNER JOIN "free_trial"."permissions" AS "Permissions" ON "Permissions"."permission_id" = "Permissions->UserPermission"."permission_id") ON "User"."user_id" = "Permissions->UserPermission"."user_id" WHERE "User"."tenant_id" = '72a5afbe-dc66-4b54-a428-d7bed9a2cbd9';`,
parameters: {},
isBoom: true,
isServer: false,
data: null,
output: {
statusCode: 400,
payload: {
statusCode: 400,
error: 'Bad Request',
message: 'no existe la relación «user_permissions»'
},
headers: {}
}
}
As can be seen in the query, all the tables are preceded by the name of the schema in which they are found, but in the join table it refers to the public schema and therefore it cannot be found; I have already tried thousands of ways but nothing seems to work, any ideas are appreciated.
I have tried to pass the name of the schema to the join table, referring to it in the include but it does not give results, I also tried within permissions to include the join table to be able to pass the name of the schema.

Related

Sequelize ts: How to set foreign key value

I need that when registering the user, the account should also be created automatically and I managed to do this using sequelize hooks, but the accountId I am not able to insert its value automatically and it always has a null value, I need that when the accountId has the id of Account, but I don't know how to do that
My models
import { Model, DECIMAL, INTEGER } from 'sequelize';
import db from '.';
class Account extends Model {
id!: number;
balance!: number;
};
Account.init({
id: {
type: INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
balance: {
type: DECIMAL(13, 2),
allowNull: false
},
},
{
sequelize: db,
modelName: 'accounts',
timestamps: false,
underscored: true,
},
);
export default Account;
import { Model, STRING, INTEGER } from 'sequelize';
import db from '.';
import Account from './account';
class User extends Model {
id!: number;
username!: string;
password!: string;
accountId!: any;
};
User.init({
id: {
type: INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
username: {
type: STRING(50),
allowNull: false,
},
password: {
type: STRING(200),
allowNull: false,
},
accountId: {
type: INTEGER,
// allowNull: false,
},
},
{
sequelize: db,
modelName: 'users',
tableName: 'users',
freezeTableName: true,
timestamps: false,
underscored: true,
hooks: {
afterCreate(user, _op) {
Account.create({ balance: 100})
}
},
}
);
Account.hasOne(User, {foreignKey: 'id', as: 'accountId'});
User.belongsTo(Account, {foreignKey: 'accountId', as: 'idAccount'});
export default User;
My migrations
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
username: {
allowNull: false,
type: Sequelize.STRING(50),
},
password: {
allowNull: false,
type: Sequelize.STRING(200),
},
account_id: {
// allowNull: false,
type: Sequelize.INTEGER,
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
references: {
model: 'accounts',
key: 'id',
},
},
});
},
down: async (queryInterface, _Sequelize) => {
return queryInterface.dropTable('users');
},
};
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
return queryInterface.createTable('accounts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
balance: {
allowNull: false,
type: Sequelize.DECIMAL(13,2),
},
});
},
down: async (queryInterface, _Sequelize) => {
return queryInterface.dropTable('accounts');
},
};
register = async (req: Request, res: Response) => {
const { username, password } = req.body;
const registerUser = await this.userController.register({username, password});
res.status(200).json({registerUser});
}
I've tried going through the hooks and inserting auto increment as well

Why am I getting a validation error? Nestjs, Sequelize , PostgressQL

I want to create a dependency where one User can have many InvestorCase, but one InvestorCase belongs to only one User. I need to have user_id field in InvestorCase.
User entity:
import { InvestorCase } from 'src/investor-case/entities/investor-case.entity';
import { ApiProperty } from '#nestjs/swagger';
import { Exclude } from 'class-transformer';
import {
AllowNull,
Column,
DataType,
Default,
HasMany,
IsIn,
Model,
Table,
} from 'sequelize-typescript';
import { UserRole, UserStatus } from 'src/shared/enums';
import { IUser } from 'src/shared/interfaces';
const userRoleValues = Object.values(UserRole);
const userStatusValues = Object.values(UserStatus);
#Table({ tableName: 'user' })
export class User extends Model<User, IUser> {
#ApiProperty({ example: '1', description: 'User`s Id' })
#Column({
type: DataType.INTEGER,
unique: true,
autoIncrement: true,
primaryKey: true,
})
public id: number;
#ApiProperty({ example: 'test#gmail.com', description: 'User`s Email' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public email: string;
#ApiProperty({ example: 'password', description: 'User``s password' })
#Column({
type: DataType.STRING,
allowNull: true,
})
#Exclude()
public password: string;
#ApiProperty({ example: 'Barak', description: 'User`s name' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public firstName: string;
#ApiProperty({ example: 'Obama', description: 'User`s surname' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public lastName: string;
#ApiProperty({ example: '3806799599432', description: 'User`s phone number' })
#Column({
type: DataType.STRING,
})
public phoneNumber: string;
#ApiProperty({ example: 'verified', description: 'Account status' })
#IsIn({
args: [userStatusValues],
msg: `User status must one of the following:
${userStatusValues.join(', ')}`,
})
#Default(UserStatus.UNVERIFIED)
#Column
public status: UserStatus;
#ApiProperty({
example: 'developer',
description: 'User`s role',
enum: UserRole,
})
#IsIn({
args: [userRoleValues],
msg: `User role must one of the following:
${userRoleValues.join(', ')}`,
})
#Default(UserRole.INVESTOR)
#AllowNull(false)
#Column
public role: UserRole;
#HasMany(() => InvestorCase)
investorCases: InvestorCase[];
}
InvestorCare entity:
import { ApiProperty } from "#nestjs/swagger";
import { BelongsTo, Column, DataType, ForeignKey, IsIn, Model, PrimaryKey, Table } from "sequelize-typescript";
import { PaymentMethods } from 'src/shared/enums'
import { IInvestorCase } from 'src/shared/interfaces';
import { User } from "src/user/entities/user.entity";
const paymentMethods = Object.values(PaymentMethods);
#Table({ tableName: 'investor-case' })
export class InvestorCase extends Model<InvestorCase, IInvestorCase> {
#ApiProperty({ example: '1', description: 'Unique ID' })
#PrimaryKey
#Column({ type: DataType.INTEGER, unique: true, autoIncrement: true })
public id: number;
#ApiProperty({ example: '10000', description: 'The amount the investor will deposit initially.' })
#Column({ type: DataType.INTEGER, unique: true, allowNull: false, validate: { min: 1000 } })
public initialPayment: number;
#ApiProperty({ example: '1000', description: 'The amount that the investor will contribute monthly.' })
#Column({ type: DataType.INTEGER, allowNull: true, validate: { min: 500 } })
public monthlyPayment: number;
#ApiProperty({
example: 'true',
description: 'The payment method by which the investments will be made.',
enum: paymentMethods
})
#IsIn({
args: [paymentMethods],
msg: `The payment method must one of the following: ${paymentMethods.join(',')}`
})
#Column({ type: DataType.STRING, allowNull: false, defaultValue: PaymentMethods.Manually })
public paymentMethod: string;
#BelongsTo(() => User, {
foreignKey: 'userId',
as: 'UserId',
})
#ApiProperty({
example: '1',
description: 'Company representative user id',
})
#ForeignKey(() => User)
#Column({ type: DataType.INTEGER })
userId: number;
}
I try to create InvestorCase using this:
{
"initialPayment": 5000,
"monthlyPayment": 1000,
"paymentMethod": "Link a bank account",
"userId": 2
}
[Nest] 244 - 05/10/2022, 10:30:26 AM ERROR [ExceptionsHandler] Validation error
Error:
at Query.run (/app/node_modules/sequelize/src/dialects/postgres/query.js:76:25)
at /app/node_modules/sequelize/src/sequelize.js:643:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at PostgresQueryInterface.insert (/app/node_modules/sequelize/src/dialects/abstract/query-interface.js:773:21)
at InvestorCase.save (/app/node_modules/sequelize/src/model.js:4046:35)
at Function.create (/app/node_modules/sequelize/src/model.js:2253:12)
at InvestorCaseService.create (/app/src/investor-case/investor-case.service.ts:18:16)
at InvestorCaseController.create (/app/src/investor-case/investor-case.controller.ts:18:16)
at /app/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /app/node_modules/#nestjs/core/router/router-proxy.js:9:17
But alwways got error:
[Nest] 244 - 05/10/2022, 10:30:26 AM ERROR [ExceptionsHandler] Validation error
Error:
at Query.run (/app/node_modules/sequelize/src/dialects/postgres/query.js:76:25)
at /app/node_modules/sequelize/src/sequelize.js:643:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at PostgresQueryInterface.insert (/app/node_modules/sequelize/src/dialects/abstract/query-interface.js:773:21)
at InvestorCase.save (/app/node_modules/sequelize/src/model.js:4046:35)
at Function.create (/app/node_modules/sequelize/src/model.js:2253:12)
at InvestorCaseService.create (/app/src/investor-case/investor-case.service.ts:18:16)
at InvestorCaseController.create (/app/src/investor-case/investor-case.controller.ts:18:16)
at /app/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /app/node_modules/#nestjs/core/router/router-proxy.js:9:17

how to create association instance from source instance with sequelize

I have 1:n association User -> Cart, what I want is, I find User by Id then create Cart by that instance and then fill the product_id manually
I'm using sequelize
I have tried get,set,create from this association and none of them worked, it gave me this error
TypeError: user.createCart is not a function
at /home/horus/beginner-html-site-scripted/server/controllers/Cart.controller.ts:21:26
and this is my CartController:
const { product_id } = req.params;
const product = await ProductModel.findByPk(product_id);
if (!product) {
return res.status(401).json({ message: 'product not found' });
}
//#ts-ignore
const userData = req.user;
const user = await UserModel.findByPk(userData.user_id);
if (!user) return res.status(401).json({ message: 'user not found' });
const cart = await user.createCart();
res.status(200).json({ message: '', cartData: user.getCart() });
those are my Models:
Cart Class
export default class Cart
extends Model<InferAttributes<Cart>, InferCreationAttributes<Cart>>
implements CartInterface {
declare cart_id: CreationOptional<string>;
declare quantity: CreationOptional<number>;
declare user_id: string;
}
Cart.init(
{
cart_id: { type: UUID, defaultValue: UUIDV4, primaryKey: true },
quantity: { type: INTEGER, defaultValue: 1 },
user_id: {
type: UUID,
references: {
model: 'Users',
key: 'user_id',
},
},
},
{ sequelize: db, tableName: 'Carts' }
);
User Class
export default class User
extends Model<InferAttributes<User>, InferCreationAttributes<User>>
implements UserInterface {
declare user_id: CreationOptional<string>;
declare firstName: string;
declare lastName: string;
declare email: string;
declare password: string;
declare avatar: string;
declare verificationCode: CreationOptional<string>;
declare passwordResetCode: CreationOptional<string>;
declare verified: CreationOptional<boolean>;
declare isAdmin: CreationOptional<boolean>;
// timestamps!
// createdAt can be undefined during creation
declare createdAt: CreationOptional<Date>;
// updatedAt can be undefined during creation
declare updatedAt: CreationOptional<Date>;
declare createSession: HasManyCreateAssociationMixin<Session, 'user_id'>;
declare createCart: HasOneCreateAssociationMixin<CartModel>;
declare setCart: HasOneSetAssociationMixin<CartModel, 'cart_id'>;
declare getCart: HasOneGetAssociationMixin<CartModel>;
}
User.init(
{
user_id: {
primaryKey: true,
allowNull: false,
type: UUID,
defaultValue: UUIDV4,
},
firstName: { type: new STRING(128), allowNull: false },
lastName: { type: new STRING(128), allowNull: false },
email: { type: new STRING(128), allowNull: false, unique: true },
password: {
type: new STRING(128),
allowNull: false,
},
avatar: { type: new STRING(128), defaultValue: '' },
verificationCode: { type: UUID, allowNull: false, defaultValue: UUIDV4 },
passwordResetCode: { type: UUID, allowNull: false, defaultValue: '' },
verified: { type: BOOLEAN, defaultValue: false, allowNull: false },
isAdmin: { type: BOOLEAN, defaultValue: false, allowNull: false },
createdAt: DATE,
updatedAt: DATE,
},
{
sequelize: db,
tableName: 'Users',
}
);
user Data
User {
dataValues: {
user_id: '20b42b70-f777-420b-b15b-cbf3732c1a9b',
firstName: 'leo',
lastName: 'qal',
email: 't#gmail.com',
password: '$2.',
avatar: '',
verificationCode: '',
passwordResetCode: '',
verified: true,
isAdmin: true,
createdAt: 2022-02-17T16:49:40.000Z,
updatedAt: 2022-02-17T16:49:40.000Z
},
_previousDataValues: {
user_id: '20b42b70-f777-420b-b15b-cbf3732c1a9b',
firstName: 'leo',
lastName: 'qal',
email: 't#gmail.com',
password: '$2.',
avatar: '',
verificationCode: '',
passwordResetCode: '',
verified: true,
isAdmin: true,
createdAt: 2022-02-17T16:49:40.000Z,
updatedAt: 2022-02-17T16:49:40.000Z
},
uniqno: 1,
_changed: Set(0) {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [
'user_id',
'firstName',
'lastName',
'email',
'password',
'avatar',
'verificationCode',
'passwordResetCode',
'verified',
'isAdmin',
'createdAt',
'updatedAt'
]
},
isNewRecord: false
}
Associations
UserModel.hasOne(CartModel, {
sourceKey: 'user_id',
foreignKey: 'user_id',
as: 'users',
});
CartModel.belongsTo(UserModel, {
foreignKey: 'cart_id',
as: 'carts',
});
//OneToMany Association
UserModel.hasMany(SessionModel, {
sourceKey: 'user_id',
foreignKey: 'user_id',
as: 'sessions', // this determines the name in `associations`!
});
CategoryModel.hasMany(ProductModel, {
sourceKey: 'category_id',
foreignKey: 'category_id',
as: 'products',
});

Belongs to many associations add foreign keys to source model, SequelizeDatabaseError: column "CategoryID" does not exist

I try to explain my case. I have two models: Film and Category. They are N:M associations.
migration file 20200123070411-createTables.js:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Category', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: Sequelize.STRING(20),
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
});
await queryInterface.createTable('Language', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: Sequelize.STRING(20),
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
});
await queryInterface.createTable('Film', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
LanguageID: {
type: Sequelize.INTEGER,
references: {
model: 'Language',
key: 'ID',
},
onDelete: 'restrict',
allowNull: false,
},
Title: {
type: Sequelize.STRING,
allowNull: false,
},
Description: {
type: Sequelize.STRING,
allowNull: false,
},
Release_Year: {
type: Sequelize.INTEGER,
allowNull: false,
},
Rental_Duration: {
type: Sequelize.INTEGER,
allowNull: false,
},
Rental_Date: {
type: Sequelize.DECIMAL(19, 0),
allowNull: false,
},
Length: {
type: Sequelize.INTEGER,
allowNull: false,
},
Replacement_Cost: {
type: Sequelize.DECIMAL(19, 0),
allowNull: false,
},
Rating: {
type: Sequelize.INTEGER,
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
Special_Features: {
type: Sequelize.STRING,
allowNull: false,
},
Fulltext: {
type: Sequelize.STRING,
allowNull: false,
},
});
await queryInterface.createTable(
'Film_Category',
{
FilmID: {
type: Sequelize.INTEGER,
// composite primary key
primaryKey: true,
references: {
model: 'Film',
key: 'ID',
},
onDelete: 'restrict',
},
CategoryID: {
type: Sequelize.INTEGER,
primaryKey: true,
references: {
model: 'Category',
key: 'ID',
},
onDelete: 'cascade',
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
}
);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Film_Category');
await queryInterface.dropTable('Film');
await queryInterface.dropTable('Category');
await queryInterface.dropTable('Language');
},
};
After executing the db migration, I define models below:
models/category.ts:
import { Model, DataTypes, BelongsToManyGetAssociationsMixin } from 'sequelize';
import { sequelize } from '../db';
import { Film } from './film_category';
class Category extends Model {
public ID!: number;
public Name!: string;
public Last_Update!: Date;
public getFilms!: BelongsToManyGetAssociationsMixin<Film>;
}
Category.init(
{
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: DataTypes.STRING(20),
allowNull: false,
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
},
{ sequelize, modelName: 'Category' },
);
export { Category };
models/film.ts:
import { Model, DataTypes, BelongsToManyGetAssociationsMixin } from 'sequelize';
import { sequelize } from '../db';
import { Category } from './film_category';
class Film extends Model {
public ID!: number;
public LanguageID!: number;
public Title!: string;
public Description!: string;
public Release_Year!: number;
public Rental_Duration!: number;
public Rental_Date!: number;
public Length!: number;
public Replacement_Cost!: number;
public Rating!: number;
public Last_Update!: Date;
public Special_Features!: string;
public Fulltext!: string;
public getCategories!: BelongsToManyGetAssociationsMixin<Category>;
}
Film.init(
{
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
LanguageID: {
type: DataTypes.INTEGER,
references: {
model: 'Language',
key: 'ID',
},
onDelete: 'restrict',
allowNull: false,
},
Title: {
type: DataTypes.STRING,
allowNull: false,
},
Description: {
type: DataTypes.STRING,
allowNull: false,
},
Release_Year: {
type: DataTypes.INTEGER,
allowNull: false,
},
Rental_Duration: {
type: DataTypes.INTEGER,
allowNull: false,
},
Rental_Date: {
type: DataTypes.DECIMAL(19, 0),
allowNull: false,
},
Length: {
type: DataTypes.INTEGER,
allowNull: false,
},
Replacement_Cost: {
type: DataTypes.DECIMAL(19, 0),
allowNull: false,
},
Rating: {
type: DataTypes.INTEGER,
allowNull: false,
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
Special_Features: {
type: DataTypes.STRING,
allowNull: false,
},
Fulltext: {
type: DataTypes.STRING,
allowNull: false,
},
},
{ sequelize, modelName: 'Film' },
);
export { Film };
models/film_category.ts:
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../db';
import { Category } from './category';
import { Film } from './film';
class FilmCategory extends Model {
public FilmID!: number;
public CategoryID!: number;
public Last_Update!: Date;
}
FilmCategory.init(
{
FilmID: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Film',
key: 'ID',
},
onDelete: 'restrict',
},
CategoryID: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Category',
key: 'ID',
},
onDelete: 'cascade',
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
},
{ sequelize, modelName: 'Film_Category' },
);
export { FilmCategory, Film, Category };
models/index.ts:
import { Category } from './category';
import { Film } from './film';
import { Language } from './language';
import { FilmCategory } from './film_category';
Category.belongsToMany(Film, { through: FilmCategory });
Film.belongsToMany(Category, { through: FilmCategory });
Language.hasMany(Film);
export { Category, Film, Language, FilmCategory };
When I try to call Film.findByPk(1), sequelize throw an error:
SequelizeDatabaseError: column "CategoryID" does not exist
The SQL query generated by sequelize of Film.findByPk(1) like this:
Executing (default): SELECT "ID", "LanguageID", "Title", "Description", "Release_Year", "Rental_Duration", "Rental_Date", "Length", "Replacement_Cost", "Rating", "Last_Update", "Special_Features", "Fulltext", "CategoryID" FROM "Film" AS "Film" WHERE "Film"."ID" = 1;
I know when I use Film.belongsToMany(Category, { through: FilmCategory });, sequelize will add CategoryID of target model Category to source model Film. I expect the film data find by primary key has the same properties as the model schema. This extra CategoryID column is the issue.
So I don't want this CategoryID column to be added on Film model and FilmID column to be added on Category model. Because Film table doesn't have CategoryID column and Category table doesn't have FilmID column in the database. They are connected by the join table Film_Category. Is there any way to do this? Or, am I missing something?
Created a minimal reproducible code repo: https://github.com/mrdulin/node-sequelize-examples/tree/master/src/db
Should work if you explicitly define the 'through' table in the association, like so:
Film.belongsToMany(Category,
{
through: FilmCategory,
foreignKey: 'FilmID',
otherKey: 'CategoryID'
});
It's possible the problems may occurs because you're using ID rather than Id, but that is just speculation...
HTH

Cyclic dependency found

I have the following error:
Unhandled rejection Error: Cyclic dependency found. coupons is
dependent of itself. Dependency chain: coupons -> orders => coupons
My order.js model looks like as follows:
'use strict';
module.exports = (sequelize, DataTypes) =>
{
var Order = sequelize.define('orders',
{
id:
{
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
userid:
{
type: DataTypes.INTEGER,
references:
{
model: 'users',
key: 'id'
}
},
coupon_id:
{
type: DataTypes.INTEGER,
allowNull: true,
references:
{
model: 'coupons',
key: 'id'
},
},
product:
{
type: DataTypes.INTEGER,
references:
{
model: 'products',
key: 'id'
}
},
address:
{
type: DataTypes.INTEGER,
references:
{
model: 'address',
key: 'id'
}
},
canceled:
{
type: DataTypes.INTEGER,
defaultValue: 0
},
quantity:
{
type: DataTypes.INTEGER,
},
note:
{
allowNull: true,
type: DataTypes.STRING
},
},
{
freezeTableName: true,
tableName: 'orders',
createdAt: 'createdat',
updatedAt: 'updatedat',
});
Order.associate = models => {
Order.hasMany(models.coupons, {
foreignKey: 'id',
onDelete: 'cascade',
onUpdate: 'cascade',
constrains: false
});
};
return Order;
};
and coupon.js looks like this:
'use strict';
module.exports = (sequelize, DataTypes) =>
{
var Coupon = sequelize.define('coupons',
{
id:
{
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
name:
{
type: DataTypes.STRING
},
code:
{
type: DataTypes.STRING
},
discount:
{
type: DataTypes.FLOAT
},
expires:
{
type: 'TIMESTAMP',
},
created_by:
{
type: DataTypes.INTEGER,
references:
{
model: 'users',
key: 'id'
},
},
maxuse:
{
type: DataTypes.INTEGER,
},
},
{
freezeTableName: true,
tableName: 'coupons',
createdAt: 'createdat',
updatedAt: 'updatedat'
});
Coupon.associate = models => {
Coupon.belongsTo(models.orders,
{
foreignKey: 'id',
onDelete: 'cascade',
onUpdate: 'cascade',
});
};
return Coupon;
};
It seems that I am doing something wrong with the associations. Any help would be appreciated.
NOTE: Everytime I comment out coupon_id: on orders.js, the error goes out. Still, I need this functionality.

Resources