NestJS/TypeORM error: The value passed as UUID is not a string when inserting record - postgresql

I created a NestJS sample application that uses TypeORM to access the Postgres database.
The complete codes can be found from this link.
There are two entities like this.
#Entity({ name: 'posts' })
export class PostEntity {
#PrimaryGeneratedColumn('uuid')
id?: string;
#Column()
title: string;
#Column({ nullable: true })
content?: string;
#OneToMany((type) => CommentEntity, (comment) => comment.post, {
cascade: true,
})
comments?: Promise<CommentEntity[]>;
#ManyToOne((type) => UserEntity, { nullable: true })
#JoinColumn({ name: 'author_id' })
author?: UserEntity;
#RelationId((post: PostEntity) => post.author)
authorId?: string;
#CreateDateColumn({ name: 'created_at', type: 'timestamp', nullable: true })
createdAt?: Date;
#UpdateDateColumn({ name: 'updated_at', type: 'timestamp', nullable: true })
updatedAt?: Date;
}
#Entity({ name: 'comments' })
export class CommentEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
content: string;
#ManyToOne((type) => PostEntity, (p) => p.comments)
#JoinColumn({ name: 'post_id' })
post: PostEntity;
#RelationId((comment: CommentEntity) => comment.post)
postId?: string;
#CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;
}
When adding a comment through the GraphQL endpoint, which will call the following codes.
addComment(id: string, comment: string): Observable<Comment> {
const entity = new CommentEntity();
Object.assign(entity, {
content: comment,
postId: id,
});
return from(this.commentRepository.save(entity)).pipe(
map((c) => {
return { id: c.id, content: c.content } as Comment;
}),
);
}
When running the e2e tests it will fail due to an error message(I added a console.log to print the GraphQL errors in the response body):
addComment errors: [{"message":"The value passed as UUID is not a string"}]

Related

Cannot read properties of undefined reading 'getEntityValue' typeorm

I am creating a nestjs project with typeorm and postgresql. Here I am creating a user entity.
My entity will be like
{
email: string,
name: string,
avatar: {
public_key: string;
url: string;
}
}
In avatar column I need to add a object with public_key and url.
I create two entity like this-
#Entity()
class Avatar {
#Column({ type: "text", nullable: true })
public_key: string;
#Column({ type: "text", nullable: true })
url: string;
}
#Entity()
#Tree("closure-table")
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: "text", nullable: false })
userName: string;
#Column({ type: "text", nullable: false })
email: string;
#Column({ type: "text", nullable: false })
firstName: string;
#Column({ type: "text", nullable: false })
lastName: string;
#Column({ type: "text", nullable: false, select: false })
password: string;
#Column({ type: "text", nullable: true })
socket_id: string;
#Column({ type: "text", nullable: true, select: false })
otp: string;
#TreeChildren()
avatar: Avatar;
#Column({ type: "boolean", default: false, nullable: false })
is_verify: boolean;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
//Before Insert Injection
#BeforeInsert()
createUserName() {
this.userName = uniqid.time()
}
}
Here I use closure-table to do nested avatar entity
Here I write
#TreeChildren()
avatar: Avatar;
For nesting Avatar Entity
Then in my controller I try to create my first data-
const result = await this.userRepository.create({
...userInput,
otp: secret.base32,
password: passwordHash,
});
await this.userRepository.save(result)
Note: avatar field is not required. Avatar field will be add when user update their profile. Here I only create first user data.
But I am getting this error-
Cannot read properties of undefined (reading 'getEntityValue')
Additionally, here is my userInput
#InputType()
export class UserInput {
#Field(() => String, { nullable: false })
#IsString()
#IsEmail()
#IsNotEmpty()
email: string;
#Field(() => String, { nullable: false })
#IsString()
#IsNotEmpty()
firstName: string;
#Field(() => String, { nullable: false })
#IsString()
#IsNotEmpty()
lastName: string;
#Field(() => String, { nullable: false })
#IsString()
#MinLength(8)
#MaxLength(20)
#IsNotEmpty()
#Matches(/^.*(?=.{4,10})(?=.*\d)(?=.*[a-zA-Z]).*$/, { message: "password too weak" })
password: string
}
Please help me. I need this is very much. Over 10 days, I can't solve this. Please help me.

How to Implement Typeorm Entity Array of Object

I want to achieve this output:
{
.....,
dependants: [{name: 'john', age: 29},{name: 'doe', age: 17}]
}
I have an entity like this:
class PartnerStaff extends BaseEntity {
constructor(
id: string,
company: string,
branch: string,
dependants: DependantDto[],
) {
super();
this.staffId = id;
this.company = company;
this.branch = branch;
this.dependants = dependants;
}
#PrimaryGeneratedColumn('increment')
id!: number;
#Column({
unique: true,
nullable: true,
})
staffId!: string;
#Column({
nullable: true,
name: 'company',
})
company!: string;
#Column()
branch!: string;
#Column('json', {nullable: true})
dependants?: DependantDto[];
}
And my dependants dto:
class DependantDto {
#IsString()
#IsNotEmpty({ message: 'dependant name is required' })
readonly name!: string;
#IsString()
#IsNotEmpty({ message: 'dependant age is required' })
readonly age!: number;
}
I am getting dependants: ['string'] on swagger.
I have tried these but still not working...
#Column('jsonb', {nullable: true})
#Column({type: 'array', nullable: true})
I was able to solve it this way....
#Column({type: 'json'})
dependants: DependantsDto[];
Then in my PartnerStaffDto, I did ...
#IsOptional()
#ApiModelProperty({
isArray: true
})
dependants: DependantsDto[];
So now I get what i was expecting ealier dependants: [{name: '', age: 0}]

Get entities within 100km with postgresql and typeorm ordered

I get this error
Cannot read property 'databaseName' of undefined
https://github.com/typeorm/typeorm/issues/4270
I follow github issue but it not help, the query works correctly If I delete addOrderBy method
but I need the results ordered by the distance so there is a way to make this work ?
Entity
#Entity('restaurant')
export class RestaurantEntity extends AbstractEntity {
#Column()
name: string;
#Column()
description: string;
#OneToMany(() => MenuEntity, (menu) => menu.restaurant, {
onDelete: 'CASCADE',
cascade: true,
})
menus: MenuEntity[];
#Column()
type: string;
#Column()
location: string;
#Column('geometry', {
name: 'geo',
nullable: true,
spatialFeatureType: 'Point',
})
geoLocation: object;
#Column({
nullable: true,
})
likes: number;
#OneToMany(
() => RestaurantImageEntity,
(restaurantImage) => restaurantImage.restaurant,
{
onDelete: 'CASCADE',
cascade: true,
},
)
restaurantImages: RestaurantImageEntity[];
#Column({
nullable: true,
})
views: number;
#Column({
nullable: true,
})
totalFavorites: number;
#Column({
nullable: true,
})
telephone: string;
#Column({
nullable: true,
})
web: string;
#Column({
nullable: true,
})
email: string;
#ManyToOne(() => UserEntity, (user) => user.restaurants, {
onDelete: 'CASCADE',
})
creator: UserEntity;
}
Query
const query.geoLocation = [x,y];
restaurants = await this.restaurantRepository
.createQueryBuilder('restaurant')
.leftJoinAndSelect('restaurant.restaurantImages', 'restaurantImages')
.where(ST_DWithin(restaurant.geoLocation, ST_MakePoint(${query.geoLocation[0]},${query.geoLocation[1]})::geography, 100000))
.orderBy(sort ? sort : { 'restaurant.id': 'DESC' })
.addOrderBy(`restaurant.geoLocation <-> ST_MakePoint(${query.geoLocation[0]},${query.geoLocation[1]})::geography`)
.skip((page - 1) * perPage)
.take(perPage)
.getMany();

NestJS Insert a Comment into a user blog post

I have an app where an user can create a list of Recipes and each Recipe can have multiple comments that many users can post.
This is what im trying to do:
I have a comments Enitity:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeUpdate,
ManyToOne,
JoinColumn,
ManyToMany,
} from 'typeorm';
import { UserEntity } from 'src/user/models/user.entity';
import { RecipeEntity } from 'src/recipe/model/recipe-entry.entity';
import { User } from 'src/user/models/user.interface';
#Entity('comments_entry')
export class CommentsEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
#BeforeUpdate()
updateTimestamp() {
this.updatedAt = new Date();
}
#ManyToOne(
type => UserEntity,
user => user.username,
)
author: UserEntity;
#Column()
recipe_id: number;
#Column()
author_id: number;
#ManyToOne(
type => RecipeEntity,
recipe => recipe.comment,
)
#JoinColumn({ name: 'recipe_id', referencedColumnName: 'id' })
comment: RecipeEntity;
}
Linked to a Recipe entity:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeUpdate,
ManyToOne,
JoinColumn,
OneToMany,
JoinTable,
ManyToMany,
} from 'typeorm';
import { UserEntity } from 'src/user/models/user.entity';
import { CommentsEntity } from 'src/comments/model/comments.entity';
#Entity('recipe_entry')
export class RecipeEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column()
slug: string;
#Column('text', { array: true, nullable: true })
ingr: string[];
#Column({ default: '' })
description: string;
#Column({ default: '' })
body: string;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
#BeforeUpdate()
updateTimestamp() {
this.updatedAt = new Date();
}
#Column({ nullable: true })
headerImage: string;
#Column({ nullable: true })
publishedDate: Date;
#Column({ nullable: true })
isPublished: boolean;
#Column()
user_id: number;
#ManyToOne(
type => UserEntity,
user => user.recipeEntries,
)
#JoinColumn({ name: 'user_id', referencedColumnName: 'id' })
author: UserEntity;
#Column({ default: 0 })
totalWeight: number;
#Column('text', { array: true, default: '{}' })
dietLabels: string[];
#Column({ default: 0 })
calorieQuantity: number;
#Column({ default: 0 })
proteinQuantity: number;
#Column({ default: 0 })
carbQuantity: number;
#Column({ default: 0 })
fatQuantity: number;
#Column({ default: 0 })
sugarQuantity: number;
#Column('text', { array: true, nullable: true })
likes: string[];
#Column({ default: false, nullable: true })
isLiked: boolean;
#OneToMany(
type => CommentsEntity,
comments => comments.comment,
)
comment: CommentsEntity[];
}
Linked to an User entity:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BeforeInsert,
OneToMany,
} from 'typeorm';
import { UserRole } from './user.interface';
import { RecipeEntity } from 'src/recipe/model/recipe-entry.entity';
import { CommentsEntity } from 'src/comments/model/comments.entity';
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column({ unique: true })
username: string;
#Column({ unique: true })
email: string;
#Column({ select: false })
password: string;
#Column({ type: 'enum', enum: UserRole, default: UserRole.USER })
role: UserRole;
#Column({ nullable: true })
profileImage: string;
#Column({ default: false, nullable: true })
favourite: boolean;
#OneToMany(
type => RecipeEntity,
recipeEntity => recipeEntity.author,
)
recipeEntries: RecipeEntity[];
#OneToMany(
type => CommentsEntity,
recipeEntryEntity => recipeEntryEntity.author,
)
commentEntries: CommentsEntity[];
#BeforeInsert()
emailToLowerCase() {
this.email = this.email.toLowerCase();
}
}
As an user i can post recipes. But im failing to add comments on specific recipes.
2 errors:
When i create a Recipe with some hardcoded comments, the users and recipe table gets filled but the comments_entry table is empty.
And im failing to implement the method to add comments to a specific recipe.
Controller:
#UseGuards(JwtAuthGuard)
#Post('recipe/:id')
createComment(
#Param() params,
#Body() comment: string,
#Request() req,
): Observable<RecipeEntry> {
const user = req.user;
const id = params.id;
return this.recipeService.createComment(user, id, comment);
}
createComment(id: number, commentEntry: string): Observable<RecipeEntry> {
return from(this.findOne(id)).pipe(
switchMap((recipe: RecipeEntry) => {
const newComment = recipe.comment.push(commentEntry);
return this.recipeRepository.save(newComment);
}),
);
}
Type 'Observable<DeepPartial[]>' is not assignable to type 'Observable'.
Property 'comment' is missing in type 'DeepPartial[]' but required in type 'RecipeEntry'.ts(2322)
recipe-entry.interface.ts(18, 3): 'comment' is declared here.
Any help?
Can't build working example but may be would helpful:
Redo your relations (I simplified entities for example, but you should use full:) ):
#Entity('comments_entry')
export class CommentsEntity {
#Column()
authorId: number;
#ManyToOne(type => UserEntity, user => user.id)
author: UserEntity;
#ManyToOne(type => RecipeEntity, recipe => recipe.id)
recipe: RecipeEntity;
}
#Entity('recipe_entry')
export class RecipeEntity {
#Column()
authorId: number;
#ManyToOne(type => UserEntity, user => user.id)
author: UserEntity;
#OneToMany(type => CommentsEntity, comment => comment.recipe)
comments: CommentsEntity[];
}
#Entity('user_entry')
export class UserEntity {
#OneToMany( type => RecipeEntity, recipe => recipe.author)
recipes: RecipeEntity[];
#OneToMany(type => CommentsEntity, comment => comment.author)
comments: CommentsEntity[];
}
RecipeDto something like:
RecipeDto: {
authorId: number | string,
comments: CommentDto[],
****other recipe data
}
create new Recipe:
createRecipe(recipeDto: RecipeDto): Observable<RecipeEntry> {
const { comments, authorId } = recipeDto;
if(comments) {
const commentPromises = comments.map(async comment => {
comment.authorId = authorId;
return await this.commentRepository.save(comment);
});
recipeDto.comments = await Promise.all(commentPromises);
}
return await this.recipeRepository.save(recipeDto);
}
If I understood correctly, you are trying that:
One User --> Many Recipes
One User --> Many Comments
One Recipe --> Many Comments
Your entities seems right.
Normally a typeorm repository returns a promise and not an observable.
You need to convert it to an Observable.
And at the moment you are trying to store a comment in the recipeRepo. You should save the whole recipe. And before you have to save the comment in the comment repo (if you are not working with cascades).
Something like this:
createComment(id: number, commentEntry: string): Observable<RecipeEntry> {
return from(this.findOne(id)).pipe(
switchMap((recipe: RecipeEntry) => {
return from(this.commentRepository.save(newComment)).pipe(
map(com: Comment) => {
recipe.comment.push(com);
return from(this.recipeRepository.save(recipe));
}
)
);
}
If you enable cascades, you can do this in only one call.

how to limit typeorm join queries?

I'm new to typeorm, maybe someone can resolve my problem.
I'm using NestJS and TypeORM and have two tables (categories and talents). I wish to find a solution to limit typeorm join queries.
each category can have many talents in talent_worked and each talent can have many categories in working_categories.
i like to find all categories and there respected talent but i wish to get(limit) only five talents.
Talent:
#Entity('talents')
#Unique(['mobile'])
export class TalentsEntity extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ nullable: true })
name: string;
#Column({ unique: true })
mobile: string;
#Column({ nullable: true })
email: string;
#Column({ select: false })
password: string;
#Column({ select: false })
salt: string;
#Column({ default: false })
isBlocked: boolean;
#Column({ default: true })
isActive: boolean;
// relation to categories model
#ManyToMany(
type => CategoriesEntity,
categoriesEntity => categoriesEntity.talent_worked,
{ eager: true },
)
#JoinTable({ name: 'talents_working_categories' })
working_categories: CategoriesEntity[];
}
Category:
#Entity('categories')
#Unique(['title'])
export class CategoriesEntity extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ nullable: true })
title: string;
// relation to talents
#ManyToMany(
type => TalentsEntity,
talentsEntity => talentsEntity.working_categories,
{ eager: false },
)
talent_worked: TalentsEntity[];
}
here is my typeorm query so far:
const query = await this.createQueryBuilder('category');
query.leftJoinAndSelect('category.talent_worked', 'talent');
query.leftJoinAndSelect('talent.working_categories', 'talentCategories');
query.where('talent.isActive = :isActive AND talent.isBlocked = :isBlocked', { isActive: true, isBlocked: false});
if (categoryId) query.andWhere('category.id = :categoryId', { categoryId });
query.select([
'category.id',
'category.title',
'talent.id',
'talent.name',
'talentCategories.id',
'talentCategories.title',
]);
query.orderBy('category.created_at', 'ASC');
query.addOrderBy('talent.created_at', 'ASC');
return await query.getMany();