(Typeorm/Postgres) Saving Tree Entity (Category) from relation Entity (Product) cause error - postgresql

Creating Product with category name, return MaterializedPathSubjectExecutor Error.
Creation of Categories works. But trying to save product with relation category throws error.
Tried adding to #OneToMany()
{
persistence:false, cascade:false, eager:true
}
Also Tried #JoinColumn() on both side of relation
Category Retreival Function
async getCategories(category: string) {
const manager = getManager();
const cat = await manager
.getRepository(Category)
.findOne({
name: Like(category),
})
.catch((error) => {
console.error(error);
});
console.log(cat);
if (!cat) {
const cat = new Category();
cat.name = category;
cat.description = category;
return await manager.getRepository(Category).save(cat);
}
return cat;
}
Save Function
async create(createdBy: string, productDto: CreateProductDto) {
const { name, price, isActive, description, category, images } = productDto;
const product = new Product();
product.name = name;
product.price = price;
product.isActive = !!isActive;
const creator = await this.usersService.findForId(createdBy);
product.createdBy = creator;
product.updatedBy = creator;
product.description = description;
product.images = await this.imageService.upload(images);
if (category) {
console.log('WHAT');
product.category = await this.getCategories(category);
}
return await this.productsRepository.save(product).catch((err) => {
console.error(err);
console.log(product);
});
Product.entity.ts
import { Image } from 'src/modules/image/entity/image.entity';
import { User } from 'src/modules/user/user.entity';
import { BaseEntity } from 'src/utility/entity/base.entity';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
Tree,
} from 'typeorm';
#Entity()
#Tree('materialized-path')
export class Product extends BaseEntity {
#Column()
name: string;
#Column()
price: number;
#Column({ type: 'varchar', length: 300 })
description: string;
#OneToMany(() => Image, (image) => image.product)
images: Image[];
#ManyToOne((type) => Category, (category) => category.products)
category: Category;
#ManyToOne((type) => User, (user) => user.products_created, { lazy: true })
#JoinColumn()
createdBy: User;
#ManyToOne((type) => User, (user) => user.products_created, { lazy: true })
#JoinColumn()
updatedBy: User;
}
Category.entity.ts
#Entity({ name: 'category' })
#Tree('materialized-path')
export class Category extends BaseEntity {
#Column({ type: 'varchar', length: 300 })
/**
* Name of category
* #example 'book'
* */
name: string;
#Column({ type: 'varchar', length: 300 })
description: string;
#TreeChildren()
children: Category[];
#TreeParent()
parent: Category;
#OneToMany((type) => Product, (product) => product.category)
products: Product[];
#ManyToOne((type) => User, (user) => user.category_created, { lazy: true })
#JoinColumn()
createdBy: User;
#ManyToOne((type) => User, (user) => user.category_updated, { lazy: true })
#JoinColumn()
updatedBy: User;
}
Error: Generated
TypeError: Cannot read property 'getEntityValue' of undefined
at MaterializedPathSubjectExecutor.<anonymous>

Related

TypeORM OneToMany and ManyToOne relations cases 500 error on GET request

After adding OneToMany and manyToOne relations in the entities and doing GET request it shows in postman 500 error.
// Car entity
#Entity({ name: 'car' })
export class Car {
#PrimaryColumn()
car_id: number;
#Column()
name: string;
#Column()
enabled: boolean;
#ManyToOne(() => Person, (person) => person.cars)
person: Person;
}
// Person entity
#Entity({ name: 'person' })
export class Person {
#PrimaryColumn()
person_id: number;
#Column()
name: string;
#Column()
enabled: boolean;
#OneToMany(() => Car, (car) => car.person)
cars: Car[];
}
// Postman
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/v2/cars",
"method": "GET",
}
UPDATE
In try catch it shows this error
ERROR QueryFailedError: column Car.personPersonId does not exist
//Service
GET request to fetch cars by person id
async getByPersonId(
personId: string | number,
relations: string[] = [],
throwsException = false,
): Promise<Car[] | []> {
return await this._carRepository.getByPersonId(
personId,
relations,
throwsException,
);
}
// Repository
async getByPersonId(
personId: string | number,
relations: string[] = [],
throwsException = false,
enabled?: boolean,
): Promise<Car[]> {
const where: IPersonById = {
person_id: personId,
};
if (!isNil(enabled)) {
where.enabled = enabled;
}
return await this.find({
where: {
...where,
},
relations,
})
.then((entities) => {
if (!entities && throwsException) {
return Promise.reject(
new NotFoundException(
'Car is not found by provided person id.',
),
);
}
return Promise.resolve(entities ? this.transformMany(entities) : null);
})
.catch((error) => {
console.log('ERROR', error);
return Promise.reject(error);
});
}
Try this:
on the Car entity:
#ManyToOne(type => Person, person => person.cars, {eager: false})
person: Person
on the Person Entity
#OneToMany(type => Car, car => car.person, {eager: true})
cars: Car[];

Error: Cannot query across many-to-many for property on updating

I am try to update many to may relation.
export class CreateProductDto {
#ApiProperty()
#IsString()
description: string;
#ApiProperty()
#IsString()
name: string;
#ApiProperty({ isArray: true })
#IsNumber({}, { each: true })
#IsArray()
categoryIds: number[];
}
export class UpdateProductDto extends PartialType(CreateProductDto) {}
export class ProductsService {
constructor(
#InjectRepository(Product)
private productRepository: Repository<Product>,
private categoriesService: CategoriesService,
) {}
async update(id: number, updateProductDto: UpdateProductDto) {
let categories: Category[] = undefined;
if (updateProductDto.categoryIds) {
categories = await Promise.all(
updateProductDto.categoryIds.map(
async (id) => await this.categoriesService.findOneOrFail(id),
),
);
delete updateProductDto.categoryIds;
}
await this.productRepository.update(
{ id },
{ ...updateProductDto, categories },
);
return await this.findOneOrFail(id);
}
async findOneOrFail(id: number) {
const product = await this.productRepository.findOne({ id });
if (product) {
return product;
}
throw new BadRequestException(`Product is not present`);
}
}
#Entity()
export class Product extends BaseEntity {
#Column()
description: string;
#Column()
name: string;
#ManyToMany(() => Category, (object) => object.products, {
cascade: true,
eager: true,
})
#JoinTable()
categories: Category[];
}
#Entity()
export class Category extends BaseEntity {
#Column()
name: string;
#ManyToMany(() => Product, (object) => object.categories)
products: Product[];
}
Finally when i try to call ProductsService.update with this payload it
"categoryIds": [ 2 ]
i got an error like this
Error: Cannot query across many-to-many for property categories
Can some please help me to update many to many
In your Category Entity add the relation id of Product and use save method instead of update when you update your entity.
#Entity()
export class Category extends BaseEntity {
#Column()
name: string;
#ManyToMany(() => Product, (object) => object.categories)
products: Product[];
// Add this
#Column()
productId: string;
}
To solve problemes like i use.
async dummyUpdate(objetUpdateDto: ObjectUpdateDto): Promise<TypeReturn> {
const { idObjectToUpdate, colum1ToUpate, colum2ToUpate } = objetUpdateDto;
try {
const objectToUpdate = await this.repositoryOfEntity.findOne(idObjectToUpdate);
idObjectToUpdate.colum1ToUpate = colum1ToUpate;
idObjectToUpdate.colum2ToUpate = colum2ToUpate;
await this.repositoryOfEntity.save(objectToUpdate);
return objectToUpdate,
} catch(err) {
throw new ConflictException("your message" + err);
}
}

TypeORM PostgreSQL #ManyToOne save violates not-null constraint

I have a basic nestjs app with 3 entities :
document: DocumentEntity has pages: PageEntity[] as #OneToMany relation
page: PageEntity has words: WordEntity[]as #OneToMany relation + #ManyToOne document
word: WordEntity has a #ManyToOne page: PageEntity
This is quite straightforward and the first part of those relations works as expected :
I can save new pages doing so :
async createPage(documentId: number, url: string) {
const document = await this.documentRepository.findOne({ id: documentId });
if (document) {
const pageEntity = new PageEntity();
pageEntity.imgUrl = url;
pageEntity.document = document;
await this.pageRepository.save(pageEntity);
}
}
but when I try to apply the same logic to the words/page relation, it fails. I m not sure why this behaves differently
async postWord(pageId: number, word: { text: string }) {
const page = await this.pageRepository.findOne({ id: pageId });
if (page) {
const wordEntity = new WordEntity();
wordEntity.text = word.text;
wordEntity.page = page;
await this.wordRepository.save(wordEntity);
}
}
Error Message :
[ExceptionsHandler] null value in column "pageId" of relation "word_entity" violates not-null constraint +107723ms
QueryFailedError: null value in column "pageId" of relation "word_entity" violates not-null constraint
here are the entities declarations :
// document.entity.ts
#Entity()
class DocumentEntity {
#PrimaryGeneratedColumn()
public id?: number;
#Column()
public name: string;
#Column()
public locale: string;
#Column()
public pdfUrl: string;
#Column()
public folderPath: string;
#OneToMany(() => PageEntity, (page) => page.document, {
primary: true,
eager: true,
cascade: true,
})
public pages?: PageEntity[];
}
export default DocumentEntity;
// page.entity.ts
#Entity()
class PageEntity {
#PrimaryGeneratedColumn()
public id?: number;
#Column({ nullable: true })
public pageNumber?: number;
#Column({ nullable: true })
public text?: string;
#Column()
public imgUrl: string;
#OneToMany(() => WordEntity, (word) => word.page, {
eager: true,
onDelete: 'CASCADE',
primary: true,
})
words?: WordEntity[];
#ManyToOne(() => DocumentEntity, {
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn()
public document: DocumentEntity;
}
export default PageEntity;
// word.entity.ts
#Entity()
class WordEntity {
#PrimaryGeneratedColumn()
public id?: number;
#ManyToOne(() => PageEntity, {
nullable: true,
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn()
public page!: PageEntity;
#Column()
public text: string;
#Column({ type: 'decimal', nullable: true })
public confidence?: number;
}
Try this:
#Entity()
class WordEntity {
.....
#ManyToOne(() => PageEntity, {
nullable: true,
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn({
name: 'pageId',
referencedColumnName: 'id',
})
page?: PageEntity;
#Column({ nullable: true })
pageId?: number
.....
}
async postWord(pageId: number, word: { text: string }) {
const wordEntity = new WordEntity();
wordEntity.text = word.text;
// check if the pageId exists, maybe inside Dto with a decorator
wordEntity.pageId = pageId;
await this.wordRepository.save(wordEntity);
}
You need to remove the primary: true from your #ManyToOne relation because a primary relation cannot be set as NULL

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.

Nestjs - QueryFailedError: invalid input syntax for type uuid

I have this method that is supposed to return all Posts from all users
Basically trying to so Select * from posts
/**
* Find all posts
*/
async findAll(): Promise<Post[]> {
try {
return await await getConnection()
.createQueryBuilder()
.select("posts")
.from(Post, "posts").getMany();
} catch (error) {
throw new Error(error)
}
}
When this method is called, this is the response from TypeORM
QueryFailedError: invalid input syntax for type uuid: "findAll"
Query output
SELECT DISTINCT
"distinctAlias"."Post_post_id" as "ids_Post_post_id"
FROM
(
SELECT
"Post"."post_id" AS "Post_post_id",
"Post"."uid" AS "Post_uid",
"Post"."title" AS "Post_title",
"Post"."sub_title" AS "Post_sub_title",
"Post"."content" AS "Post_content",
"Post"."userUserId" AS "Post_userUserId",
"Post__comments"."comment_id" AS "Post__comments_comment_id",
"Post__comments"."content" AS "Post__comments_content",
"Post__comments"."postPostId" AS "Post__comments_postPostId",
"Post__comments"."userUserId" AS "Post__comments_userUserId"
FROM
"posts" "Post"
LEFT JOIN
"comments" "Post__comments"
ON "Post__comments"."postPostId" = "Post"."post_id"
WHERE
"Post"."post_id" = $1
)
"distinctAlias"
ORDER BY
"Post_post_id" ASC LIMIT 1
Here is my schema
Posts
/**
* Post Entity
*/
#Entity('posts')
export class Post {
#PrimaryGeneratedColumn('uuid') post_id: string;
#Column({ type: 'varchar', nullable: false, unique: true }) uid: string;
#Column('text') title: string;
#Column('text') sub_title: string;
#Column('text') content: string;
#ManyToOne(
type => User,
user => user.posts,
{
cascade: true,
},
)
user: User;
#OneToMany(
type => Comment,
comment => comment.post,
{
cascade: true,
},
)
comments: Comment[];
constructor(title?: string, content?: string) {
this.title = title || '';
this.content = content || '';
}
#BeforeInsert() async generateUID() {
this.uid = uuid();
}
}
Users
/**
* User Entity
*/
#Entity('users')
export class User {
#PrimaryGeneratedColumn('uuid') user_id: string;
#Column({ type: 'varchar', nullable: false, unique: true }) uid: string;
#Column({ type: 'varchar', nullable: false }) name: string;
#Column({ type: 'varchar', nullable: false, unique: true }) email: string;
#Column({ type: 'varchar', nullable: false, unique: true }) username: string;
#Column({ type: 'varchar', nullable: false }) password: string;
#OneToMany(
type => Post,
post => post.user,
{
eager: true,
},
)
posts: Post[];
#OneToMany(
type => Comment,
comment => comment.user,
)
comments: Comment[];
constructor(name?: string, posts?: []);
constructor(name?: string) {
this.name = name || '';
}
#BeforeInsert() async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
this.uid = uuid();
}
}
Comments
/**
* Comments Entity
*/
#Entity('comments')
export class Comment {
#PrimaryGeneratedColumn('uuid') comment_id: string;
#Column('text') content: string;
#ManyToOne(
type => Post,
post => post.comments,
)
post: Post;
#ManyToOne(
type => User,
user => user.comments,
)
user: User;
}
Why is this happening?
Why is there a where clause when none was specified?
TypeORM version: ^0.2.22
TypeScript: ^3.7.4
Clearly the kush in these parts is potent and I don't read my own code..
in post.controller.ts, the #Get() decorator was missing the find keyword and my request looked like this:
http://localhost:3000/posts/find where find was not defined in the controller as a route
the solution was to add #Get('find') from nestjs/common
Post.controller.ts
/**
* Get all posts from all users
*/
#Get('find')
#ApiCreatedResponse({
status: 201,
description: 'All posts have been successfully retreived.',
type: [PostDTO],
})
#ApiResponse({ status: 403, description: 'Forbidden.' })
async find() {
try {
return this.postService.findAll();
} catch (error) {
throw new Error(error);
}
}
Post.service.ts
/**
* Find all posts
*/
async findAll(): Promise<Post[]> {
const posts = await this.postRepository.find();
return posts;
}
PS. I will add the nestjs tags since this is actually related to it more than TypeORM or PG