sequelize-typescript many-to-many relationship model data with - postgresql

I am using sequelize with sequelize-typescript library, and am trying to achieve the following relationship:
Team.ts
#Scopes({
withPlayers: {
include: [{model: () => User}]
}
})
#Table
export default class Team extends Model<Team> {
#AllowNull(false)
#Column
name: string;
#BelongsToMany(() => User, () => TeamPlayer)
players: User[];
}
User.ts
#Scopes({
withTeams: {
include: [{model: () => Team, include: [ () => User ]}]
}
})
#Table
export default class User extends Model<User> {
#AllowNull(false)
#Column
firstName: string;
#AllowNull(false)
#Column
lastName: string;
#BelongsToMany(() => Team, () => TeamPlayer)
teams: Team[];
}
TeamPlayer.ts
#DefaultScope({
include: [() => Team, () => User],
attributes: ['number']
})
#Table
export default class TeamPlayer extends Model<TeamPlayer> {
#ForeignKey(() => User)
#Column
userId: number;
#ForeignKey(() => Team)
#Column
teamId: number;
#Unique
#Column
number: number;
}
Now when querying for player, you get the object with the following data:
{
"id": 1,
"name": "Doe's Team",
"players": [
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"TeamPlayer": {
"userId": 1,
"teamId": 1,
"number": 32
}
}]
}
Now there are couple of things that I cannot get done..
1) I want to rename the TeamPlayer to something like "membership"; but not by changing the name of the class
2) the content of TeamPlayer should not have the id`s, but I want it to contain the data of the team, for example:
{
"firstName": "John",
"lastName": "Doe"
"membership": {
"number": 32
}
In the above classes, I tried to set a scope to the TeamPlayer class to only include number inside the TeamMember inclusion, but no effect.
I used to have the TeamPlayer class have direct memberships to team and player, but that solution added redundant id to the TeamPlayer class, and also did not prevent duplicate memberships in the team. I could indeed manually (= in code) prevent duplicates in these situations, but that does not feel elegant.

I ended up solving this by adding one-to-many relationships from TeamPlayer to User and Team, and also figured out the way to make the teamId + userId pair unique by adding two more fields with #ForeignKey like this:
TeamPlayer.ts
#Table
export default class TeamPlayer extends Model<TeamPlayer> {
#BelongsTo(() => Team)
team: Team;
#ForeignKey(() => Team)
#PrimaryKey
#Column
teamId: number;
#BelongsTo(() => User)
user: User;
#ForeignKey(() => User)
#PrimaryKey
#Column
userId: number;
#Column
number: number;
}
User.ts
#Table
export default class User extends Model<User> {
#AllowNull(false)
#Column
firstName: string;
#AllowNull(false)
#Column
lastName: string;
#HasMany(() => TeamPlayer)
teams: TeamPlayer[];
}
Team.ts
export default class Team extends Model<Team> {
#AllowNull(false)
#Column
name: string;
#HasMany(() => TeamPlayer)
players: TeamPlayer[];
}
This solution allows me to control the included query attributes via scopes, and gives me proper object output.

Related

How do I search in the postgresql using Typeorm and return the mutated value for relation?

I have a User entity with a Role relationship in an application Nestjs.
#Entity()
class User {
...
#ManyToOne(() => Role, {
eager: true,
})
role?: Role;
}
#Entity()
export class Role extends EntityHelper {
#PrimaryColumn()
id: number;
#Column()
name?: string;
}
when I want get users from the DB I do:
this.usersRepository.find({
relations: {
role: true,
},
})
and I get data like this:
{ id: 1, name: "John", role: { id: 2, name: "user" }}
but I don't want to get the role as an object, I just want the name from this
Example:
{ id: 1, name: "John", role: "user" }
So, my question is how can I get the relation and return only the value of { ... role: "user" }?
You can achieve it by using QueryBuilder:
this.usersRepository.createQueryBuilder('User')
.innerJoinAndSelect('User.role', 'Role')
.select(['Role.name as role', 'User.id as id'])
.getRawMany()

Create foreign key that references another schema

I have a multi tenant app which uses sequelize-typescript. Each time a new user is created, a new schema is created for them.
There's only one table in public schema - "Users".
Each tenant schema has "Roles" and "UserRoles" as a junction table.
I want UserRoles to reference Users in a public schema, not in a tenant schema. How can I achieve that?
user model
#Table({ paranoid: true })
export default class User extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#Column(DataType.STRING)
name: string;
#BelongsToMany(() => Role, () => UserRole)
roles: Role[];
role model
#Table({ paranoid: true })
export default class Role extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#Column(DataType.STRING)
name: string;
#BelongsToMany(() => User, () => UserRole)
users: User[];
}
user-role model
#Table({ paranoid: true, freezeTableName: true })
export default class UserRole extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#ForeignKey(() => Role)
#Column(DataType.BIGINT)
roleId: number;
#Unique
#ForeignKey(() => User)
userId: number;
}
I tried to specify schema inside column, but it still references tenant schema
#Table({ paranoid: true, freezeTableName: true })
export default class UserRole extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#ForeignKey(() => Role)
#Column(DataType.BIGINT)
roleId: number;
#Unique
#ForeignKey(() => User)
#Column({
type: DataType.BIGINT,
references: {
model: {
tableName: 'users',
schema: 'public',
},
key: 'id',
},
})
userId: number;
}

TypeORM does not make further query for nested object

I'm currently using PostgresQL with typeORM, as well as Typegraphql. With the ManyToOne (User has many orderItems) relationship, somehow I cannot query for the nested object relation. I set the logging: true and saw that there is no SELECT query for the User entity. However, I think the query should be automatically generated giving the relation I defined in the Entity according to TypeORM.
In CartItem.ts Entity
#ObjectType()
#Entity()
export class CartItem extends BaseEntity {
#PrimaryGeneratedColumn()
#Field()
id!: number;
#Column()
#Field()
userId: string;
#Field(() => User, { nullable: true })
#ManyToOne((type) => User, (user) => user.cartItems)
user: User;
In User.ts Entity
export class User extends BaseEntity {
#PrimaryGeneratedColumn("uuid")
#Field()
id!: string;
#OneToMany((type) => CartItem, (cartItem) => cartItem.user)
cartItems: CartItem[];
In cartItem.ts Resolver
#Mutation(() => CartItem)
#UseMiddleware(isAuth)
async createCartItem(
#Arg("input") input: CartItemInput,
#Ctx() { req }: MyContext
): Promise<CartItem> {
const newCart = await CartItem.create({
quantity: input.quantity,
userId: req.session.userId,
mealkitId: input.mealkitId,
}).save();
return newCart;
With the following graphql Query, user would return null even though I'm supposed to get the username of the user
query cartItems{
cartItems {
id
quantity
userId
user{
username
}
}
}
Here is the response I received
{
"data": {
"cartItems": [
{
"id": 2,
"quantity": 2,
"userId": "5619ffb2-6ce2-42cf-bd5c-042f2685a045",
"user": null
},
{
"id": 1,
"quantity": 10,
"userId": "5619ffb2-6ce2-42cf-bd5c-042f2685a045",
"user": null
}
]
}
}```
I just ran into this myself and in my query resolver I had to leftJoinAndSelect all of the sub-objects to get it to work. You aren't showing your query resolver, but something like
async cartItems(): Promise<CartItem[]> {
return await getConnection()
.createQueryBuilder(CartItem, 'cartItem')
.leftJoinAndSelect('cartItem.user', 'user', 'cartItem.userId = user.id')
.getMany()
}

findUnique query returns null for array fields

I read the Prisma Relations documentation and it fixed my findMany query which is able to return valid data but I'm getting inconsistent results with findUnique.
Schema
model User {
id Int #id #default(autoincrement())
fname String
lname String
email String
password String
vehicles Vehicle[]
}
model Vehicle {
id Int #id #default(autoincrement())
vin String #unique
model String
make String
drivers User[]
}
Typedefs
const typeDefs = gql'
type User {
id: ID!
fname: String
lname: String
email: String
password: String
vehicles: [Vehicle]
}
type Vehicle {
id: ID!
vin: String
model: String
make: String
drivers: [User]
}
type Mutation {
post(id: ID!, fname: String!, lname: String!): User
}
type Query {
users: [User]
user(id: ID!): User
vehicles: [Vehicle]
vehicle(vin: String): Vehicle
}
'
This one works
users: async (_, __, context) => {
return context.prisma.user.findMany({
include: { vehicles: true}
})
},
However, for some reason the findUnique version will not resolve the array field for "vehicles"
This one doesn't work
user: async (_, args, context) => {
const id = +args.id
return context.prisma.user.findUnique({ where: {id} },
include: { vehicles: true}
)
},
This is what it returns
{
"data": {
"user": {
"id": "1",
"fname": "Jess",
"lname": "Potato",
"vehicles": null
}
}
}
I was reading about fragments and trying to find documentation on graphql resolvers but I haven't found anything relevant that can solve this issue.
Any insight would be appreciated! Thanks!
You need to fix the arguments passed to findUnique. Notice the arrangement of the { and }.
Change
return context.prisma.user.findUnique({ where: { id } },
// ^
include: { vehicles: true}
)
to
return context.prisma.user.findUnique({
where: { id },
include: { vehicles: true }
})

Typeorm update record in OneToMany relation

I have the following two entities with a OneToMany/ManyToOne relation between them:
#Entity({ name: 'user' })
export class User {
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ type: 'varchar' })
firstName: string;
#Column({ type: 'varchar' })
lastName: string;
#ManyToOne(() => Group, group => group.id)
#JoinColumn({ name: 'groupId' })
group: Group;
#Column({ type: 'integer' })
groupId: number;
}
#Entity({ name: 'group' })
export class Group {
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ type: 'varchar' })
name: string;
#OneToMany(() => User, user => user.group)
members: User[];
}
When I create a new group in my Group repository, I can add existing members as follows:
const group = new Group();
group.name = 'Test';
group.members = [{ id: 1 }, { id: 2 }]; // user ids
this.save(group);
I am wondering if I can update existing members in a similar way. For example if a group with id 1 has two members:
{ id: 1, firstName: 'Member', lastName: 'One', groupId: 1 }
{ id: 2, firstName: 'Member', lastName: 'Two', groupId: 1 }
Is it possible to update a member through the OneToMany relation in a similar way as I'm adding them ? Something like (making this up):
const group = await repository.findOne(groupId, { relations: ['members'] });
group.members = [{ id: 1, firstName: 'Updated' }]; // this would update the firstName of member with id 1 if it exists in the relation
repository.save(group);
Thanks!
There is no built in functionality in typeorm to update specific related entity from the base entity. However if you want to do this then you can add a general function which updates the base table as well as it's relations.