How to update or create entities(with relations) via graphql and typeorm - postgresql

I use typeorm, type-graphql and postgres, apollo-graphql-express at my project. I have some troubles with creating or updating entities via typeorm.
Example: I made queries and mutations(by my task explanation) such as
//Mutation
registerRestaurant(name: String, address: String, phone: String, type: Int, restaurant_key: String, headquarter: Int): Restaurant
//Mutation
updateRestaurant(restaurantId: Int!, name: String, address: String, phone: String, type: Int, restaurant_key: String, headquarter: Int): Restaurant
Restaurant entity has headquarter property which returns Headquarter entity(for relation cases), here is an trouble, that then i can't update or create Restaurant entity using id as an argument in mutation. (example: in mutation update restaurant i need to change headquarter with id = int, but Restaurant entiry doesn't allow me to do that because of JoinColumn i think)).
I will be very happy if you pay attention to this, if some questions about my code, i will answer, THANKS!
#ObjectType()
#Entity("restaurants")
export class Restaurant extends BaseEntity {
#Field(() => Int)
#PrimaryGeneratedColumn()
id: number;
#Field()
#Column("text", { default: '' })
name: string;
#Field()
#Column("text", { default: '' })
address: string;
#Field()
#Column("text", { default: '' })
phone: string;
#Field(() => Int)
#Column("int", { default: 0 })
type: number;
#Field()
#Column("text", { default: '' })
restaurant_key: string;
#OneToOne(() => Headquarter)
#Field(() => Headquarter)
#JoinColumn()
headquarter: Headquarter;
}
#Resolver()
export class RestaurantResolver {
#Query(() => Restaurant)
async getRestaurant(
#Arg("restaurantId", { nullable: false }) restaurantId: number
) {
console.log(await Restaurant.findOne(restaurantId, { relations:
["headquarter"] }));
const restaurant = await Restaurant.findOne(restaurantId, { relations:
["headquarter"] });
if (restaurant) {
return restaurant;
} else throw new Error("restaurant not found");
};
#Query(() => [Restaurant])
async getRestaurants() {
try {
return await Restaurant.find({ relations: ["headquarter"] });
} catch (error) {
console.log(error);
throw new Error('restaurants not found');
}
};
#Mutation(() => Restaurant)
async registerRestaurant(
#Arg("name", { nullable: false }) name: string, // nullable: false means
REQUIRED FIELD
#Arg("address", { nullable: false }) address: string,
#Arg("phone", { nullable: false }) phone: string,
#Arg("type", { nullable: false }) type: number,
#Arg("restaurant_key", { nullable: false }) restaurant_key: string,
// #Arg("headquarter", { nullable: false }) headquarter: number
) {
const restaurant = new Restaurant();
restaurant.name = name || restaurant.name;
restaurant.address = address || restaurant.address;
restaurant.phone = phone || restaurant.phone;
restaurant.type = type || restaurant.type;
restaurant.restaurant_key = restaurant_key ||
restaurant.restaurant_key;
// restaurant.headquarter = headquarter || restaurant.headquarter;
//??? how to update when in Restaurant Entity is OnetoOne and
JoinColumn
try {
return await Restaurant.save(restaurant);
} catch (error) {
console.log(error);
return error.message;
};
};
#Mutation(() => Restaurant)
async updateRestaurant(
#Arg("restaurantId", { nullable: false }) restaurantId: number,
#Arg("name", { nullable: true }) name?: string,
#Arg("address", { nullable: true }) address?: string,
#Arg("phone", { nullable: true }) phone?: string,
#Arg("type", { nullable: true }) type?: number,
#Arg("restaurant_key", { nullable: true }) restaurant_key?: string,
#Arg("headquarter", { nullable: true }) headquarter?: number
) {
console.log("phone",phone);
const restaurantToUpdate = await Restaurant.findOne(restaurantId);
console.log(restaurantToUpdate);
if (restaurantToUpdate) {
restaurantToUpdate.name = name || restaurantToUpdate.name;
restaurantToUpdate.address = address || restaurantToUpdate.address;
restaurantToUpdate.phone = phone || restaurantToUpdate.phone;
restaurantToUpdate.type = type || restaurantToUpdate.type;
restaurantToUpdate.restaurant_key = restaurant_key ||
restaurantToUpdate.restaurant_key;
restaurantToUpdate.headquarter = headquarter ||
restaurantToUpdate.headquarter; // type number is not assignable to type
Headquarter
return Restaurant.save(restaurantToUpdate);
} else {
throw new Error("restaurant not found");
};
};
};

Related

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

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>

typeorm geometry type Undefined type error

everyone. I am creating a project that uses geometry data using postgresql postgis this time. So I want to declare geometry in the column and use it, but there's an error. Could you tell me why there is an error?
Multiple official documents were checked, but no method was found.
Commenting the coordinate column will create the code normally.
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn
} from 'typeorm';
import { Location_Group } from './location_group.entity';
import { Geometry } from 'geojson';
import { Field, ID, Int, ObjectType } from '#nestjs/graphql';
#ObjectType()
#Entity()
export class Location {
#Field(() => ID)
#PrimaryGeneratedColumn('increment')
id: number;
#Field(() => String)
#Column({ type: 'varchar' })
name: string;
#Field()
#Column({
type: 'geometry',
nullable: true,
spatialFeatureType: 'Point',
srid: 4326
})
coordinate: Geometry;
#Field(() => Int)
#Column({ type: 'int' })
order_number: number;
#Field()
#CreateDateColumn({ type: 'timestamptz' })
created_at: Date;
#Field(() => Location_Group)
#ManyToOne(
() => Location_Group,
(location_group) => location_group.location
)
#JoinColumn([{ name: 'location_group_id', referencedColumnName: 'id' }])
location_group: Location_Group;
}
There is someone who wants me to share the scalar I made, so I write it here. I hope this code helps you.
import { GraphQLScalarType } from 'graphql';
export const GeoJSONPoint = new GraphQLScalarType({
name: 'GeoJSONPoint',
description: 'Geometry scalar type',
parseValue(value) {
return value;
},
serialize(value) {
return value;
},
parseLiteral(ast) {
const geometryData = {
type: '',
coordinates: []
};
for (const i in ast['fields']) {
if (ast['fields'][i]['name']['value'] == 'type') {
if (ast['fields'][i]['value']['value'] != 'point') {
return null;
}
geometryData.type = ast['fields'][i]['value']['value'];
}
if (ast['fields'][i]['name']['value'] == 'coordinate') {
for (const j in ast['fields'][i]['value']['values']) {
geometryData.coordinates.push(
parseFloat(
ast['fields'][i]['value']['values'][j]['value']
)
);
}
}
}
return geometryData;
}
});

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();

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