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
Related
Let's say we have the following User model.
{ id: ID, email: string, username: string }
Then I want to define 2 queries:
Used by the owner to get their settings page so it contains sensitive information such as email (perhaps sin number)
Used by other users to search for a user (by username) & we do NOT want to expose the email or sin number
I have been researching the documentation & cannot find how to accomplish this. I was thinking to grab the info for the fields manually & parse it per query but that seems like an oversight.
UPDATE:
Here is sort of what I am trying to do:
class User {
#Field(
() => ID
)
id: string;
#Authorized("CURRENT_USER")
#Field(
{
nullable: true
}
)
email: string;
#Field()
username: string;
}
Resolver:
export default class UserResolver {
#Authorized("CURRENT_USER")
#Query(
() => User
)
async user(#Arg('username', () => String) username: string) {
// TODO: if username is current user then allow email
// else do not allow email, (I need auth checker in here)
}
}
If I understand your question correctly you should be able to use the Authorized decorator in TypegraphQL. With this solution you should be able to add it to the email field in your User model. This should also be able to work with the sid field as well
Have a look here: https://typegraphql.com/docs/authorization.html
For example your User model could look something like this:
class User {
#Field()
id: ID;
#Authorized("LOGGEDINUSER")
#Field({nullable: true})
email: string;
#Field()
username: string;
}
You will have to allow the email field to be nullable
You will also need to define an authChecker, with this you can run your logic to check if the user is the owner of the data, therefore granting them access to the data.
An authChecker can look something like this:
export const customAuthChecker: AuthChecker<Context> = (
{ args, context, info, root },
roles
) => {
// roles is an array of string which contains the authorization required to access the current resource
// this is specified in the #Authorized decorator
if (roles.includes("LOGGEDINUSER")) {
// here check if the user is actually logged in and if they are allowed to access the resource
// return true if they are allowed to access the resource
}
return false;
};
You will also need to change your call to the buildSchema to include the custom authChecker and authMode.
For example:
const schema = await buildSchema({
resolvers: [UserResolver],
authChecker: customAuthChecker,
authMode: "null",
});
Note this will still return an email field but instead of returning the actual email it will return null when the user does not meet the authentication requirements
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.
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 would like to show only some of the columns in the Entity using NestJS and TypeORM. These are my columns in the Entity:
#Entity({ name: "products" })
export class Product extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
quality: number;
#Column()
color: string;
#Column()
desc: string;
My repository.ts:
#EntityRepository(Product)
export class ProductRepository extends Repository<Product> {}
For example is someone wants to open products/specific it should show only the name and desc.
This is my function in services.ts now:
async get(productid: number): Promise<Product> {
var product: Product= await this.productRepositroy.findOne(productid);
return product;
}
How should I modify it to return only the chosen Columns? I have read about QueryBuilders, but I have no idea how i should implement them.
Your repository has inherited from the method createQueryBuilder by extending Repository. Thus, you can use it with select in order to only get the expected columns:
const product = await this.productRepository
.createQueryBuilder('p')
.where('p.productId = :productId', { productId })
.select(['p.name', 'p.desc']);
.getOne();
Additionally, Nest is able to modify your outgoing objects by using a Serializer: serialization doc. But this is always a good idea to only select the required columns.
NB: I'd also advise not mixing your business logic (here, handling "products") with your database too much (you'd better split the responsibility of Getting a product and Fetching data from the database, for testability/maintainability reasons).
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.