how to limit typeorm join queries? - postgresql

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

Related

Cannot read properties of undefined reading 'getEntityValue' typeorm

I am creating a nestjs project with typeorm and postgresql. Here I am creating a user entity.
My entity will be like
{
email: string,
name: string,
avatar: {
public_key: string;
url: string;
}
}
In avatar column I need to add a object with public_key and url.
I create two entity like this-
#Entity()
class Avatar {
#Column({ type: "text", nullable: true })
public_key: string;
#Column({ type: "text", nullable: true })
url: string;
}
#Entity()
#Tree("closure-table")
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: "text", nullable: false })
userName: string;
#Column({ type: "text", nullable: false })
email: string;
#Column({ type: "text", nullable: false })
firstName: string;
#Column({ type: "text", nullable: false })
lastName: string;
#Column({ type: "text", nullable: false, select: false })
password: string;
#Column({ type: "text", nullable: true })
socket_id: string;
#Column({ type: "text", nullable: true, select: false })
otp: string;
#TreeChildren()
avatar: Avatar;
#Column({ type: "boolean", default: false, nullable: false })
is_verify: boolean;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
//Before Insert Injection
#BeforeInsert()
createUserName() {
this.userName = uniqid.time()
}
}
Here I use closure-table to do nested avatar entity
Here I write
#TreeChildren()
avatar: Avatar;
For nesting Avatar Entity
Then in my controller I try to create my first data-
const result = await this.userRepository.create({
...userInput,
otp: secret.base32,
password: passwordHash,
});
await this.userRepository.save(result)
Note: avatar field is not required. Avatar field will be add when user update their profile. Here I only create first user data.
But I am getting this error-
Cannot read properties of undefined (reading 'getEntityValue')
Additionally, here is my userInput
#InputType()
export class UserInput {
#Field(() => String, { nullable: false })
#IsString()
#IsEmail()
#IsNotEmpty()
email: string;
#Field(() => String, { nullable: false })
#IsString()
#IsNotEmpty()
firstName: string;
#Field(() => String, { nullable: false })
#IsString()
#IsNotEmpty()
lastName: string;
#Field(() => String, { nullable: false })
#IsString()
#MinLength(8)
#MaxLength(20)
#IsNotEmpty()
#Matches(/^.*(?=.{4,10})(?=.*\d)(?=.*[a-zA-Z]).*$/, { message: "password too weak" })
password: string
}
Please help me. I need this is very much. Over 10 days, I can't solve this. Please help me.

TypeORM one-to-many / many-to-one array empty and foreign key is null

I must be doing something wrong and I can not solve this issue.
I have two entities in my API
Collection
Asset
Each asset belongs to one collection
A Collection holds many Assets
These are my entity classes
import {
Entity,
Column,
PrimaryGeneratedColumn,
JoinColumn,
OneToOne,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { CollectionStats } from '../../../database/entities/opensea/CollectionStats';
import { Asset } from './Asset';
import { AssetContract } from './interfaces/AssetContract';
import { PaymentToken } from './interfaces/PaymentToken';
#Entity()
export class Collection {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column({ nullable: true })
external_link: string;
#Column({ nullable: true })
description: string;
#Column({ unique: true, nullable: false })
slug: string;
#Column({ nullable: true })
image_url: string;
#Column({ nullable: true })
banner_image_url: string;
#Column()
dev_seller_fee_basis_points: string;
#Column()
safelist_request_status: string;
#Column({ nullable: true })
payout_address: string;
#Column('jsonb')
primary_asset_contracts: AssetContract[];
#Column('simple-json')
traits: object;
#Column('jsonb', { nullable: true })
payment_tokens: PaymentToken[];
#Column('varchar', { array: true, nullable: true })
editors: string[];
#OneToOne(() => CollectionStats, { eager: true, cascade: true })
#JoinColumn()
stats: CollectionStats;
#OneToMany(() => Asset, (asset) => asset.collection, { cascade:true, eager: true })
assets: Asset[];
#Column({ type: 'timestamptz' })
created_date: Date;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
}
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { ColumnNumericOptions } from 'typeorm/decorator/options/ColumnNumericOptions';
import { Collection } from './Collection';
import { CollectionStats } from './CollectionStats';
import { AssetContract } from './interfaces/AssetContract';
import { Owner } from './interfaces/Owner';
import { PaymentToken } from './interfaces/PaymentToken';
import { Trait } from './interfaces/Trait';
#Entity()
export class Asset {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: false })
token_id: string;
#Column({ default: 0 })
num_sales: number;
#Column({ nullable: true })
background_color: string;
#Column({ nullable: true })
image_url: string;
#Column({ nullable: true })
image_preview_url: string;
#Column({ nullable: true })
image_thumbnail_url: string;
#Column({ nullable: true })
image_original_url: string;
#Column({ nullable: true })
animation_original_url: string;
#Column({ nullable: true })
external_link: string;
#Column({ nullable: true })
description: string;
#Column('simple-json')
asset_contract: AssetContract;
#Column({ nullable: true })
permalink: string;
#ManyToOne(() => Collection, (collection) => collection.assets)
collection: Collection;
#Column({ nullable: true })
decimals: number;
#Column({ nullable: true })
token_metadata: string;
#Column('simple-json')
owner: Owner;
#Column('simple-json', { array: false, nullable: true })
sell_orders: Object;
#Column('simple-json', { nullable: true })
traits: Trait[];
#Column('simple-json', { nullable: true })
last_sale: Object;
#Column('simple-json', { nullable: true })
top_bid: Object;
#Column('timestamptz', { nullable: true })
listing_date: Date;
#Column({ default: false })
is_presale: boolean;
#Column('simple-json', { nullable: true })
transfer_fee_payment_token: PaymentToken;
#Column({ nullable: true })
transfer_fee: number;
}
This is the the function where everything should get linked together
const collection = await getCustomRepository(CollectionRepository).create(
collectionResponse.collection as Object,
);
const assets = await this.fetchAssets(collection); //Returns an Array of type Asset
const stats = await getCustomRepository(CollectionStatsRepository).create(
statsResponse.stats as Object,
);
collection.stats = stats; //this works properly
collection.assets = assets; //this is not working
return await getCustomRepository(CollectionRepository).save(collection);
The response from getCustomRepo(CollectionRepo).save(collection) actually shows all the collection data including an array holding all the assets as I want it to be. However, when I open the postgres client, the collection table has no "assets" column and the assets table's collectionId column only contains "null"
the collection.find() also is only returning an empty assets[]
What am I doing wrong here?

Updating PSQL Tables with Relationships between Entities Using NestJS, TypeORM, GraphQL

I've been struggling for a week now on creating new tables and updating the TypeORM entities on the backend. We're using NestJS, GraphQL, and TypeORM with a PSQL database. We have a production server/database setup with clients' information saved already. I'm trying to add a new table to the database using a code-first approach to generate schema. On the master branch of the repo, I start it up in my local environment, and connect to a clean database. Once I create accounts, and save information to the tables, I then switch to a new branch that contains the code for implementing the new table, including the module, service, entity, and resolver. If I try to run this branch and connect to the same database I was using on master, it fails to compile, fails to generate a schema.gql file, and stops at "GraphQLModule dependencies initialized." This new table that I created has a ManyToOne relationship with the Teams table, that already has values contained in it. For some reason, I think TypeORM is failing to update the database properly, and I don't know why. If I create a new database, and connect to the new database on the branch with the new table code, it works just fine, and no errors are thrown. Problem is if I connect to the original database, no error is thrown, but the code fails to compile, and I don't know how to debug it.
Has anyone had any issue adding new tables to their PSQL database using TypeORM, Nest, and GraphQL?
Here are some code snippets showing what I mean:
Entity for Waiver Table (exists on the old database already)
#Entity({ name: 'waivers' })
#ObjectType()
export class WaiverEntity extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn('uuid')
id: string;
#Field(() => AccountEntity)
#ManyToOne(
() => AccountEntity,
creator => creator.waivers,
{ onDelete: 'SET NULL' },
)
#JoinColumn()
creator: Promise<AccountEntity>;
#Field(() => TeamEntity)
#ManyToOne(
() => TeamEntity,
team => team.waivers,
{ onDelete: 'CASCADE' },
)
#JoinColumn()
team: Promise<TeamEntity>;
#Field(() => ID)
#Column({ nullable: true })
creatorId: string;
#Field(() => ID)
#Index()
#Column({ nullable: true })
teamId: string;
#Field()
#Column('json')
organizer: Organizer;
#Field()
#Column('json')
event: Event;
#Field()
#Column('json', { nullable: true })
eventDate: EventDate;
#Field({ nullable: true })
#Column()
includeEmergencyContact: boolean;
#Field({ nullable: true })
#Column({ nullable: true })
customerLabel: string;
#Field(() => CustomEntity, { nullable: true, defaultValue: [] })
#Column('jsonb', { nullable: true })
intensity: CustomEntity;
#Field(() => [CustomEntity], { nullable: true, defaultValue: [] })
#Column('jsonb', { nullable: true })
activities: CustomEntity[];
#Field({ defaultValue: waiverStatus.DRAFT, nullable: false })
#Column({ default: waiverStatus.DRAFT, nullable: false })
status: string;
#Field({ nullable: true })
#Column({ type: 'varchar', nullable: true })
title: string;
#Field({ nullable: true })
#Column({ nullable: true })
body: string;
#Field({ nullable: true })
#Column({ nullable: true, default: signatureDefaultContent })
signatureContent: string;
#Field(() => [String], { nullable: true })
#Column('simple-array', { nullable: true })
ageGroup: string[];
#Field(() => [AdditionalFields], { nullable: false, defaultValue: [] })
#Column('jsonb', { nullable: true })
additionalFields: AdditionalFields[];
#Field({ nullable: false })
#Column({ nullable: false })
step: number;
#Exclude()
#Field({ nullable: true })
#Column({ nullable: true, unique: true })
pdfURL: string;
#BeforeInsert()
cleanUpBeforeUpdate(): void {
// add Prefix on retrieval
if (this.organizer && this.organizer.photoURL) {
try {
const photoUrls = this.organizer.photoURL.split(
`${AWS_BUCKETS.ORGANIZATION_BUCKET_IMAGE}/`,
);
this.organizer.photoURL =
photoUrls.length > 1 ? photoUrls[1] : this.organizer.photoURL;
} catch (e) {}
}
}
#AfterLoad()
updateURLs(): void {
// add Prefix on retrieval
this.pdfURL = this.pdfURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.WAIVER_BUCKET_FILES,
'https://',
)}/${this.pdfURL}`
: null;
if (this.organizer) {
this.organizer.photoURL = this.organizer.photoURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.ORGANIZATION_BUCKET_IMAGE,
'https://',
)}/${this.organizer.photoURL}`
: null;
}
}
#Field({ nullable: true })
#Column({ type: 'timestamp', nullable: true })
#IsDate()
publishDate: Date;
#Field({ nullable: true })
#Column({ nullable: true, unique: true })
slug: string;
#Field(() => [DownloadEntity], { nullable: true })
#OneToMany(
() => DownloadEntity,
downloadEntity => downloadEntity.waiver,
)
#JoinColumn()
waiverDownloads: Promise<DownloadEntity[]>;
#Field({ defaultValue: 0 })
downloadCount: number;
#Field(() => [WaiverMembersEntity])
#OneToMany(
() => WaiverMembersEntity,
waiverMember => waiverMember.account,
)
accountConnection: Promise<WaiverMembersEntity[]>;
#Field(() => [WaiverConsentsEntity])
#OneToMany(
() => WaiverConsentsEntity,
waiverMember => waiverMember.waiver,
)
consent: Promise<WaiverConsentsEntity[]>;
#Field(() => [AccountEntity])
waiverMember: AccountEntity[];
#Field(() => [ParticipantsEntity])
#OneToMany(
() => ParticipantsEntity,
participant => participant.waiver,
)
participants: ParticipantsEntity[];
#Field({ defaultValue: 0 })
totalResponses: number;
#Field()
eventName: string;
#Field({ nullable: true })
#Column({ type: 'varchar', nullable: true })
smsContent: string;
#Field({ nullable: true })
#Column({ nullable: true })
smsCode: string;
#Field()
#Column({ type: 'timestamp', default: () => timeStamp })
#IsDate()
createdAt: Date;
#Field()
#Column({
type: 'timestamp',
default: () => timeStamp,
onUpdate: timeStamp,
})
#IsDate()
lastUpdatedAt: Date;
}
And here is the new entity waiver templates, which has a ManyToOne relationship to the teams table, and exists on the new branch
#Entity({ name: 'waiverTemplates' })
#ObjectType()
export class WaiverTemplateEntity extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn('uuid')
id: string;
#Field(() => TeamEntity)
#ManyToOne(
() => TeamEntity,
team => team.waiverTemplates,
{ onDelete: 'CASCADE', eager: true },
)
#JoinColumn()
team: Promise<TeamEntity>;
#Field(() => ID)
#Index()
#Column({ nullable: true })
teamId: string;
#Field()
#Column('json')
event: Event;
#Field()
#Column('json')
eventDate: EventDate;
#Field({ nullable: true })
#Column({ nullable: true })
includeEmergencyContact: boolean;
#Field({ nullable: true })
#Column({ nullable: true })
customerLabel: string;
#Field(() => CustomEntity, { nullable: true, defaultValue: [] })
#Column('jsonb', { nullable: true })
intensity: CustomEntity;
#Field(() => [CustomEntity], { nullable: true, defaultValue: [] })
#Column('jsonb', { nullable: true })
activities: CustomEntity[];
#Field({ defaultValue: waiverStatus.DRAFT, nullable: false })
#Column({ default: waiverStatus.DRAFT, nullable: false })
status: string;
#Field({ nullable: true })
#Column({ type: 'varchar', nullable: true })
title: string;
#Field({ nullable: true })
#Column({ nullable: true })
body: string;
#Field({ nullable: true })
#Column({ nullable: true, default: signatureDefaultContent })
signatureContent: string;
#Field(() => [String], { nullable: true })
#Column('simple-array', { nullable: true })
ageGroup: string[];
#Field(() => [AdditionalFields], { nullable: false, defaultValue: [] })
#Column('jsonb', { nullable: true })
additionalFields: AdditionalFields[];
#Field()
eventName: string;
}
And finally, here is the teams table, which also exists on the old branch. This is the code from the new branch, which contains a new OneToMany relationship to the WaiverTemplateEntity.
#Entity({ name: 'teams' })
#ObjectType()
export class TeamEntity extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn('uuid')
id: string;
#Field()
#Column('varchar')
title: string;
#Field({ nullable: true })
#Column('varchar', { nullable: true })
taxID?: string;
#Field({ nullable: true })
#Column(simpleJSON, { nullable: true })
type: CustomEntity;
#Field({ nullable: true })
#Column('varchar', { nullable: true })
description?: string;
#Field(() => AccountEntity, { nullable: false })
#OneToOne(
() => AccountEntity,
accountEntity => accountEntity.organization,
{ nullable: true, onDelete: 'SET NULL' },
)
creator: AccountEntity;
#Field({ nullable: true })
#Column({ nullable: true })
creatorId: string;
#Field(() => BillingEntity, { nullable: true })
#OneToOne(
() => BillingEntity,
billingEntity => billingEntity.team,
{ cascade: true },
)
billingInformation: Promise<BillingEntity>;
#Field({ nullable: true })
#Column('varchar', { nullable: true })
photoURL?: string;
#Field({ defaultValue: false })
#Column({ default: false })
nonProfitFreemium: boolean;
#AfterLoad()
updateURLs(): void {
// add Prefix on retrieval
this.photoURL = this.photoURL
? `${getBucketPrefix(
AWS_BUCKETS_TYPES.ORGANIZATION_BUCKET_IMAGE,
'https://',
)}/${this.photoURL}`
: null;
}
#Field(() => [CardEntity], { nullable: true })
#OneToMany(
() => CardEntity,
cardEntity => cardEntity.holder,
{ cascade: true },
)
cards: Promise<CardEntity[]>;
#Field({ nullable: true, defaultValue: {} })
#Column(simpleJSON, { nullable: true })
location?: LocationEntity;
#Field({ nullable: true, defaultValue: {} })
#Column(simpleJSON, { nullable: true })
contact?: ContactEntity;
#Field({ nullable: true })
#Column({ nullable: true })
numberOfEmployees?: string;
#Field({ nullable: true })
#Column({ nullable: true })
stripeId?: string;
#Field()
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
#IsDate()
createdAt: Date;
#Field()
#Column({
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP(6)',
onUpdate: 'CURRENT_TIMESTAMP(6)',
})
#IsDate()
lastUpdatedAt: Date;
#Field(() => [InvitationEntity])
#OneToMany(
() => InvitationEntity,
invitationEntity => invitationEntity.team,
)
invitations: Promise<InvitationEntity[]>;
#Field(() => [WaiverEntity])
#OneToMany(
() => WaiverEntity,
waiver => waiver.team,
)
waivers: Promise<WaiverEntity[]>;
#Field({ nullable: true })
#Column({ default: () => 0 })
credits: number;
#Field({ nullable: true })
#Column({ default: () => false })
autoReload: boolean;
#Field({ nullable: true })
#Column({ default: () => 0 })
autoReloadAmount: number;
#Field({ nullable: true })
#Column({ default: () => 0 })
autoReloadMinAmount: number;
#Field({ nullable: true })
#Column({ type: 'float', default: 0.0 })
fixedWaiverPrice: number;
#Field(() => [TransactionEntity])
#OneToMany(
() => TransactionEntity,
transaction => transaction.team,
)
transactions: Promise<TransactionEntity[]>;
#Field(() => [WaiverTemplateEntity])
#OneToMany(
() => WaiverTemplateEntity,
waiverTemplate => waiverTemplate.team,
)
waiverTemplates: Promise<WaiverTemplateEntity[]>;
}
I know there's a lot of columns in the tables, but the ones to pay attention to are the relationships between the Teams table and the WaiverTemplates table. This is the only thing I changed in the entities, and what I think may be responsible for me being unable to connect to the previous database on this new branch. If you want to see my service, resolver, or modules, please ask. I don't believe they are causing any issues, because if I connect to a new database, everything compiles and works as intended, no errors are thrown. I'm really just looking for any insight on how to debug this problem.
If anyone is interested in this issue, I resolved the error finally today, at least in respects to the tables above.
When changing the PSQL database with TypeORM, it's better to either create or generate your own migration files with typeorm migration:generate -n [name of migration file] and then
typeorm migration:run. The generate command will auto generate an up and down SQL migration to run. You can use npx before this command or access the cli from node_modules, because just running the typeorm command gave me a command not found error.
I then looked inside the generated migration file, and lo and behold, the columns I was adding to the table were not set to be NULL, and thus I had the error of the values for those columns in the previous table being null. I had to manually add NULL to each of those columns for the code to compile. It's weird though, because I updated the entities to have {nullable: true} in the #Column decorators for those fields.
If anyone knows how to work better with changing relations in already existing tables with TypeORM and Nest, please reach out to me. I'm still working on writing the SQL manually for the migration file so that I can change the relationships in three other tables. I'm working with legacy code done poorly, so the relationships were wrong from the beginning.

Get entities within 100km with postgresql and typeorm ordered

I get this error
Cannot read property 'databaseName' of undefined
https://github.com/typeorm/typeorm/issues/4270
I follow github issue but it not help, the query works correctly If I delete addOrderBy method
but I need the results ordered by the distance so there is a way to make this work ?
Entity
#Entity('restaurant')
export class RestaurantEntity extends AbstractEntity {
#Column()
name: string;
#Column()
description: string;
#OneToMany(() => MenuEntity, (menu) => menu.restaurant, {
onDelete: 'CASCADE',
cascade: true,
})
menus: MenuEntity[];
#Column()
type: string;
#Column()
location: string;
#Column('geometry', {
name: 'geo',
nullable: true,
spatialFeatureType: 'Point',
})
geoLocation: object;
#Column({
nullable: true,
})
likes: number;
#OneToMany(
() => RestaurantImageEntity,
(restaurantImage) => restaurantImage.restaurant,
{
onDelete: 'CASCADE',
cascade: true,
},
)
restaurantImages: RestaurantImageEntity[];
#Column({
nullable: true,
})
views: number;
#Column({
nullable: true,
})
totalFavorites: number;
#Column({
nullable: true,
})
telephone: string;
#Column({
nullable: true,
})
web: string;
#Column({
nullable: true,
})
email: string;
#ManyToOne(() => UserEntity, (user) => user.restaurants, {
onDelete: 'CASCADE',
})
creator: UserEntity;
}
Query
const query.geoLocation = [x,y];
restaurants = await this.restaurantRepository
.createQueryBuilder('restaurant')
.leftJoinAndSelect('restaurant.restaurantImages', 'restaurantImages')
.where(ST_DWithin(restaurant.geoLocation, ST_MakePoint(${query.geoLocation[0]},${query.geoLocation[1]})::geography, 100000))
.orderBy(sort ? sort : { 'restaurant.id': 'DESC' })
.addOrderBy(`restaurant.geoLocation <-> ST_MakePoint(${query.geoLocation[0]},${query.geoLocation[1]})::geography`)
.skip((page - 1) * perPage)
.take(perPage)
.getMany();

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.