So, I'm using TypeORM with the ActiveRecord pattern and have this entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
public id: number;
#Column()
public username: string;
#Column()
public password: string;
#BeforeInsert()
public async hashPassword() {
this.password = await hashPassword(this.password);
}
}
now what I want to accomplish is rehashing my password when any given User changes BUT only if the password field changed. I have seen some answers where I should store the tempPassword as a field in the User class but if for some reason the server goes down I would lose that data. I have also seen some people suggest the Subscriber thing typeorm has and im interested in that but not exactly sure how I would implement this with that.
for reference this is how I would do what I want to do with mongoose
UserSchema.pre("save", function(next) {
if (this.isModified("password") || this.isNew) {
// hash the password
}
})
any help is appreciated
According to TypeOrm documentation, You can define a method with any name in the entity and mark it with #BeforeUpdate decorator and TypeORM will call it before an existing entity is updated using repository/manager save.
#BeforeUpdate()
async updatePassword(): Promise<void> {
this.password = await hashPassword(this.password);;
}
You can check inside whatever you want, but I don't think it'll be necessary because if the password field is not changed it will be the same, and ORM will not update this column anyway. It'll only be updated if it's changed.
Mongoose provides us with the isModified() & isNew() methods because it does not handles the checks for you, you need to explicitly check if the field is actually modified or new.
Related
I am using NestJS to build an api, and everything is working great so far!
I have a users table and a corresponding users.entity.ts file:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
...
I am using AWS Cognito as an authentication provider - everything is working great there too. I am trying not to duplicate things, so I'd like to keep given_name and family_name values with Cognito and not have redundant fields on my postgres table.
I can get the user without problems, but I'm not sure how to "combine" them into my user entity.
For example,
// users.service.ts
const user = await this.usersRepository.findOne({
where: {
id: userId,
},
});
// id: dbc92...
// email: example#email.com
const cognitoUser = await this.cognitoService.adminGetUser(user.id);
// id: dbc92...
// email: example#email.com
// given_name: "Tony"
// family_name: "Stark"
return user;
I don't have a given_name property on my user entity—I'm not sure if I need one?
Ideally, I think what I'm trying to do is something like,
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
...
return user
But since my user.entity file doesn't have a givenName property, it's not to happy to do that.
I know I can "merge" the two objects using a spread operator, but I'd like to return the actual user entity if possible. How can I pull from two sources and return the proper entity?
As Mohammad said, if what you are trying to accomplish is:
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
then you can do this:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
given_name: string;
family_name: string;
}
and be able to handle the data just from User but without replicating data in the database with unnecessary columns.
You can add your desired properties without adding #Column decorator. It will not make column in your table but still you have them in your entity.
From the official docs: https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md#afterload
You can define a method with any name in entity and mark it with #AfterLoad and TypeORM will call it each time the entity is loaded using QueryBuilder or repository/manager find methods.
TypeORM Entity with custom properties / virtual fields:
// tested with `typeorm#0.3.7`
#Entity()
export class Post {
#AfterLoad()
updateCounters() {
if (this.likesCount === undefined) this.likesCount = 0
}
}
I don't think the chosen answer is quite correct because an exception for missing columns is thrown when an insert or update operation is attempted:
https://github.com/typeorm/typeorm/blob/cdabaa30287d357c0ae994209e573f97f92dad22/src/metadata/EntityMetadata.ts#L806-L814
If you are interested in reading more about the issue, is sounds like a feature selectAndMap is soon coming to typeorm to support this in a way that is more intuitive https://github.com/typeorm/typeorm/issues/1822#issuecomment-376069476
I have an interface created for my model, where I only want to return specific data from the record
// code.interface.ts
import { Document } from 'mongoose';
export interface CodeI extends Document {
readonly _id: string;
readonly logs: any;
}
But when I get the result from mongo, it completely ignores what is in my interface. (I am using NestJs as framework)
//constructor
constructor(#InjectModel(Coupon.name) private couponModel: Model<CouponDocument>) {}
// function
async findOne(codeId: string): Promise<CodeI> {
const coupon = await this.couponModel.findOne({ _id: codeId }).exec();
if (!coupon) {
throw new NotFoundException([`#${codeId} not found`]);
}
return coupon;
}
TypeScript interfaces don't work this way. They can't limit the fields of an object because they don't exist at runtime, so, we can't use them to guide any runtime behavior. TypeScript interfaces are useful for compile-time type check only.
However, in your case, there are two ways you can achieve the expected behavior.
The first one is to select only the required fields which you need to return (recommended).
In your findOne, you can do something like this
async findOne(codeId: string): Promise<CodeI> {
const coupon = await this.couponModel.findOne({ _id: codeId }, '_id logs').exec();
if (!coupon) {
throw new NotFoundException([`#${codeId} not found`]);
}
return coupon;
}
Here, as you can see, I have passed an additional string type parameter to findOne function which is projection and it will select only the specified fields from the object. This will not only solve your problem but also save query time and have increase query performance. Read more about findOne here.
The other way is to create a DTO where you can define the fields you want to return from the function.
Something like this:
// CouponDto.ts
class CouponDto {
public readonly _id: string;
public readonly logs: any;
constructor(data: CodeI) {
this._id = data._id;
this.logs = data.logs;
}
}
Then, in your service file, you can do something like
return new CouponDto(coupon);
(make sure to change the return type of the function to CouponDto as well)
You can use any of these two ways. While I would recommend going with the first one, it's up to you and how you wanna structure your project.
External Links:
Mongoose FindOne Docs
I am going to implement multi-language on a model, the structure is
const post = {
id: "123",
publishAt: "2020-09-04T00:00:00",
version: 1,
translations: [
{
locale: "en-US",
title: "hello world"
}
]
}
To store inside a relation database such as mysql, I need 2 table posts and post_translations
and thus 2 entities for typeorm
#Entity()
class Post {
#VersionColumn()
version: number;
#Column()
publishAt: Date;
#OneToMany(
(type) => PostTranslation,
(translation) => translation.post,
{ cascade: true }
)
translations: PostTranslation[];
#BeforeUpdate()
beforeUpdate() {
// do something
}
}
#Entity()
class PostTranslation {
#Column()
postId: number;
#Column()
title: string;
#ManyToOne((type) => Post, (post) => post.translations)
post: Post;
}
I am using repository.save(post) for creating/ updating records. If there are some changes inside post entity, such as publishAt, #BeforeUpdate() and #VersionColumn() will be triggered. But if there are no changes, they will not be trigger (reference).
It means that if I only modify the title from PostTranslations, and persist the changes via repository.save(post), it does not see changes and not going to trigger #BeforeUpdate() and #VersionColumn(). But the title (which lives in PostTranslations) is actually part of Post to me.
Is there any ways to ask typeorm to update the entity if some related entities changed during cascade persist?
So apparently, if there's no change in data being passed, the update method won't commit any change. The workaround I've found for this one is to update another field like a updated_at i.e from your code above:
#Entity()
class Post {
#VersionColumn()
version: number;
#Column()
publishAt: Date;
#Column() // new column/field
updatedAt: Date;
// the rest of your post entity
...
Then, whenever you pass the object to be updated, you update this date manually. It could be with a repository or something, such as:
...
post.updatedAt = new Date()
postRepository.save(post)
This will see some difference in old data, and then it would "force" the save. Hope it helps
I am getting the following error with my query:
Class constructor CitizenDTO cannot be invoked without 'new'
I have tried finding information on the .from() syntax and I have not found any information oh what the first parameter should be. I have a plain old javascript type, an arrow function, and a class as shown below.
const lastProcessedAt = '2019-08-01';
await this.getConnection();
const results = await this.connection
.createQueryBuilder()
.select('*', 'citizen')
.from(CitizenDTO, 'citizen')
.where('citizen.cit_last_status_change_date >= :date', { date: lastProcessedAt })
.getMany();
and the dto:
#Entity("citizen")
export class CitizenDTO {
#PrimaryGeneratedColumn()
public cit_id: string;
#Column()
public cit_uid_serial: string;
#Column()
public cit_last_status_change_date: string;
}
I'm not sure but I think my CitizenDTO isn't designed correctly.
I found a few links, such as this one, but they didn't answer my question. The typeorm github shows the from being used this way but they did not explain the structure of their DTO either.
Okay, so I'm starting to dig into graphql a little bit, and I've built an api using koa, type-graphql, and sequelize-typescript. Everything works pretty well.... I managed to get a query working, and even managed to optimize a little bit by using graphql-fields to filter the columns I query in the database... However when I've aliased a field name, I can't seem to get the mapped name.....
For example, given the following ObjectType/Sequelize Model....
export interface IDepartment {
departmentId: number;
name: string;
description: string;
}
#ObjectType()
#Table({ underscored: true })
export class Department extends Model<Department> implements IDepartment {
#Field({ name: 'id' })
#PrimaryKey
#Column({ field: 'department_id'})
public departmentId: number;
#Field()
#Length({ max: 100 })
#Column
name: string;
#Field()
#Length({ max: 100 })
#AllowNull
#Column
description: string;
}
and sample query....
query {
department(name: "Test Dept") {
id
name,
description
}
}
sample resolver...
async department(#Arg('name') name: string, #Info() info: GraphQLResolveInfo) {
return Department.findOne({
where: { name }
});
}
This works just fine.... but when I do
async department(#Arg('name') name: string, #Info() info: GraphQLResolveInfo) {
let fields = Object.keys(getFields(info))
return Department.findOne({
attributes: fields,
where: { name }
});
}
(getFields is graphql-fields), it fails because the query specified field name id, which is what graphql-fields returns, but the column name is department_id (sequelize model name departmentId).
I've gone through the schema with a fine tooth comb, using the introspectionFromSchema function to see a detailed copy of my schema, but nowhere is there a mention of departmentId or department_id.... However I know it's out there somewhere because when I exclude the attributes field from my sequelize query, even though sequelize returns departmentId as the property name, when I return it from my resolver and it reaches the client, the property name is id.
Any help would be appreciated.... I'm trying to optimize everything by only fetching requested properties and not the entire object. I could always store the maps as separate constants and use those in my #Field definition, but I want to do that as a last resort, however if I can I'm trying to keep the code as lean as possible....
Thank you all in advance.
Unfortunately, the name option was introduced mostly to support resolvers inheritance. Using this for mapping the schema field names is a kinda undocumented feature so it's doesn't provide any mapping or exposing mapping metadata.
Using the name option for input or args types will be even worse - it will result in no access to the fields and the properties being undefined.
For now my recommendation is to just keep it simple and don't map the field names until a proper fix arrives.