TypeORM Postgres NodeJs Saving Data ManyToMany - node.js

I have 4 Entities that I set up and I want to save a recipe.
#Entity('recipes')
export class Recipe extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ type: String, unique: true })
recipe_name: string;
#Column({ type: String })
description: string;
#OneToMany(() => RecipeIngredientList, (r: RecipeIngredientList) => r.recipeId)
recipeLists: RecipeIngredientList[];
}
#Entity('measurements')
export class MeasurementUnits {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: String })
unit: string;
#ManyToMany(() => RecipeIngredientList, (r) => r.unitID, { cascade: true })
recipeList: RecipeIngredientList[];
}
#Entity('ingredients')
export class Ingredient {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: String })
ingredient_name: string;
#ManyToMany(() => RecipeList, (r: RecipeList) => r.ingredientID, { cascade: true })
recipeList: RecipeList[];
}
#Entity('RecipeIngredientList')
export class RecipeIngredientList{
#PrimaryGeneratedColumn()
id: number;
#Column({type:Number})
amount: number;
#ManyToOne(() => Recipe, (r) => r.recipeLists)
recipeID: Recipe;
#ManyToMany(() => Ingredient)
#JoinTable()
ingredientID: Ingredient[];
#ManyToMany(() => MeasurementUnits)
#JoinTable()
unitID: MeasurementUnits[];
}
//My Save Method() The getDatabaseConnention() is in a service I wrote to
// check/connect via the DataSource.manager
const create = async (recipe: Recipe): Promise<Recipe> => {
const recipeRepository = await (
await getDatabaseConnection()).getRepository(Recipe);
const newRecipe: Recipe = await recipeRepository.save(recipe)
.catch((e) => {
console.debug('failed to create recipe Record', e);
throw new Error(e);
});
return newRecipe;
};
The effect of this is its creating the 6 tables in Postgres as expected but its only saving to the main recipes table and not able to save any of the relations. Am I missing foreignKeys or constraints in my entity setup? Or is my save method not properly identifying the relationships?
Now I know the way I have the DB setup the RecipeIngredientList Table is going to be the work horse and has a potential to get bloated, but I think this is a decent setup for a recipe management (I removed a lot of metadata to not bloat the post).

Related

Make a user object property resolved inside #FieldResolver (posts)

I am creating a reddit clone, using a type-graphql, express-graphql and typegoose,
first i run a query for posts and i get the user info (_id, username) :
query posts {
posts {
_id
title
image
body
user {
_id
username
}
subredditList {
topic
_id
}
createdAt
}
}
but when i run query for subreddit to get all posts that belong to that subreddit, i get a user is null
the query i run for subreddit:
query subreddit($topic: String!) {
subreddit(input: { topic: $topic }) {
_id
topic
posts {
_id # works fine
title # works fine
body # works fine
image # works fine
# the user is null !!, i get user: null
user {
_id
username
}
createdAt
}
}
}
I get user: null even when the posts field in Subreddit object type is resolved
Subreddit object type:
#ObjectType()
export class Subreddit {
#Field(() => String)
_id: string;
#Field(() => String)
#prop({ required: true })
topic: string;
#Field(() => [Post])
posts: Post[]; // i get all info exept the user object
#Field(() => String)
createdAt: string;
#Field(() => String)
updatedAt: string;
}
Subreddit resolver:
#Resolver(() => Subreddit, {})
export default class SubredditResolver {
constructor(private readonly subredditService: SubredditService) {
this.subredditService = new SubredditService();
}
#Query(() => [Subreddit])
async subreddits() {
return this.subredditService.subreddits();
}
#Query(() => Subreddit, { nullable: true })
async subreddit(#Arg("input") input: GetSubredditByTopic) {
return this.subredditService.subreddit(input);
}
#Mutation(() => Subreddit)
async createSubreddit(
#Arg("input") input: CreateSubredditInput
): Promise<Subreddit> {
return this.subredditService.createSubreddit(input);
}
// here where i resolve the "posts" field in my Subreddit object type
#FieldResolver(() => Post, {})
async posts(#Root() subreddit: Subreddit) {
console.log(args);
return this.subredditService.posts(subreddit);
}
}
and finally the resolver function is like this:
async posts(input: Subreddit) {
try {
const posts = await PostModel.find({ subredditId: input._id });
return posts;
} catch (err) {
console.log(err);
throw new Error("Couldn't get the posts");
}
}
Here is my Post object type looks like:
#ObjectType()
export class Post {
#Field(() => String)
_id: string;
#Field(() => String)
#prop({ required: true })
title: string;
#Field(() => String)
#prop({ required: true })
body: string;
#Field(() => String, { nullable: true })
#prop({})
image: string;
#Field(() => String)
#prop({ ref: () => Subreddit })
subredditId: Ref<Subreddit>;
#Field(() => [Subreddit])
subredditList: [Subreddit];
#Field(() => [Comment])
comments: Comment[];
// note that the "user" field is string refers to id of the user,
#Field(() => User, { nullable: true })
#prop({ ref: () => User })
user: Ref<User>;
#Field(() => Date)
createdAt: Date;
#Field(() => Date)
updatedAt: Date;
}
Post resolver:
#Resolver(() => Post)
export default class PostResolver {
constructor(private readonly postService: PostService) {
this.postService = new PostService();
}
#Query(() => [Post])
async posts() {
return this.postService.posts();
}
#Authorized()
#Mutation(() => Post)
async createPost(
#Arg("input") input: CreatePostInput,
#Ctx() context: Context
): Promise<Post> {
return this.postService.createPost(input, context);
}
#FieldResolver()
async subredditList(#Root() post: Post) {
return this.postService.subredditList(post);
}
// here i resolve the user field in my Post object type, and it works fine
#FieldResolver()
async user(#Root() post: Post) {
return this.postService.user(post);
}
}
the resolver function for "user" field in Post object type:
async user(input: Post) {
try {
const user = await UserModel.findOne({
_id: input.user,
}).lean();
return user;
} catch (err) {
console.log(err);
throw new Error("Couldn't get the user");
}
}
the property "user" from post query is resolved but when i run subreddit query i get user is null .
any help please ?
thanks in advance.
Your problem seems to be that you are missing setting the type for arrays, like:
class YourClass {
// ...
#Field(() => [Post])
posts: Post[];
// ...
}
but it would need to be like:
class YourClass {
// ...
#Field(() => [Post])
#prop({ type: () => [Post] })
posts: Post[];
// ...
}
see Quick-Start-Guide
type has to be defined when working with Arrays, because Reflection only returns basic information. Look here for why
Like public: string[] is in reflection only Array.
or the note in #prop: Array Options
Option type must be provided, otherwise the array will result in Mixed read typegoose issue #300 for more info
Note: i dont know if anything is wrong with your graphql setup
i also read in the comments that there is supposedly a _doc property, which is internal in mongoose and should never be shown, but it gets exposed instead of properly translated due to a problem with class-transformer, the only workaround is to use #Exclude() for the whole class (or globally) and only #Expose() the proper properties, also see Integration Examples: Using with class-transformer

typeorm) query builder, left join and delete

I want to receive id and user information from the controller as arguments and delete it with query builder. But I am getting QueryFailedError: Unknown column 'certificate.id' in 'where clause' error. What's the problem?
#Entity()
#Unique(['url'])
export class CertificateEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#IsUrl()
url: string;
#Column()
valid_from: string;
#Column()
valid_to: string;
#ManyToOne((type) => AuthEntity, (user) => user.certificates, {
eager: false,
})
user: AuthEntity;
}
#Unique(['username'])
#Entity()
export class AuthEntity extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
email: string;
#Column()
username: string;
#Column()
password: string;
#Column()
signupVerifyToken: string;
#OneToMany((type) => CertificateEntity, (certificates) => certificates.user, {
eager: true,
})
certificates: CertificateEntity[];
}
// service
async remove(id: number, user: AuthEntity): Promise<boolean> {
console.log(id, user.id);
// error this point !
await this.certificateRepo
.createQueryBuilder('certificate')
.leftJoin('certificate.user', 'user')
.where('certificate.id=:id', { id: id })
.andWhere('user.id=:id', { id: user.id })
.delete()
.execute();
return true;
}
I'm not familiar with the query builder. Why Do I Get QueryFailedError: Unknown column 'certificate.id' in 'where clause' Error?
If it's still relevant. I never saw the solution to delete with use leftJoin.
try to use default delete(), where "id" it's "certificate.id"
this.certificateRepo.delete({ user: { id }, id: "id" });
it's just a fast example, rewrite to suit your case.
Or use 2 steps (I suggest it's a bad sulution)
const result = await this.certificateRepo
.createQueryBuilder('certificate')
.leftJoin('certificate.user', 'user')
.where('certificate.id=:id', { id: id })
.andWhere('user.id=:id', { id: user.id })
.getMany(); // .getOne()
result.forEach((el) => this.repository.delete(el.id));
you need to remove the alias from the where, and the where expression needs to provide a unique parameter, otherwise the :id in andWhere will override the :id in where.
await this.certificateRepo
.createQueryBuilder('certificate')
.leftJoin('certificate.user', 'user')
.where('id=:id', { id: id })
.andWhere('user.id=:userId', { userId: user.id })
.delete()
.execute();

NestJs, TypeOrm) Delete request with 2 Params

I want to allow only the author of the post to remove the post from the db.
When requesting delete from controller, typeORM intended delte({id, user}) by sending post ID and user information together, but an error occurs.
//entity
#Entity()
export class Board extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column()
description: string;
#Column()
status: BoardStatus;
#ManyToOne((type) => UserEntity, (user) => user.boards, { eager: false })
user: UserEntity;
}
// controller
#Delete(':id')
deleteBoard(
#Param('id', ParseIntPipe) id: number,
#GetUser() user: UserEntity,
): Promise<void> {
return this.boardService.deletBoard(id, user);
}
// service
async deletBoard(id: number, user: UserEntity): Promise<void> {
const result = await this.boardRepository.delete({ id, user });
/* Argument of type '{ id: number; user: UserEntity; }' is not assignable to parameter of type 'string | number | Date | ObjectID | string[] | number[] | Date[] | ObjectID[] | FindOptionsWhere<Board>'.
Types of property 'user' are incompatible. */
if (result.affected === 0)
throw new NotFoundException(`Can't find by ${id}`);
}
try createQueryBuilder():
await this.boardService.createQueryBuilder('board')
.leftJoin('board.user', 'user')
.where('board.id = :id', {id})
.andWhere('user.id = :id', {id: user.id})
.delete()
.execute();

Type 'string' has no properties in common with type 'FindOneOptions<User>'

I am trying to build a backend with express.js. I now have the following problem:
import { Request, Response } from "express";
import { getManager } from "typeorm";
import { User } from "../entity/user.entity";
export const GetUser = async (req: Request, res: Response) => {
const repository = getManager().getRepository(User);
const { password, ...user } = await repository.findOne(req.params.id);
res.send(user);
};
The following error always occurs:
(parameter) req: Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>
Type 'string' has no properties in common with type 'FindOneOptions'.ts(2559)
router.ts
router.get("/api/users/:id", AuthMiddleware, GetUser);
user.entity.ts
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import { Role } from "./role.entity";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
first_name: string;
#Column()
last_name: string;
#Column({
unique: true,
})
email: string;
#Column()
password: string;
#ManyToOne(() => Role)
#JoinColumn({ name: "role_id" })
role: Role;
}
Can anyone help me with my problem?
The typeorm's findOne function is equivalent to Select * ... limit 1. This means that the findOne actually expects an object containing conditions to match and return the first element for which the condition is satisfied.
In your case, the code should be:
repository.findOne({where: {id: parseInt(req.params.id, 10)}})
or
repository.findOneBy({id: parseInt(req.params.id, 10)})
This will find the user whose id field will match match the req.params.id.
according to manual, use the { criteria: search }
so
findOneBy({id: req.params.id});

Is it possible to create an entity member that will group other members with OneToMany relations under a single class in typeorm?

Server side built with nodeJS + express with nestJS, apollo-server (graphQL), typeorm and postgreSQL.
There is an entity which has oneToMany relations with other entities.
is it possible to group some of them in a class that has no representation in the DB, or in other words - is not an entity?
For example:
There is an entity Patient which has relations with the entities: PatientAlergy, PatientSymptom, MedicalCondition, etc...
I would lie to group all of those in a single class MedicalHistory so that Patient will have a member medicalHistory.
Is is possible to give this medicalHistory field a deecorator to define it so that the ORM will recognize it?
I tried looking for an answer in typeorm's docs and examples, but didn't find anything useful.
patient.entity.ts:
#Entity()
#ObjectType()
#InputType('patient')
export class Patient extends BaseEntity {
#PrimaryGeneratedColumn()
#Field(type => ID, { nullable: true })
id: number
#Field(type => MedicalHistory, { nullable: true })
//#SomeDecorator()
medicalHistory?: MedicalHistory
}
madical-history.dto.ts:
#ObjectType()
#InputType('medicalHistory')
// #SomeOtherDecorator()
export class MedicalHistory {
#Field(type => MedicalCondition, { nullable: true })
#OneToMany(type => MedicalCondition, medicalCondition => medicalCondition.patient, { cascade: true })
medicalConditions: MedicalCondition[]
#Field(type => FamilyMedicalCondition, { nullable: true })
#OneToMany(type => FamilyMedicalCondition, familyMedicalCondition => familyMedicalCondition.patient, { cascade: true })
familymedicalConditions: FamilyMedicalCondition[]
#Field(type => PatientAlergy, { nullable: true })
#OneToMany(type => PatientAlergy, patientAlergy => patientAlergy.patient, { cascade: true })
alergies: PatientAlergy[]
#Field(type => PatientProcedure, { nullable: true })
#OneToMany(type => PatientProcedure, patientProcedure => patientProcedure.patient, { cascade: true })
procedures: PatientProcedure[]
#Field(type => PatientSymptom, { nullable: true })
#OneToMany(type => PatientSymptom, patientSymptom => patientSymptom.patient, { cascade: true })
symptoms: PatientSymptom[]
}
I would love to know if there is an option to describe the patient's medical history that way.

Resources