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

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

Related

#nestjs/mongoose, virtual populate with 2 databases

I'm trying to populate userId field which exists in database 1 from user document which exists in database 2.
I've already defined connectionName parameter in MongooseModule.ForRootAsync() I can't find out where the problem is. Also it works if I request info from db1 and db2 individually.
Actually on console.log(commentPopulated) userId field is just the objectId without populated fields from User schema and also sometimes with certain #Prop() and .populate() parameters app throw me this error:
MissingSchemaError: Schema hasn't been registered for model "User".
Using #nestjs/mongoose decorators how do I achieve this?
app.module.ts
MongooseModule.forRootAsync({
connectionName: 'db1',
useFactory: () => ({
uri: process.env.DB1,
connectionFactory: (connection: { plugin: (arg0: unknown) => void }) => {
connection.plugin(_)
connection.plugin(autoPopulate)
return connection
},
}),
}),
MongooseModule.forRootAsync({
connectionName: 'db2',
useFactory: () => ({
uri: process.env.DB2,
connectionFactory: (connection: { plugin: (arg0: unknown) => void }) => {
connection.plugin(_)
connection.plugin(autoPopulate)
return connection
},
}),
}),
comment.module.ts
const commentModule: DynamicModule = MongooseModule.forFeatureAsync([
{
name: Comment.name,
useFactory: () => {
return CommentSchema
}
}
], 'db1')
#Module({
imports: [commentModule],
providers: [CommentService, CommentResolver]
})
export class CommentModule { }
comment.schema.ts
#Schema({ toJSON: { virtuals: true, getters: true }, toObject: { virtuals: true, getters: true } })
#ObjectType()
export class Comment extends Document {
#Prop()
#Field(() => String)
readonly _id: MongooseSchema.Types.ObjectId
#Prop({ required: true })
#Field(() => String)
text: string
//TODO: Reference User document from DB2, Comment document exists in DB1
#Prop({ type: MongooseSchema.Types.ObjectId, ref: User.name})
#Field(() => User, { nullable: true })
userId: MongooseSchema.Types.ObjectId
#Prop({ type: String, enum: UserType, required: true, default: UserType.Regular })
#Field(() => UserType, { defaultValue: UserType.Regular })
userType: UserType
#Prop({ default: Date.now })
#Field(() => Date)
created: Date
}
export const CommentSchema = SchemaFactory.createForClass(Comment)
user.module.ts
const userModule: DynamicModule = MongooseModule.forFeatureAsync([
{
name: User.name,
useFactory: () => {
return UserSchema
},
},
], 'db2')
#Module({
imports: [userModule],
providers: [UserService, UserResolver]
})
export class UserModule { }
user.schema.ts
#Schema()
#ObjectType()
export class User extends Document {
#Prop()
#Field(() => String)
readonly _id: MongooseSchema.Types.ObjectId
#Prop({ required: true })
#Field(() => String)
firstName: string
#Prop({ required: true })
#Field(() => String)
lastName: string
#Prop({ required: true })
#Field(() => String)
email: string
}
export const UserSchema = SchemaFactory.createForClass(User)
comment.service.ts
#Injectable()
export class CommentService {
constructor(#InjectModel(Comment.name, 'db1') private readonly model: Model<Comment>) { }
async getComments() {
const commentPopulated = await this.model.findById('63b8608c7d4f880cba028bfe').populate('userId')
console.log(commentPopulated)
return commentPopulated
}
}
I have tried randomly playing with parameters on #Prop() decorator with no success, I think there is the problem, also played with .populate() function parameters.

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[];

(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.