for some reason, one of my ManyToOne relationship not saving to DB. I have the following entity. The "by" relationship is saving just fine. But the evidence is not. I do have the evidenceId foreign key column in my table. But when I do entity.save(), the evidenceId is always null. Any ideas what I'm missing here?
#Entity('evidence_log')
export class EvidenceLogEntity extends Base {
#ManyToOne(() => EvidenceEntity, evidence => evidence.logs)
evidence: EvidenceEntity;
#Column()
action: string;
#ManyToOne(() => UserEntity)
by: UserEntity;
#Column()
text: string;
constructor(evidence?: EvidenceEntity, action?: string, by?: UserEntity) {
super();
this.evidence = evidence || this.evidence;
this.action = action || this.action;
this.by = by || this.by;
this.text = this.text || this.by ? this.getText() : '';
}
getText(): string {
return `${this.by.firstName} ${this.by.lastName} (${this.by.id}) ${this.action} Evidence "${this.evidence.name}"`;
}
}
Related
Are there any ways or would it be possible to have two validator in one single entity? Like for the given example code below, the identifier would accept an email as its payload but it would also accept
number/mobile number as its payload as well.
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
identifier: string;
EDIT:
I have tried,
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
#IsPhoneNumber('US')
identifier: string;
But it does not work.
EDIT 2:
I found a reference code based on this previous thread, How to use else condition in validationif decorator nestjs class-validator?, and I copied his validation class.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from "class-validator";
import { IdentifierType } from "../interface/access.interface";
#ValidatorConstraint({ name: 'IdentifierValidation', async: false })
export class IdentifierValidation implements ValidatorConstraintInterface {
validate(identifier: string, args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
var regexp = new RegExp('/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im');
// "regexp" variable now validate phone number.
return regexp.test(identifier);
} else {
regexp = new RegExp("^[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$");
// "regexp" variable now validate email address.
return regexp.test(identifier);
}
}
defaultMessage(args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
return 'Enter a valid phone number.'
} else {
return 'Enter a valid email address.'
}
}
}
DTO -
export class VerifyOtpDto {
#Validate(IdentifierValidation)
#ApiProperty()
#IsNotEmpty()
identifier: string;
#ApiProperty({ enum: IdentifierType })
#IsNotEmpty()
identifierType: IdentifierType;
}
ENUM -
export enum IdentifierType {
EMAIL = 'email',
MOBILE = 'mobile',
}
It does work with email but trying to feed a mobile number still does not work.
You have two ways to do this, first with regex:
#Matches(/YOUR_REGEX/, {message: 'identifier should be email or phone'})
identifier: string;
Or you can get the idea from this:
#IsType(Array<(val: any) => boolean>)
#IsType([
val => typeof val == 'string',
val => typeof val == 'boolean',
])
private readonly foo: boolean | string;
Of course it can get more than one validator in one DTO column.
Did you check https://www.npmjs.com/package/class-validator here?
if you want to check mobile number, you can use to #IsMobilePhone(locale: string).
I've got a Question regarding TypeORM-Relations and how to use them 'nest-like'.
Suppose I have two Entities defined ChildEntity and TestEntity, which are related.
TestEntity:
import { ChildEntity } from 'src/modules/child-entity/entities/child-entity.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
constructor(name: string, childEntity: ChildEntity) {
this.name = name;
this.childEntity = childEntity;
}
}
My first question occurs when I want to create the entity. I have to first translate the passed childEntityId into a ChildEntity, which I can pass to the constructor:
CreateTestEntityDto
import { ApiProperty } from '#nestjs/swagger';
import { IsNotEmpty, IsNumber } from 'class-validator';
export class CreateTestEntityDto {
#ApiProperty()
#IsNotEmpty()
name: string;
#ApiProperty()
#IsNumber()
childEntityId: number;
constructor(name: string, childEntityId: number) {
this.name = name;
this.childEntityId = childEntityId;
}
}
async create(createTestEntityDto: CreateTestEntityDto) {
const { name, childEntityId } = createTestEntityDto;
const childEntity = await this.childEntityService.findOne(childEntityId);
const testEntity = new TestEntity(name, childEntity);
return this.testEntityRepo.save(testEntity);
}
Is there a way to just pass the childEntityId to the save()-Method without explicitly looking for the ChildEntity beforehand?
The Second problem occurs when updating.
UpdateTestEntityDto
import { PartialType } from '#nestjs/swagger';
import { CreateTestEntityDto } from './create-test-entity.dto';
export class UpdateTestEntityDto extends PartialType(CreateTestEntityDto) {}
As updating only a partial Entity is possible I have to check if the Id is even passed along the request and if it is I have to retrieve the correct Entity for the update. Is there a more streamlined way to do this?
async update(id: number, updateTestEntityDto: UpdateTestEntityDto) {
const { name, childEntityId } = updateTestEntityDto;
const props = { name };
if (childEntityId) {
props['childEntity'] = await this.childEntityService.findOne(
childEntityId,
);
}
return this.testEntityRepo.update(id, props);
}
You should add a childEntityId to the test entity:
#Entity()
export class TestEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar')
name: string;
#Column('int')
childEntityId: number;
#ManyToOne(() => ChildEntity, (childEntity) => childEntity.testEntities)
childEntity: ChildEntity;
...
}
and then you can use it to set the id directly. Something like:
async create(dto: Dto) {
const { name, childEntityId } = dto;
const entity = new TestEntity();
entity.name = name;
entity.childEntityId = childEntityId;
return this.testEntityRepo.save(entity);
}
Check this out.
1.) Saving relational entity
There's no need to do all these roundtrips cluttering to save the entity. While, the solution given by #UrosAndelic works but still there's no need to write 3 extra lines of code.
If you hover over a relational param inside the create() method of the repository from an IDE, you'll notice that it accepts two types. First, An Instance of an entity OR Second, a DeepPartial object of an entity.
For instance:
const entity = this.testEntityRepo.create({
name: 'Example 1',
childEntity: {
id: childEntityId // notice: it's a DeepPartial object of ChildEntity
}
})
await this.testEntityRepo.save(entity)
2.) Updating entity
There's no need for child entity's id if you are updating test entity. You can simply update the props of test entity.
const testEntityId = 1;
await this.testEntityRepo.update(testEntityId, {
name: 'Example 2'
})
This will update the name of TestEntity = 1;
I'm working on refactoring a koa api to nest and am kinda stuck on refactoring the queries from native psql to typeorm. I have the following table, view and dto.
#Entity()
export class Challenge {
#PrimaryGeneratedColumn()
id!: number;
#Column()
endDate!: Date;
#CreateDateColumn()
createdAt!: Date;
}
#ViewEntity({
expression: (connection: Connection) => connection.createQueryBuilder()
.select('SUM(cp.points)', 'score')
.addSelect('cp.challenge', 'challengeId')
.addSelect('cp.user', 'userId')
.addSelect('RANK() OVER (PARTITION BY cp."challengeId" ORDER BY SUM(cp.points) DESC) AS rank')
.from(ChallengePoint, 'cp')
.groupBy('cp.challenge')
.addGroupBy('cp.user')
})
export class ChallengeRank {
#ViewColumn()
score!: number;
#ViewColumn()
rank!: number;
#ViewColumn()
challenge!: Challenge;
#ViewColumn()
user!: User;
}
export class ChallengeResultReponseDto {
#ApiProperty()
id!: number;
#ApiProperty()
endDate!: Date;
#ApiProperty()
createdAt!: Date;
#ApiProperty()
score: number;
#ApiProperty()
rank: number;
test() {
console.log("test")
}
}
As the object I want to return is not of any entity type, I'm kinda lost on how to select it and return the correct class. I tried the following:
this.challengeRepository.createQueryBuilder('c')
.select('c.id', 'id')
.addSelect('c.endDate', 'endDate')
.addSelect('c.createdAt', 'createdAt')
.addSelect('cr.score', 'score')
.addSelect('cr.rank', 'rank')
.leftJoin(ChallengeRank, 'cr', 'c.id = cr."challengeId" AND cr."userId" = :userId', { userId })
.where('c.id = :id', { id })
.getRawOne<ChallengeResultReponseDto>();
Which returns an object that has the correct fields, but that is not of the class type "ChallengeResultReponseDto". If I try to call the function "test" the application crashes. Further it feels weird to use the challengeRepository but not return a challenge, should I use the connection or entity manager for this instead?
I'm rather certain that getRawOne<T>() returns a JSON that looks like whatever you give the generic (T), but an not instance of that class. You should try using getOne() instead to get the instance of the returned entity
Let's say I have a class like this one :
class Entity {
id?: string;
name?: string;
constructor({ id, name }: { id?: string; name?: string }) {
this.id = id;
this.name = name;
}
}
And then I have a function like this which requires an argument of type Entity but with the id property field set:
function myFunc(entity: Entity & { id: string }) {
console.log("do something", entity.id);
}
I would expect the following to work, but it doesn't:
const myEntityWithoutID = new Entity({ name: "no id" });
const myEntityWithID = new Entity({ name: "dummy", id: "dummy" });
myFunc(myEntityWithoutID); // error, as expected
myFunc(myEntityWithID); // error, but this should work
I'm not sure how I can force typescript to correctly infer the type of the class instance. I hope I've given enough information in order to present the problem, does anyone have any idea ?
I have a model in sequelize-typescript, Door.ts:
import { Table, Model, Column, AutoIncrement, PrimaryKey, ForeignKey, DataType, AllowNull, BelongsTo, HasMany } from 'sequelize-typescript';
import { Location } from '#modules/location';
import { AkilesServiceV1, AkilesServiceV0, IDoorService } from '#services/DoorService';
import { BelongsToGetAssociationMixin } from 'sequelize/types';
import { DoorLog } from '#modules/door_log';
import { HasManyCreateAssociationMixin } from 'sequelize';
#Table({ tableName: 'door' })
class Door extends Model<Door> {
#PrimaryKey
#AutoIncrement
#Column
id!: number;
#AllowNull(false)
#Column
type!: string;
#Column
button_id!: string;
#Column
gadget_id!: string;
#Column
action_id!: string;
#AllowNull(false)
#Column(DataType.ENUM('vehicular','pedestrian'))
access_type!: 'vehicular' | 'pedestrian';
#AllowNull(false)
#Column
description_tag!: string;
#Column(DataType.VIRTUAL)
description!: string;
#ForeignKey(() => Location)
#AllowNull(false)
#Column
location_id!: number;
#BelongsTo(() => Location)
location!: Location;
#HasMany(() => DoorLog)
door_logs!: DoorLog[];
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
public async open () {
let doorService: IDoorService;
switch(this.type) {
case 'akiles-v0':
doorService = new AkilesServiceV0();
break;
case 'akiles-v1':
doorService = new AkilesServiceV1();
break;
default:
doorService = new AkilesServiceV1();
break;
}
//await doorService.open(this);
return await this.createDoorLog({ door_id: this.id, timestamp: new Date() });
}
public async getParking() {
const location: Location = await this.getLocation();
return await location.getParking();
}
}
export default Door
As you can see it has these two functions associated with Mixins:
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
The first works perfectly using it like this: await this.getLocation(). However, the second when I call it like this: await this.createDoorlog ({door_id: this.id, timestamp: new Date ()}) returns the following error:
TypeError: this.createDoorLog is not a function
I've also tried calling the function without parameters but got the same result. I don't understand why the two functions, while created almost identically, behave differently. Am I missing something with HasManyCreateAssociationMixin?
Thank you.
For when I inevitably come across this question again perplexed by the same problem. The answer Is to ad "as" to the #HasMany mixin. Sequelize appears to have issues with camelcase classes.
So in this case adding
#HasMany(() => DoorLog, options: {as: "doorLog" })
door_logs!: DoorLog[];
or something along these lines should allow you to use this mixin