Typeorm - Transform Response - nestjs

I'm using Typeorm with NestJS, is there a way to pass in a dynamic value into the Column transformer?
I've got this post entity file:
export class Post extends EntityBase {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => PostType, (postType) => postType.id)
postType: PostType;
#Column()
name: string;
#Column()
dateStart: Date;
#Column()
dateEnd: Date;
#Column({ select: false })
status: number;
#Column({
transformer: {
to(n) {
return n;
},
from(n) {
return configService.getCDNUrl() + n;
},
},
})
imageList: string;
#Column()
slug: string;
}
I'm waiting to prefix the images with the full CDN URL, currently, this appends the CDN URL correctly as it's a static value so i pull the value from the configservice so i do get https://mycdn.com/image.jpg
However my images are stored in folders for each client so what i actually need it https://mycdn.com/{{clientId}}/image.jpg - Each request to the API does contain the clientId, is there a way to pass this information into the entity or repository to transform the response (or we can pick the clientId from postType.clientId) or will have to manually transform it in the service?

For anyone looking at this in the future, I found that I can use the #AfterLoad() decorator to update the value, can even access the related entities
#AfterLoad()
setCdnUrl() {
this.imageList = `${configService.getCDNUrl()}${
this.postType.clientId
}/content/${this.imageList}`;
}

Related

Show additional data in the response from another table that is connected via a many-to-many relation by TypeORM in NestJS

I have four tables users, ingredients, recipes and a connection table ingredients_ingrec_recipes that TypeORM created automatically. This connection table consists of two columns: ingredientsId and recipesId.
In the ingredient.entity.ts file I have defined #ManyToMany relation with { eager:true } between ingredients and recipes. When I make a Get-Request for a specific ingredient (via Postman), I also see the associated recipes in the response automatically, thanks to the magic of { eager: true } and TypeORM+NestJS.
Now I need exactly the same, but for recipes (other side). In the recipe response, the corresponding ingredients should also be displayed. Unfortunately, according to TypeORM, I can only use #ManyToMany with { eager: true } in one of the two entities. In order to achieve the same with recipes, I have to do this somehow via leftJoinAndSelect(). However, I don't know how to do this via this connection table ingredients_ingrec_recipes in my code.
Here is my code:
ingredient.entitiy.ts
#Entity({ name: 'ingredients' })
export class IngredientEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
slug: string;
#Column()
name: string;
#Column({ default: '' })
description: string;
// more fields
#ManyToMany(() => RecipeEntity, { eager: true })
#JoinTable()
ingrec: RecipeEntity[];
#ManyToOne(() => UserEntity, (user) => user.recipes, { eager: true })
author: UserEntity;
}
recipe.entity.ts
#Entity({ name: 'recipes' })
export class RecipeEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
slug: string;
#Column()
title: string;
#Column({ default: '' })
description: string;
#Column({ default: '' })
body: string;
// more fields
#ManyToOne(() => UserEntity, (user) => user.recipes, { eager: true })
author: UserEntity;
}
recipe.service.ts
async findAll(query: any) {
const queryBuilder = getRepository(RecipeEntity)
.createQueryBuilder('recipes')
.leftJoinAndSelect('recipes.author', 'author');
queryBuilder.orderBy('recipes.createdAt', 'DESC');
if (query.author) {
const author = await this.userRepository.findOne({
username: query.author,
});
queryBuilder.andWhere('recipes.authorId = :id', {
id: author.id,
});
}
const recipes = await queryBuilder.getMany();
return { recipes };
}
I have already used leftJoinAndSelect() with the author, but in a #OneToMany relationship. How can I adjust my findAll() service function so that I can still see in my recipe-response the data from my user (author) table and also the associated data from the ingredients?
ingredientsId
recipesId
3
1
1
2
3
3
3
4
If I add following to the RecipeEntity:
#ManyToMany(() => IngredientEntity, { eager: true })
ingrec: IngredientEntity[];
I get an server error and [Nest] 230 - 05/17/2022, 3:02:11 PM ERROR [ExceptionsHandler] Maximum call stack size exceeded
Without { eager: true } I don't get any errors but I also don't see any data from the ingredients table.

How can i post a DTO that contain an array of entities?

I have a couple of questions about NestJS and TypeOrm.
First, how to pass an array of strings to DTO? I tried just to use :string[] type, but the compiler gives an error.
This is my Post entity:
#Entity('posts')
export class Post {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, user => user.posts, { cascade: true })
author: number;
#Column({ type: 'timestamp' })
date: Date;
#Column()
text: string;
#Column({ default: 0 })
likes: number;
#OneToMany(() => Photo, photo => photo.post, { cascade: true })
photos: Photo[];
}
And CreatePostDto:
export class CreatePostDto {
authorId: number;
date: Date;
text?: string;
// photos?: string[];
}
And the second question: How can i save to the repository every photo (keeping the connection with post), posts to the posts repo and update user by adding new post binded to him.
I tried something like this, but it won't work obviously.
async create(createPostDto: CreatePostDto) {
const post = this.postsRepository.create(createPostDto);
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text: createPostDto.text});
}
What you missed here is saving photos before bind them with the post, here's an example:
async create(createPostDto: CreatePostDto) {
let photos:Array<Photo> = [] ; array of type photo entity
for(let urlPhoto of createPostDto.photos)
{
let photo = await this.imageRepository.save({url : urlPhoto }); you must save the photos first
photos.push(photo);
}
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text:
createPostDto.text,photos:photos});
}
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number
#Column("simple-array")
names: string[]
}

Nestjs/TypeORM - How to implement custom search by column

I am playing around with NestJs using TypeORM along with MySQL.
I have went via documentation, and I have made basic CRUD app running locally.
I have built in searches (via Repository) by id, but I would need to implement search by custom column as well.
For example I have this entity:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
username: string;
#Column()
first_name: string;
#Column()
last_Name: string;
#Column()
gender: string;
And in my repository, I have these built in methods:
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: string): Promise<User> {
return this.usersRepository.findOne(id);
}
And it works just fine, as expected. I would need another custom search, so I can search also by username, how can I achieve that?
I would need something like this:
findByUsername(username: string): Promise<User> {
return this.usersRepository.findByUsername(username);
}
I assume I have to implement custom query, but I have no clue where to do it :(
Here is the simpler solution:
findByUsername(username: string): Promise<User | undefined> {
return this.usersRepository.findOne({ username });
}
You can use this code
findByName(user_name: string): Promise<User> {
return this.usersRepository.findOne({ user_name });
}
I managed to achieve it, by using queryBuilder
So this is how my function looks now:
findByUsername(username: string): Promise<User | undefined> {
const user = getRepository(User)
.createQueryBuilder("user")
.where("user.username = :username", { username: username })
.getOne();
return user;
}
const firstUser = await connection
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne();

TypeORM eager loading invalid column name with select

When implementing User entity and Roles entity in TypeORM, I used #ManyToMany with eager on true.
I implemented a UserRepository that extends Repository.
When using this.find() it works, without a problem (but also loads the password and other fields an API doesn't need to serve). When using this.find({select: 'email firstname roles'}), it suddenly gives me this error:
RequestError: Invalid column name 'userId'.
I also tried adding option relations, but that gives me error
QueryFailedError: Error: Invalid column name 'userId'.
Can anyone help me with this?
Node version: 12.16.2
TypeORM version: 0.2.24
Typescript version: 3.7.4
Database: SQL Server
Role entity:
#Entity()
export class Role {
#ManyToMany(type => User, user => user.roles)
#PrimaryColumn()
role!: string
}
User Entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
id!: number;
#Column()
public email!: string;
#Column()
public password!: string;
#Column()
public firstname!: string;
#ManyToMany(type => Role, role => role.role, {eager: true, nullable: true})
#JoinTable()
public roles!: Role[];
}
User Repository:
#EntityRepository(User)
export class UserRepository extends Repository<User> {
whitelist: IWhitelist<User> = {
admin: ['email', 'firstname','roles', 'id',]
};
getOptions = (list: string) => {
return {select: this.whitelist[list], relations: ['roles']};
};
adminGetUsers = async (): Promise<Array<User> | undefined> => {
return await this.find(this.getOptions('admin'));
};
}
Have you tried
this.find({select: ['email', 'firstname', 'roles']}
from the documentation :
https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options

How to filter one-to-many relations with query builder

I have
#Entity('procedures')
export class ProcedureEntity {
#PrimaryGeneratedColumn()
id: number;
#OneToMany(type => MediaFileEntity, media => media.procedure, {
cascade: true
})
files: MediaFileEntity[];
};
and
#Entity('media_files')
export class MediaFileEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true })
deletedAt: Date;
#ManyToOne(type => ProcedureEntity, procedure => procedure.files)
procedure: ProcedureEntity;
};
Does anybody know how do I create a query builder so that I get all the procedures, but with media files that have deletedAt NULL ?
I'm trying to do
const procedures = await this.procedureRepository
.createQueryBuilder("procedure")
.leftJoinAndSelect("procedure.doctor", "doctor")
.leftJoinAndSelect("procedure.patient", "patient")
.innerJoin('procedure.files', 'mediaFile', 'mediaFile.deletedAt IS NULL')
.where("(patient.id = :id OR doctor.id = :doctorId)", { id: user.id, doctorId: user.id })
.andWhere('procedure.deletedAt IS NULL')
.getMany();
but I get Cannot find name 'where'. Which is odd, because this.procedureRepository
.createQueryBuilder("procedure") is also a SelectQueryBuilder<ProcedureEntity> and so is this.procedureRepository
.createQueryBuilder("procedure")
.leftJoinAndSelect("procedure.doctor", "doctor")
.leftJoinAndSelect("procedure.patient", "patient")
.innerJoin('procedure.files', 'mediaFile', 'mediaFile.deletedAt IS NULL');
Thanks in advance!
EDIT: Funny enough, I already had the answer. But I'm gonna leave it here anyway, for posterity 😄
You have a syntax error. You have semicolon after innerJoin. Is this the cause of the error?

Resources