Search code examples
typescriptquery-buildertypeorm

TypeORM, ManyToMany Get posts by category id (TreeEntity materialized-path)


I'm trying to get posts by category like a CMS does.

For example query post by categorie A will include all posts attached to Categorie A and also post attached to child of Categorie A.

I really don't know how to build this query, so any help would be greatly appreciated :) .

Here is my entities:

@Tree("materialized-path")
export class Category {
  @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;


    @ManyToMany((type) => Post, (post) => post.categories)
    posts: Post[];

    @Expose()
    @TreeChildren()
    children: Category[];

    @Expose()
    @TreeParent()
    parent: Category;
}
export class Post{
   @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @ManyToMany((type) => Category, (category) => category.posts)
    @JoinTable()
    categories: Category[];
}

Followings SQL Query do the job(Example with category id 1)

SELECT * FROM post WHERE id IN (
    SELECT postId FROM post_categories_category as postCat WHERE postCat.categoryId IN (
       SELECT id FROM category WHERE category.mpath LIKE "1.%" OR category.mpath LIKE "%.1.%"
    )
)

So the question is, how to convert this SQL query into a typeORM query ?


Solution

  • I just wrote a quick possible solution. I tested it and it should work fine. Just respond if it doesnt

    @Entity()
    @Tree("materialized-path")
    export class Category extends BaseEntity {
      @PrimaryGeneratedColumn()
        id: number;
    
        @Column()
        title: string;
    
        @ManyToMany((type) => Post, (post) => post.categories)
        posts: Post[];
    
        @TreeChildren()
        children: Category[];
    
        @TreeParent()
        parent: Category;
    
        async getPosts(): Promise<Post[]> {
          const categories = await getConnection().getTreeRepository(Category).findDescendants(this); // gets all children
          categories.push(this); // adds parent
    
          const ids  = categories.map(cat => cat.id) // get an array of ids
    
          return await Post.createQueryBuilder('post')
            .distinct(true) // dont get duplicates (posts in two categories)
            .innerJoin('post.categories', 'category', 'category.id IN (:...ids)', {ids}) // get posts where category is in categories array
            .innerJoinAndSelect('post.categories', 'cat') // add all categories to selected post 
            .orderBy('post.id')
            .getMany()
        }
    }
    
    @Entity()
    export class Post extends BaseEntity {
      @PrimaryGeneratedColumn()
       id: number;
    
       @Column()
       title: string;
    
       @ManyToMany((type) => Category, (category) => category.posts)
       @JoinTable()
       categories: Category[];
    }
    
    
    

    this method uses querybuilders https://github.com/typeorm/typeorm/issues/2135#issuecomment-388801132

    aswell as the findDescendants function https://typeorm.io/#/tree-entities

    Hope this helps :)