Use ObjectId from MongoDB with mysql - typeorm

Is their any possibility to use mongoDB ObjectID system with MySql and typeORM instead of using an incremental ID?
The goal would be to define my entity like this:
#Entity()
export class RolePermission implements IRolePermission {
#ObjectIdColumn() id?: ObjectID;
#Column()
#IsNotEmpty()
roleId: ObjectID;
#Column()
#IsNotEmpty()
permissionId: ObjectID;
}
My entities could therefore have an ID without even being persisted. The ObjectId system would prevent collisions on the unique constraint I'd like to use for this column.
If a system like that can be implemented, is their any performance downside? I remember implementing such a system with PHP, and at the time, I had read this response that made me think that it was ok: Is there a REAL performance difference between INT and VARCHAR primary keys?

It's in fact really simple. You just need to use the ObjectID object from the mongodb package and declare your entities like you would do usually.
First, install mongodb dependencies:
yarn add mongodb
yarn add #types/mongodb
Then, declare your entity. Here an example with a working relationship between a user and an article:
user.entity.ts:
import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm';
import { Article } from './article.entity';
import { ObjectID } from 'mongodb';
#Entity()
export class User {
constructor() {
this.id = (new ObjectID()).toString();
}
#PrimaryColumn()
id: string;
#Column({ length: 500 })
username: string = null;
#OneToMany(type => Article, article => article.user)
articles: Article[];
}
article.entity.ts:
import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm';
import { User } from './user.entity';
import { ObjectID } from 'mongodb';
#Entity()
export class Article {
constructor() {
this.id = (new ObjectID()).toString();
}
#PrimaryColumn()
id: string;
#Column({ length: 500 })
title: string = null;
#ManyToOne(type => User, user => user.articles, {nullable: true})
user: User;
}
And use it as you would normally do:
const user = new User();
user.username = 'email#adress.com';
const article = new Article();
article.title = 'Mon titre';
article.user = user;
await this.userRepository.save(user);
await this.articleRepository.save(article);

Related

Update parent entity when updating children on cascade

I am looking for a way to update parents entities when updating children entity using cascade on a OneToMany relationship.
Entities
#Entity()
export class Activity {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
createAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#OneToMany(
type => ActivityTranslation,
activity_translation => activity_translation.activity,
{
cascade: true
}
)
activity_translations: ActivityTranslation[]
}
#Entity()
export class ActivityTranslation {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#CreateDateColumn()
createAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#ManyToOne(
type => Activity,
activity => activity.activity_translations,
{
onUpdate: 'CASCADE'
}
)
activity: Activity
}
Repository
public async updateActivityTranslation(activity_translation: ActivityTranslationModel): Promise<ActivityTranslationModel> {
try {
delete activity_translation.updatedAt;
return this.repository.save(activity_translation);
} catch {
throw new ServerError("Unable to update activity translation")
}
}
Controller
await this.activityTranslationRepository.updateActivityTranslation(activity_translations[i]);
The activity_translation is updated as expected.
I would like to update the updatedAt column of activity entity when saving activity_translation entity.
I tried everything I found about cascade issues on GitHub and StackOverflow.
Thank you for your help.
I know it is not a solution.... but i was able to do it by forcing the parent entity to change by updating one field. For example, create a field being the number of elements related and update it in the updating method.

Error: Object metadata not found after adding many-to-many relationships

I need to have additional properties for many-to-many relationships, I created a new entity myself as shown in the documentation.
https://github.com/typeorm/typeorm/blob/master/docs/many-to-many-relations.md#many-to-many-relations-with-custom-properties
It's user entity.
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { ShareTask } from './../../share-taks/entity/share-taks.entity';
#Entity('users')
export class User {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'varchar', nullable: false })
public email: string;
#Column({ type: 'varchar', nullable: false })
public password: string;
#OneToMany(() => ShareTask, (shareTask: ShareTask) => shareTask.user)
public shareTask: ShareTask[];
}
It's task entity.
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { ShareTaks } from './../../share-taks/entity/share-taks.entity';
#Entity('tasks')
export class Taks {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'varchar', nullable: false, length: 50 })
public title: string;
#OneToMany(() => ShareTaks, (shareTaks: ShareTaks) => shareTaks.taks)
public shareTaks: ShareTaks[];
}
It's ormconfig.json.
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "7047",
"database": "db",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
It's ShareTaks entity.
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './../../users/entity/user.entity';
import { Taks } from './../../taks/entity/taks.entity';
#Entity('shareTasks')
export class shareTask {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'integer', nullable: false })
public taskId: number;
#Column({ type: 'integer', nullable: false })
public userId: number;
#ManyToOne(() => (task: Task) => task.shareTask, { cascade: ['insert', 'update', 'remove'] })
public task: Task;
#ManyToOne(() => (user: User) => user.shareTask, { cascade: ['insert', 'update', 'remove'] })
public user: User;
}
Before I made a many-to-many relationship, everything worked well. When I added a many-to-many relationship, I get an error.
Error: Entity metadata for ShareTask#task was not found. Check if you specified a correct entity object and if it's connected in the connection options.
Because of what I get an error, I can’t understand.
I found some answers to my question, but they did not help me.
Entity metadata for Role#users was not found
https://github.com/typeorm/typeorm/issues/420
Likely, the error is caused by the path you provided in the entities in ormconfig file. Try changing it to a relative path from the current directory, something like
"entities": [__dirname + "/../**/*.entity{.ts,.js}"]
I resolved this by exporting from an index, and then importing from that index in both my User and Role file. When I was importing directly from the class files I was getting the this error.
Error during server startup TypeORMError: Entity metadata for User#role_id was not found. Check if you specified a correct entity object and if it's connected in the connection options.
My guess is that the strict equals (===) is colliding with a cache and thinking that the different relative import paths refer to different classes.
src/entities/index.ts:
import Team from "./Team";
import Role from "./Role";
import User from "./User";
export { Team, Role, User };
DataService.ts (where the datasources are defined)
import { Role, Team, User } from "../entities";
...
entities: [Role, Team],
...
src/entities/User.ts
import { Team, Role } from "../entities";
...
#Column()
#IsNotEmpty()
#ManyToOne(() => Role, (role) => role.id)
role_id: number;

How to fix 'TypeError: relatedEntities.forEach is not a function' from typeorm

I'm setting up a server using TypeORM + PostgreSQL. When saving saving my entity to the entity's repository, I receive the error: TypeError: relatedEntities.forEach is not a function and the entity is not saved to the database.
This seems to only happen when I am using the #OneToMany or #TreeChildren decorators.
Here is my entity class that is causing the problem:
import { ServiceData } from './service-data.entity';
import { ManufacturerData } from './manufacturer-data.entity';
import { Entity, Column, PrimaryGeneratedColumn, TreeChildren } from 'typeorm';
#Entity()
export class Advertisement {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true })
name?: string;
#Column()
gatewayId: string;
#Column()
rssi: number;
#Column({ nullable: true })
mac?: string;
#TreeChildren()
manufacturerData?: ManufacturerData[];
#TreeChildren()
serviceData?: ServiceData;
}
The (abbreviated) error output is:
UnhandledPromiseRejectionWarning: TypeError: relatedEntities.forEach is not a function
at OneToManySubjectBuilder.buildForSubjectRelation (/<project-directory>/src/persistence/subject-builder/OneToManySubjectBuilder.ts:78:25)
Found the problem.
#TreeChildren and #OneToMany always expect an array. I had to change serviceData?: ServiceData; to serviceData?: ServiceData[];
This error can come up when the oneTomany relationships are not properly written in the Entity design and also when you trying to store the relationships in a table.
The first One is, setting the Entity holding many of the other Entity like this
#OneToMany(() => Address, (address) => address.users)
address: Address[];
The other entity that has manyToOne relationship like this,
#ManyToOne(() => Users, (users) => users.address)
users: Users;
If the relationships are all good, then the problem is storing the entity relations to the database.
when creating the user, store the relationships like this
address = await this.addressRepository.findOneOrFail({
where: {
user_id: Number(userId),
},
});
const user = new User();
user.name = name;
user.age = age;
### store the address as an array of objects to the relationship column
users.address = [address]

How to query a entity based on an related entity property in TypeORM?

I have an Entity Transaction and an Entity Integration
#Entity()
export default class Transaction {
#PrimaryGeneratedColumn()
public id_trans?: number;
#OneToOne(type => Integration, i => i.transaction, { nullable: true })
public integration?: Integration;
}
and
#Entity()
export default class Integration {
#PrimaryGeneratedColumn()
public id_cust?: number;
#OneToOne(type => Transaction, t => t.integration)
#JoinColumn({ referencedColumnName: 'id_trans', name: 'int_id_module' })
public transaction?: Transaction;
}
I tried to query the Transaction by using a Integration property as filter.
const id_api = 10;
const transaction = await repository.find({
where: { integration: { int_id_api: id_api} },
relations: ['integration', 'customer'],
});
but it returns the entire table of Transactions, even if the integration.int_id_api is different from id_api property
What am I doing wrong. What should I do to get this query working ?
You need to add a primary key to all your entities. From the doc:
Each entity must have at least one primary key column. This is a
requirement and you can't avoid it. To make a column a primary key,
you need to use #PrimaryColumn decorator.
Assuming you update the Integration entity like so
#Entity()
export default class Integration {
#PrimaryColumn()
public id_inte: number;
#OneToOne(type => Transaction, t => t.integration)
#JoinColumn({ referencedColumnName: 'id_trans', name: 'int_id_module' })
public transaction?: Transaction;
}
You should be able to find transactions like so
const transaction = await repository.find({
where: { integration: { id_inte: some_id } },
relations: ['integration', 'customer'],
});

Is it possible to 'protect' a property and exclude it from select statements

I'd like to protect certain properties on the data-layer level. For example I'd like to protect the password hash I store in the database for a user, so that it doesn't show up in arbitrary select-statements.
This way only when it's explicitly requested in a select property, property2 statement.
I think a more accurate answer would be to set select: false on column options:
#Column({ select: false })
password: string;
And explicitly select the column like this:
const user = await getRepository(User)
.createQueryBuilder()
.addSelect('password')
.getOne()
TypeORM goes well with routing-controllers so you should use it, behind the scenes it uses class-transformer to serialize and deserialize your data. So you can use the #Exclude decorator from that library to prevent certain properties being sent down to the clients.
It also uses the class-validator library to validate the data when specifying it as the type in the controller functions. These are powerful toys. Here is a small example of how you can leverage both:
import { Entity, Column, PrimaryGeneratedColumn, Index, OneToMany } from "typeorm";
import { Exclude, Expose } from "class-transformer";
import { IsNotEmpty, IsEmail, MinLength, MaxLength, Min, Max, IsNumber, IsString } from "class-validator";
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#IsNotEmpty()
#IsEmail()
#Index({ unique: true })
email: string;
#Exclude()
#Column()
passwordHash: string;
#Column()
#IsNotEmpty()
#IsString()
firstName: string;
#Column()
#IsNotEmpty()
#IsString()
lastName: string;
#Column({ type: 'integer', default: Gender.NotSpecified })
#IsNumber()
#Min(1)
#Max(3)
gender: Gender;
#Expose()
get admin() {
return this.role == Role.Admin;
}
#Expose()
get stylist() {
return this.role == Role.Stylist;
}
}
If you use an another server-side library you can still take advantage of class-transformer and class-validator. You just need to call the validate function manually in your routes, for example for restify you can write:
import {validate } from "class-validator";
import {plainToClass} from "class-transformer";
// ... more code
server.post('/hello', function create(req, res, next) {
let bodyJSON = parseBodyTheWayYouWant(req.body);
let post = plainToClass(bodyJSON);
validate(post)
return next();
});
You can use delete
Exemple Find All users
async findUsers(){
const users:User[] = await userRepository.find();
return users.map(user => {
delete user.password;
delete user.salt;
return user;
}) ;
}
Exemple Find User By Id
async findUserById(id){
const user:User = await userRepository.findOne(id);
delete user.password;
return user;
}
Here is solution - https://github.com/typeorm/typeorm/issues/535
The simplest solution is to exclude field(s) during query.

Resources