Config
nest: 9.0.0
typeorm : 0.3.7
node: v14.17.1
npm: 6.14.13
mysql: 5.7.33
Data model
------------ -------------------- -----------
|VEHICLE | |VEHICLE_ASSIGNMENT| |DRIVER |
|----------| |------------------| |---------|
|VEHICLE_ID|<----|VEHICLE_ID | |FIRSTNAME|
|BRAND | |DRIVER_ID |---->|DRIVER_ID|
|MODEL | |ASSIGNMENT_DATE | |LASTNAME |
------------ -------------------- -----------
Rules
A vehicle is assignable to one driver at a time
A driver can drive only one vehicle at a time (even Chuck Norris, I see you comming)
3 entities
#Entity('VEHICLE')
export class VehicleEntity {
#PrimaryColumn({ name: 'VEHICLE_ID', length: 10 })
id: string;
#Column({ name: 'BRAND', length: 30, nullable: true })
brand: string;
#Column({ name: 'MODEL', length: 30, nullable: true })
model: string;
#OneToOne(
() => VehicleAssignmentEntity,
(vehicleAssignment) => vehicleAssignment.vehicle,
{
nullable: true,
createForeignKeyConstraints: false,
},
)
#JoinColumn({ name: 'VEHICLE_ID' })
#Exclude()
vehicleAssignment?: VehicleAssignmentEntity;
}
#Entity('VEHICLE_ASSIGNMENT')
export class VehicleAssignmentEntity {
#PrimaryColumn({ name: 'DRIVER_ID', length: 50 })
driverId: string;
#OneToOne(() => DriverEntity, {
nullable: false,
})
#JoinColumn({ name: 'DRIVER_ID' })
#Type(() => DriverEntity)
driver: DriverEntity;
#PrimaryColumn({ name: 'VEHICLE_ID', length: 10 })
vehicleId: string;
#OneToOne(() => VehicleEntity, {
nullable: false,
})
#JoinColumn({ name: 'VEHICLE_ID' })
#Type(() => VehicleEntity)
vehicle: VehicleEntity;
#Column({ name: 'ASSIGNATION_DATE', nullable: false })
affectationDate: Date;
}
#Entity('DRIVER')
export class DriverEntity {
#PrimaryColumn({ name: 'DRIVER_ID', length: 50 })
id: string;
#Column({ name: 'FIRSTNAME', length: 50, nullable: true })
firstname: string;
#Column({ name: 'LASTNAME', length: 50, nullable: true })
lastname: string;
#OneToOne(
() => VehicleAssignmentEntity,
(vehicleAssignment) => vehicleAssignment.driver,
{
nullable: true,
createForeignKeyConstraints: false,
},
)
#JoinColumn({ name: 'DRIVER_ID' })
#Exclude()
vehicleAssignment?: VehicleAssignmentEntity;
}
In the DriverRepository
async findDriverById(id: string): Promise<DriverEntity> {
return this.findOne({
relations: {
vehicleAssignment: true,
},
where: {
id: id,
},
});
},
The issue
When the method findDriverById is called, the SQL generated is weird :
... LEFT JOIN VEHICLE_ASSIGNMENT ON VEHICLE_ID=DRIVER_ID AND DRIVER_ID=VEHICLE_ID ...
The two keys of the table VEHICLE_ASSIGNMENT are inverted in the JOIN :-o !
The only workaround I found at this time : invert the two properties (driver & vehicle) in VehicleAssignementEntity ; in this case, the SQL generated id correct
Related
I have set up a many to many relation between a user table and a spot table.
Here is the user entity:
#Entity('user')
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ default: '' })
first_name: string;
#Column({ default: '' })
last_name: string;
#Column({ default: '' })
email: string;
#Column({ default: '' })
adress: string;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#ManyToMany(() => SpotEntity, (spot) => spot.users)
#JoinTable({
name: 'spot_user',
joinColumn: {
name: 'user_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'spot_id',
referencedColumnName: 'id',
},
})
spots: Spot[];
}
And here is the spot entity:
#Entity('spot')
export class SpotEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ default: '' })
title: string;
#Column({ default: '' })
description: string;
#Column({ default: '' })
address: string;
#Column({ default: '' })
coordinates: string;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#ManyToOne(() => TagEntity, (tag) => tag.spots)
tag: TagEntity;
#ManyToMany(() => UserEntity, (user) => user.spots)
users: User[];
}
This creates the join table as it should.
But now I am a bit confused as to how add a spot to a user.
To do so I added a service like so, which has access to both user and spot repository:
I a able to get the spot and the user, but the spot never saves into the user, and the join table is never updated.
async addSpotToUser(id?: number, spotId?: number): Promise<void> {
const mySpot = Object.values(spotId)[0];
const user = await this.userRepository.findOneBy({ id: Number(id) });
const spot = await this.spotRepository.findOneBy({ id: mySpot });
user.spots.push(spot);
this.userRepository.save(user);
}
}
I have a problem, when launching a query "Cannot return null for non-nullable field Account.groups".
I have graphql resolver:
#Resolver(of => Account)
export class AccountResolver {
constructor(
#Inject(AccountService) private accountService: AccountService,
) { }
#Query(returns => [Account], {
description: `To get all client's accounts`
})
#UseGuards(AuthGuard)
async accounts(
#Context('clientId') clientId: string,
): Promise<Account[]> {
return await this.accountService.getAllAccounts(clientId);
}
}
And model:
#ObjectType()
#Entity()
export class Account {
#Field()
#PrimaryGeneratedColumn('uuid')
id: string;
#Field()
#Column('text', {
default: ''
})
firstName: string;
#Field()
#Column('text', {
default: ''
})
lastName: string;
#Field()
#Column('text', {
nullable: true,
default: ''
})
avatar: string;
#Field()
#Column('text', {
unique: true,
})
username: string;
#Column('text', { nullable: false })
#Exclude({ toPlainOnly: true })
password: string;
#Field()
#Column({
type: 'varchar',
length: 300,
enum: Roles,
nullable: false
})
role: Roles
#Field()
#Column('text', { nullable: false, unique: true })
email: string;
#Field()
#Column({
default: false
})
emailVerified: boolean;
#Field()
#Column('text', { nullable: true, default: ''})
verificationLink: string;
#OneToMany(() => KitTask, kitTask => kitTask.account)
kitTasks: KitTask[];
#OneToMany(() => Trace, trace => trace.account)
traces: Trace[];
#Field(type => Client)
#ManyToOne(() => Client, client => client.accounts, { onDelete: 'CASCADE' })
client: Client;
#Field(type => Group)
#ManyToMany(() => Group, group => group.accounts)
#JoinTable()
groups: Group[];
}
I'm trying to launch query:
query accounts{
accounts{
id
firstName
lastName
avatar
username
role
email
emailVerified
client {
name
}
groups {
id
}
}
And getting the following error:
What is wrong?
When i'm trying to make a POST request, it records data in client table but doesn't in user_profile table, also it's doesn't record passportId in passport table. And also after request i got this error.
What am i missing?
{
"msg": "insert or update on table "user_profile" violates foreign key constraint "FK_259184fcdfba6d78485f5a048a0""
}
My ClientEntity:
#Entity()
export default class Client extends BaseEntity {
#PrimaryGeneratedColumn()
clientId: number;
#Column({ type: 'enum', enum: ClientStatus, default: ClientStatus.NOT_CLIENT })
clientStatus: ClientStatus;
#Column({ nullable: true })
countryOfResidence: string;
#Column({ nullable: true, length: 30 })
firstName: string;
#Column({ nullable: true, length: 30 })
lastName: string;
#Column({ nullable: true, length: 30 })
middleName: string;
#OneToOne(() => Passport)
#JoinColumn({ referencedColumnName: 'passportId', name: 'passportId' })
passportId: string;
#OneToOne(() => AuthVerification)
#JoinColumn({ referencedColumnName: 'mobilePhone', name: 'mobilePhone' })
mobilePhone: string;
#OneToOne(() => UserProfile, (userProfile) => userProfile.clientId)
userProfile: UserProfile;
}
My UserProfileEntity:
#Entity()
class UserProfile extends BaseEntity {
#PrimaryColumn()
#OneToOne(() => Client)
#JoinColumn({ referencedColumnName: 'clientId', name: 'clientId' })
clientId: number;
#Column({ nullable: true, default: false })
smsNotification: boolean;
#Column({ nullable: true, default: false })
pushNotification: boolean;
#Column({ nullable: true, default: false })
emailSubscription: boolean;
#Column({ length: 50, nullable: true })
securityQuestion: string;
#Column({ length: 50, nullable: true })
securityAnswer: string;
#Column({ length: 50, nullable: true, default: null })
email: string;
#Column({ length: 255, nullable: true })
password: string;
}
Line in migration with Constraint:
await queryRunner.query(
`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_259184fcdfba6d78485f5a048a0" FOREIGN KEY ("clientId") REFERENCES "client"("clientId") ON DELETE NO ACTION ON UPDATE NO ACTION`
);
My register function:
public async registerNonClients(req: Request, res: Response) {
try {
const {
clientId,
mobilePhone,
password,
securityQuestion,
securityAnswer,
email,
firstName,
lastName,
middleName,
passportId,
countryOfResidence,
} = req.body;
await getRepository(Client).save({
clientId,
mobilePhone,
firstName,
lastName,
middleName,
countryOfResidence,
});
await getRepository(UserProfile).save({ clientId, password, securityQuestion, securityAnswer, email });
await getRepository(Passport).save({ passportId });
return res.status(StatusCodes.OK).json({ msg: 'Register successfull' });
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: error.message });
}
}
I'm trying to make a POST-request and getting this error
{
"msg": "insert or update on table "client" violates foreign key constraint "FK_e61c5ecfaabf7e6d9285fbfb070""
}
My Client entity:
#Entity()
export default class Client extends BaseEntity {
#PrimaryGeneratedColumn()
clientId: number;
#Column({ type: 'enum', enum: ClientStatus, default: ClientStatus.NOT_CLIENT })
clientStatus: ClientStatus;
#Column({ nullable: true })
countryOfResidence: string;
#Column({ nullable: true, length: 30 })
firstName: string;
#Column({ nullable: true, length: 30 })
lastName: string;
#Column({ nullable: true, length: 30 })
surName: string;
#OneToOne(() => Passport)
#JoinColumn({ referencedColumnName: 'passportId', name: 'passportId' })
passportId: string;
#OneToOne(() => AuthVerification)
#JoinColumn({ referencedColumnName: 'mobilePhone', name: 'mobilePhone' })
mobilePhone: string;
#OneToOne(() => UserProfile, (userProfile) => userProfile.clientId)
userProfile: UserProfile;
}
My AuthVerification entity:
#Entity()
class AuthVerification extends BaseEntity {
#PrimaryColumn()
#OneToOne(() => Client)
mobilePhone: string;
#Column({ nullable: true })
email: string;
#Column({ nullable: false, length: 6 })
verificationCode: string;
}
Line in migration where constraint key mentioned:
await queryRunner.query(
`ALTER TABLE "client" ADD CONSTRAINT "FK_e61c5ecfaabf7e6d9285fbfb070" FOREIGN KEY ("mobilePhone") REFERENCES "auth_verification"("mobilePhone") ON DELETE NO ACTION ON UPDATE NO ACTION`
);
So, I can't figure out where the problem is.
Have got error 'Cannot read properties of undefined (reading 'joinColumns')'
How to implement: user can have only one assigned role with permissions to resources?
I used sequilize before, in typeorm I'm newbee
user_roles table entity:
#Entity({ name: 'user_roles' })
export class UserRoleEnitity extends DateAudit implements IUserRole {
#Column('varchar')
name: string;
#Column('varchar', {
nullable: true,
})
description?: string;
#Column('boolean', {
name: 'is_active',
default: false
})
isActive?: boolean;
#OneToMany(() => RolePermissionsEnitity, permission => permission.id)
permissions: IUserRolePermission[];
}
and users entity:
#Entity({ name: 'users' })
export class UserEnitity extends DateAudit implements IUser {
#JoinColumn({ name: 'role_id' })
roleId: string;
#OneToMany(() => UserRoleEnitity, role => role.id)
role: UserRoleEnitity;
#Column('varchar', {
unique: true,
})
username: string;
#Column('varchar')
password: string;
#Column('varchar', {
nullable: true,
})
email?: string;
#Column('varchar', {
name: 'mobile_number',
nullable: true,
})
modileNumber?: string;
#Column('varchar', {
nullable: true,
})
name?: string;
#Column('varchar', {
nullable: true
})
position?: string;
#Column({
name: 'is_verified',
default: true
})
isVerified?: boolean;
#Column('timestamp', {
name: 'password_modified_at',
default: new Date()
})
passwordModifiedAt?: Date;
}
and role_permissions
#Entity({ name: 'user_role_permissions' })
export class RolePermissionsEnitity extends DateAudit implements IUserRolePermission {
#JoinColumn({ name: 'role_id' })
roleId: string;
#ManyToOne(() => UserRoleEnitity)
role: IUserRole;
#Column({
type: 'enum',
enum: Actions,
default: Actions.READ
})
action: Actions;
#JoinColumn({ name: 'resource_id' })
resourceId: string;
#ManyToOne(() => ResourceEntity, resource => resource.id)
resource: IResource;
}
When i query repository like this:
const userEntity = await this._userRepository.findOne({
where: {
username,
},
relations: ['role']
});
Your parent entity should not refer to an ID, you should have a relation to a field that defines your user entity.
like this :
UsersRoleEntity
#OneToMany(() => RolePermissionsEnitity, permission => permission.role)
permissions: IUserRolePermission[];
UsersEntity
#OneToMany(() => UserRoleEnitity, role => role.id)
role: UserRoleEnitity;
You don't need to use JoinColumn() in ManyToOne and OneToMany relations.
You can find more information here:
TypeORM Relations Documentation