I want to create a self-related table in typeorm using nestjs - postgresql

I need an entity to have following columns:
Category:{ division_id:int cat_id:int cat_name:varchar }
I also want to self-related a column inside it to have parent category for some categories, so I created the entity below:
`import { ApiProperty } from '#nestjs/swagger';
import { BaseEntity } from '../embeded/base.type';
import { Commodity } from './commodity.entity';
import { Division } from '../public/division.entity';
import {
Entity,
Column,
ManyToOne,
JoinColumn,
OneToMany,
PrimaryColumn,
PrimaryGeneratedColumn,
ManyToMany,
} from 'typeorm';
#Entity()
export class Category extends BaseEntity {
#ApiProperty()
#PrimaryColumn()
division_id: number;
#ManyToOne(() => Division, (division) => division.categories, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
#JoinColumn({ name: 'division_id', referencedColumnName: 'division_id' })
division: Division;
#PrimaryGeneratedColumn()
cat_id: number;
#ApiProperty()
#Column()
cat_name: string;
#ApiProperty()
#Column()
parent_id: number;
#ManyToOne(() => Category, (category) => category.sub_category, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
#JoinColumn([{ name: 'parent_id', referencedColumnName: 'cat_id' }])
#ApiProperty({ type: () => Category, required: false })
parent_category: Category;
#OneToMany(() => Category, (category) => category.parent_category)
sub_category: Category[];
#ManyToMany(() => Commodity, (commodity) => commodity.categories)
commodities: Commodity[];
}`
But I got this error:
there is no unique constraint matching given keys for referenced table "category"

Related

Create foreign key that references another schema

I have a multi tenant app which uses sequelize-typescript. Each time a new user is created, a new schema is created for them.
There's only one table in public schema - "Users".
Each tenant schema has "Roles" and "UserRoles" as a junction table.
I want UserRoles to reference Users in a public schema, not in a tenant schema. How can I achieve that?
user model
#Table({ paranoid: true })
export default class User extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#Column(DataType.STRING)
name: string;
#BelongsToMany(() => Role, () => UserRole)
roles: Role[];
role model
#Table({ paranoid: true })
export default class Role extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#Column(DataType.STRING)
name: string;
#BelongsToMany(() => User, () => UserRole)
users: User[];
}
user-role model
#Table({ paranoid: true, freezeTableName: true })
export default class UserRole extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#ForeignKey(() => Role)
#Column(DataType.BIGINT)
roleId: number;
#Unique
#ForeignKey(() => User)
userId: number;
}
I tried to specify schema inside column, but it still references tenant schema
#Table({ paranoid: true, freezeTableName: true })
export default class UserRole extends Model {
#AutoIncrement
#PrimaryKey
#Column(DataType.BIGINT)
id: number;
#ForeignKey(() => Role)
#Column(DataType.BIGINT)
roleId: number;
#Unique
#ForeignKey(() => User)
#Column({
type: DataType.BIGINT,
references: {
model: {
tableName: 'users',
schema: 'public',
},
key: 'id',
},
})
userId: number;
}

How can I create a UUID FK column in NestJS?

I am running into an odd issue where I can't create a FK relationship between two entities.
// organization.entity.ts
#PrimaryGeneratedColumn('uuid')
id: string;
...
#OneToMany(() => User, (user) => user.organization)
users: User[];
// user.entity.ts
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
type: 'uuid',
})
organizationId: string;
...
#ManyToOne(() => Organization, (organization) => organization.users)
organization: Organization;
In my ormconfig.json file I have these settings (among connection creds)
...
"logging": true,
"entities": [
"dist/**/*.entity{.ts,.js}"
],
"synchronize": true
...
I am using "typeorm": "^0.2.45" in my package.json file.
Key columns "organizationId" and "id" are of incompatible types: character varying and uuid.
How can I create an FK relationship between users & organizations?
So from your question I understood is you want a "organizationId" field in your users table which will be a FK.
To create OnetoMany Relation between Organization and users do as below:
// organization.entity.ts
#Entity({ name: 'organizations' })
export class Organization {
#PrimaryGeneratedColumn('uuid')
id: string;
...
#OneToMany(() => User, (user) => user.organization)
users: User[];
}
// user.entity.ts
#Entity({ name: 'users' })
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ type: 'uuid' })
organizationId: string;
...
#ManyToOne(() => Organization, (organization) => organization.users)
#JoinColumn({ name: 'organizationId' })
organization: Organization;
}

TypeORM PostgreSQL #ManyToOne save violates not-null constraint

I have a basic nestjs app with 3 entities :
document: DocumentEntity has pages: PageEntity[] as #OneToMany relation
page: PageEntity has words: WordEntity[]as #OneToMany relation + #ManyToOne document
word: WordEntity has a #ManyToOne page: PageEntity
This is quite straightforward and the first part of those relations works as expected :
I can save new pages doing so :
async createPage(documentId: number, url: string) {
const document = await this.documentRepository.findOne({ id: documentId });
if (document) {
const pageEntity = new PageEntity();
pageEntity.imgUrl = url;
pageEntity.document = document;
await this.pageRepository.save(pageEntity);
}
}
but when I try to apply the same logic to the words/page relation, it fails. I m not sure why this behaves differently
async postWord(pageId: number, word: { text: string }) {
const page = await this.pageRepository.findOne({ id: pageId });
if (page) {
const wordEntity = new WordEntity();
wordEntity.text = word.text;
wordEntity.page = page;
await this.wordRepository.save(wordEntity);
}
}
Error Message :
[ExceptionsHandler] null value in column "pageId" of relation "word_entity" violates not-null constraint +107723ms
QueryFailedError: null value in column "pageId" of relation "word_entity" violates not-null constraint
here are the entities declarations :
// document.entity.ts
#Entity()
class DocumentEntity {
#PrimaryGeneratedColumn()
public id?: number;
#Column()
public name: string;
#Column()
public locale: string;
#Column()
public pdfUrl: string;
#Column()
public folderPath: string;
#OneToMany(() => PageEntity, (page) => page.document, {
primary: true,
eager: true,
cascade: true,
})
public pages?: PageEntity[];
}
export default DocumentEntity;
// page.entity.ts
#Entity()
class PageEntity {
#PrimaryGeneratedColumn()
public id?: number;
#Column({ nullable: true })
public pageNumber?: number;
#Column({ nullable: true })
public text?: string;
#Column()
public imgUrl: string;
#OneToMany(() => WordEntity, (word) => word.page, {
eager: true,
onDelete: 'CASCADE',
primary: true,
})
words?: WordEntity[];
#ManyToOne(() => DocumentEntity, {
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn()
public document: DocumentEntity;
}
export default PageEntity;
// word.entity.ts
#Entity()
class WordEntity {
#PrimaryGeneratedColumn()
public id?: number;
#ManyToOne(() => PageEntity, {
nullable: true,
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn()
public page!: PageEntity;
#Column()
public text: string;
#Column({ type: 'decimal', nullable: true })
public confidence?: number;
}
Try this:
#Entity()
class WordEntity {
.....
#ManyToOne(() => PageEntity, {
nullable: true,
primary: true,
onDelete: 'CASCADE',
})
#JoinColumn({
name: 'pageId',
referencedColumnName: 'id',
})
page?: PageEntity;
#Column({ nullable: true })
pageId?: number
.....
}
async postWord(pageId: number, word: { text: string }) {
const wordEntity = new WordEntity();
wordEntity.text = word.text;
// check if the pageId exists, maybe inside Dto with a decorator
wordEntity.pageId = pageId;
await this.wordRepository.save(wordEntity);
}
You need to remove the primary: true from your #ManyToOne relation because a primary relation cannot be set as NULL

How can I use the point type coordinate information of postgis in graphql using mutations?

I created a mutation to insert new data into the postgresql called location. The column coordinate must receive and store data, for example, ST_GeomFromGeoJSON ('{ "type": "Point", "coordinates": [-48.23456,20.12345]}').
However, graphql is not working, so I don't know where to modify it. I think it's because the scalar called GeoJSONPoint that I made is not working properly. Could you tell me how to create a scalar if graphql puts the data above?
GeoJSONPoint Scalar
import { GraphQLScalarType, Kind } from 'graphql';
export const GeoJSONPoint = new GraphQLScalarType({
name: 'GeoJSONPoint',
description: 'Geometry scalar type',
parseValue(value) {
return value;
},
serialize(value) {
return value;
},
parseLiteral(ast) {
if (ast.kind === Kind.OBJECT) {
console.log(ast);
return new Object(ast);
}
return null;
}
});
location.entity
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';
import { GeoJSONPoint } from 'src/scalar/geoJSONPoint.scalar';
#ObjectType()
#Entity('location')
export class Location {
#Field(() => ID)
#PrimaryGeneratedColumn('increment')
id: number;
#Field(() => String)
#Column({ type: 'varchar' })
name: string;
#Field(() => GeoJSONPoint)
#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;
}
resolver
#Mutation(() => Location)
async createLocation(
#Args('data') data: LocationDataInput
): Promise<Location> {
console.log(data);
return await this.locationService.setLocation(data);
}
I solved this problem. First of all, we divided the values entered by parseLiteral in scalar into
{type: '', coordinates: []}
and removed the foreign key column.

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.