I have tried adding toJSON(){return classtoPlain(this)} in my entity still its not working.
Here is my controller
#Controller('users')
#UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
#HttpCode(200)
#Post()
async create(#Body() user: User): Promise<User> {
return await this.usersService.create(user).catch((err) => {
throw new HttpException({ message: err.message }, HttpStatus.BAD_REQUEST);
});
}
Here is my Entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Gender } from '../Constants/enum';
import { Exclude, instanceToPlain } from 'class-transformer';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
#Column()
#Exclude({ toPlainOnly: true })
password: string;
constructor(partial: Partial<User>) {
Object.assign(this, partial);
}
toJSON() {
return instanceToPlain(this);
}
}
Here is my service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private readonly user_detailsRepository: Repository<User>,
) {}
create(user: User) {
return this.user_detailsRepository.save(user);
}
I have tried many solutions but still nothing works.
For GET its working but for post its showing the password field.If any one can provide any solution it would be great.
You're mixing between Entity definition/validation, which typeORM functions/decorators (in your case) should handle. And DTO definition, which class-transformer/validator should handle
So you should define a createUserDTO.ts file like below, and use it as in the controller/service file:
export class createUserDTO {
#IsNotEmpty()
#IsString()
firstName: string;
#IsNotEmpty()
#IsString()
lastName?: string;
#IsNotEmpty()
#IsEnum(Gender)
gender: Gender;
#IsNotEmpty()
email: string;
#Exclude({ toPlainOnly: true })
password: string;
}
The entity file shouldn't have the password column (just omit the field completely), should look like this:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
}
Then, when you call return this.user_detailsRepository.save(userDto);, it should work fine
Related
I have 5 entities: User, Todos, Project, Section and Label. Relationships happen as follows:
Each User is associated with multiples Todo.
Each Todo is associated with a single User, Project and Section, and is associated with multiples Label.
Each Project is associated with a single User.
Each Section is associated with a single Project.
Each Label is associated with a single Todo.
The migration code to the #OneToOne relationship is done using the createForeignKey method, as done for project_id column at Section entity:
import {
MigrationInterface,
QueryRunner,
TableColumn,
TableForeignKey,
} from 'typeorm';
export class AddProjectIdToSections1669830961233 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'sections',
new TableColumn({
name: 'project_id',
type: 'uuid',
isNullable: true,
})
);
await queryRunner.createForeignKey(
'sections',
new TableForeignKey({
name: 'SectionsProject',
columnNames: ['project_id'],
referencedTableName: 'projects',
referencedColumnNames: ['id'],
onDelete: 'SET NULL',
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropForeignKey('sections', 'SectionsProject');
await queryRunner.dropColumn('sections', 'project_id');
}
}
However, how do you use createForeignkey for relations of #OneToMany and #ManyToOne, and especially when the two happen round trip?
Entities code:
// User.entity.ts
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Todo } from './Todo.entity';
#Entity('users')
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
#Column()
email: string;
#Column()
password: string;
#Column({ nullable: true })
photoURL: string;
#Column()
language: string;
#CreateDateColumn()
created_at: Date;
#OneToMany(() => Todo, (todo) => todo.user)
todos: Todo[];
}
// Todo.entity.ts
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { IProject } from '../types';
import { Label } from './Label.entity';
import { Project } from './Project.entity';
import { Section } from './Section.entity';
import { User } from './User.entity';
#Entity('todos')
export class Todo {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
title: string;
#Column()
description?: string;
#Column()
type: string;
#Column({ type: 'timestamptz' })
date: Date;
#Column()
priority: number;
#Column()
isCompleted: boolean;
#OneToOne(() => Project)
#JoinColumn({ name: 'project_id' })
project: IProject;
#OneToOne(() => Section)
#JoinColumn({ name: 'section_id' })
section?: Section;
#OneToMany(() => Label, (label) => label.todo, {
cascade: true,
})
labels: Label[];
#ManyToOne(() => User, (user) => user.todos)
#JoinColumn({ name: 'user_id' })
user: User;
}
// Project.entity.ts
import {
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from './User.entity';
#Entity('projects')
export class Project {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
type: string;
#Column()
title: string;
#Column()
colorName: string;
#Column()
class: string;
#OneToOne(() => User)
#JoinColumn({ name: 'user_id' })
user: User;
}
// Section.entity.ts
import {
Column,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Project } from './Project.entity';
#Entity('sections')
export class Section {
#PrimaryGeneratedColumn('uuid')
readonly id: string;
#Column()
index: number;
#Column()
type: string;
#Column()
title: string;
#Column({ type: 'timestamptz' })
readonly date: Date;
#OneToOne(() => Project)
#JoinColumn({ name: 'project_id' })
project: Project;
}
// label.entity.ts
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Todo } from './Todo.entity';
#Entity('labels')
export class Label {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
type: string;
#Column()
title: string;
#Column()
colorName: string;
#Column()
class: string;
#ManyToOne(() => Todo, (todo) => todo.labels)
#JoinColumn({ name: 'todo_id' })
todo: Todo;
}
I need to create one to many relationship between transaction and the user.
so the user can have multiple transactions, but the transaction can only have one user. I made the relationship in prisma, but the thing is how do I achieve this in nestjs with prisma that is the problem I'm getting. I added to the code section so I'll appreciate any help thanks.
schema.prisma :
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Crowdfund {
id String #id #default(uuid())
title String
description String
location String
target Int
image String?
}
model Institution {
institutionId String #id #default(uuid())
institutionName String
phone String
email String
location String
}
model User {
id String #id #default(uuid())
userName String
userPhone String?
password String
email String #unique
location String?
transactions Transaction[]
}
model Transaction {
id String #id #default(uuid())
amount Int
createdAt DateTime #default(now())
userId String
user User #relation(fields: [userId], references: [id])
}
create-dto from transaction :
import { InputType, Int, Field } from '#nestjs/graphql';
#InputType()
export class CreateTransactionInput {
#Field(() => Int)
amount: number;
#Field({nullable: true})
createdAt?: Date;
#Field()
userId: string;
}
entites from transaction :
import { ObjectType, Field, Int} from '#nestjs/graphql';
import { User } from 'src/user/entities/user.entity';
#ObjectType()
export class Transaction {
#Field()
id: string;
#Field(() => Int)
amount: number;
#Field()
createdAt: Date;
#Field(() => User)
user: User;
#Field()
userId: string;
}
transaction.service.ts :
import { Injectable } from '#nestjs/common';
import { PrismaService } from 'prisma/prisma.service';
import { CreateTransactionInput } from './dto/create-transaction.input';
import { UpdateTransactionInput } from './dto/update-transaction.input';
#Injectable()
export class TransactionService {
constructor(
private prismService: PrismaService
) {}
create(createTransactionInput: CreateTransactionInput) {
return this.prismService.transaction.create({data: createTransactionInput})
}
findAll() {
return this.prismService.transaction.findMany();
}
findOne(id: string) {
return this.prismService.transaction.findUnique({where: {id: id}})
}
update(id: string, updateTransactionInput: UpdateTransactionInput) {
return this.prismService.transaction.update({data: updateTransactionInput, where: {id: id}})
}
remove(id: string) {
return this.prismService.transaction.delete({where: {id: id}})
}
}
create-dto from user:
import { InputType, Int, Field } from '#nestjs/graphql';
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
#InputType()
export class CreateUserInput {
#IsString()
#IsNotEmpty()
#MinLength(3)
#Field()
userName: string;
#IsString()
#IsNotEmpty()
#Field({nullable: true})
userPhone?: string;
#IsString()
#IsNotEmpty()
#MinLength(3)
#Field()
password: string
#IsString()
#IsNotEmpty()
#MinLength(3)
#IsEmail()
#Field()
email: string;
#IsString()
#IsNotEmpty()
#Field({nullable: true})
location?: string;
}
entities from user
import { ObjectType, Field, Int } from '#nestjs/graphql';
import { Transaction } from '../../transaction/entities/transaction.entity';
#ObjectType()
export class User {
#Field()
id: string;
#Field()
userName: string;
#Field({nullable: true})
userPhone?: string;
#Field()
email: string;
#Field({nullable: true})
location?: string;
#Field(() => [Transaction], {nullable: true})
transaction: Transaction
}
user.service.ts :
import { Injectable } from '#nestjs/common';
import { PrismaService } from 'prisma/prisma.service';
import { CreateUserInput } from './dto/create-user.input';
import { UpdateUserInput } from './dto/update-user.input';
#Injectable()
export class UserService {
constructor(
private prismService: PrismaService
){}
create(createUserInput: CreateUserInput) {
return this.prismService.user.create({data: createUserInput});
}
findAll() {
return this.prismService.user.findMany({include: {transactions: true}});
}
findOne(id: string) {
return this.prismService.user.findUnique({where: {id: id}})
}
update(id: string, updateUserInput: UpdateUserInput) {
return this.prismService.user.update({data: updateUserInput, where: {id: id}})
}
remove(id: string) {
return this.prismService.user.delete({where: {id: id}})
}
}
While creating the sign in controller and service i encountered the error mentioned in the title.
Users table
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', { length: 50 })
name;
#Column('varchar', { length: 50 })
surname;
#Column('varchar', { length: 50 })
street;
#Column('varchar', { length: 50 })
city;
#Column('varchar', { length: 5 })
zip;
#Column({ type: 'int', nullable: true })
rating;
#Column('varchar', { length: 10 })
phone;
#Column('date')
date;
#Column({ type: 'varchar', length: 50, nullable: false, unique: true })
email;
#Column({ type: 'varchar', length: 75, nullable: false })
password;
}
Sign in DTO
export class SignInDto {
#IsNotEmpty()
email: string;
#IsNotEmpty()
password: string;
}
Now, validation works when testing this in postman; if i enter just the email and leave out the password a proper error will show up.
Signing up on the other hand works just fine with this DTO:
export class SignUpDto {
#IsString()
#IsNotEmpty()
name: string;
#IsString()
#IsNotEmpty()
surname: string;
#IsString()
#IsNotEmpty()
street: string;
#IsString()
#IsNotEmpty()
city: string;
#MinLength(5)
#MaxLength(5)
zip: string;
rating: number;
#IsString()
#IsNotEmpty()
phone: string;
#IsDate()
#Type(() => Date)
#IsNotEmpty()
date: Date;
#IsEmail()
#IsNotEmpty()
email: string;
#IsString()
#MinLength(8)
#MaxLength(32)
#Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/)
password: string;
}
Users repository
import {
ConflictException,
InternalServerErrorException,
} from '#nestjs/common';
import { EntityRepository, Repository } from 'typeorm';
import { SignUpDto } from './dto/signup.dto';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';
#EntityRepository(User)
export class UsersRepository extends Repository<User> {
async createUser(signUpDto: SignUpDto): Promise<void> {
const {
name,
surname,
street,
city,
zip,
rating,
phone,
date,
email,
password,
} = signUpDto;
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(password, salt);
const user = this.create({
name,
surname,
street,
city,
zip,
rating,
phone,
date,
email,
password: hashedPassword,
});
try {
await this.save(user);
} catch (error) {
if (error.code === '23505') {
throw new ConflictException('User with the same e-mail already exists');
} else {
console.log(error);
throw new InternalServerErrorException();
}
}
}
}
Signup and Signin controller
import { Body, Controller, Post } from '#nestjs/common';
import { AuthService } from './auth.service';
import { SignInDto } from './dto/signin.dto';
import { SignUpDto } from './dto/signup.dto';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('/signup')
signUp(#Body() signUpDto: SignUpDto): Promise<void> {
return this.authService.signUp(signUpDto);
}
#Post('/signin')
signIn(#Body() signInDto: SignInDto): Promise<string> {
return this.authService.signIn(signInDto);
}
}
Service where signup and signin logic is written
#Injectable()
export class AuthService {
constructor(
#InjectRepository(UsersRepository)
private usersRepository: UsersRepository,
) {}
async signUp(signUpDto: SignUpDto): Promise<void> {
return this.usersRepository.createUser(signUpDto);
}
async signIn(signInDto: SignInDto): Promise<string> {
const { email, password } = signInDto;
const user = await this.usersRepository.findOne(email);
if (user && (await bcrypt.compare(password, user.password))) {
return 'success';
} else {
throw new UnauthorizedException('Check your login credentials');
}
}
}
Current database state
Error example
HTTP request example
Does anyone have any idea where to start with this? I mean, there is no way I defined the email as an integer?
TypeORM's findOne works based on ID if no specifier is given. You should be doing
async signIn(signInDto: SignInDto): Promise<string> {
const { email, password } = signInDto;
const user = await this.usersRepository.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
return 'success';
} else {
throw new UnauthorizedException('Check your login credentials');
}
}
instead to tell TypeORM you're searching on the email and not on the ID
When user registers, I want to him to send his account name, so I will have "Accounts" table with reference to the user entity. I'm using Nest.js.
I'm looking for alternative to the following logic in my users.service.ts Register method:
Find Account by Name
If Account not found, Create It
Create User with the Account found above
Here is my Account Entity:
#Entity()
export class Account extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
name: string;
#Column()
#CreateDateColumn()
createdAt: Date;
}
And my User Entity:
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#ManyToOne(() => Account, (Account) => Account.name, { cascade:true })
#JoinColumn({name: 'name'})
account: Account;
}
My CreateUserDTO:
export class CreateUserDto {
#IsEmail()
email: string;
#IsNotEmpty()
password: string;
#IsNotEmpty()
account: string;
}
And this is the error when I try to do User.create(dto):
Type 'string' is not assignable to type 'Account | DeepPartial<Account>'.
In addition, for some reason, the User.create(dto) returns array of users and not single user, and I don't understand why.
You can proceed like this :
// create-user.dto.ts
export class CreateUserDto {
#IsEmail()
email: string;
#IsString()
#IsNotEmpty()
password: string;
#IsString()
#IsNotEmpty()
accountName: string;
}
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#Column()
name: string; // This is your relations between user and account defined in JoinColumn decorator
#ManyToOne(() => Account, (Account) => Account.name, { cascade: true })
#JoinColumn({ name: 'name' })
account: Account;
}
// user.service.ts
// user.service.ts
#Injectable()
export class UserService {
constructor(
#InjectRepository(Account)
private readonly accountRepository: Repository<Account>, // Or your custom repository
#InjectRepository(User)
private readonly userRepository: Repository<User> // Or your custom repository
) {}
public async register(dto: CreateUserDto): Promise<void> {
let accountToSaveWithUser: Account;
// First check if account exist
const account = await this.accountRepository.findOne({
where: {
name: dto.accountName,
},
});
if (isNil(account)) {
const accountToSave = this.accountRepository.create({
name: dto.accountName,
});
accountToSaveWithUser = await this.accountRepository.save(accountToSave);
} else {
accountToSaveWithUser = account;
}
await this.userRepository.save({
email: dto.email,
password: hash(dto.password), // Use your package for hash password
name: accountToSaveWithUser.name,
});
}
}
Can I use class-validator to validate Entity columns?
This doesn't validate the columns:
import { IsEmail } from 'class-validator';
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
#IsEmail()
email: string;
}
However when I use the class-validator anywhere else in the code other than entities it validates properly and doesn't allow for bad inputs.
This works:
#InputType()
export class RegisterInput {
#Field()
#IsEmail()
email: string;
}
The Entity should be clean
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
email: string;
}
While you define a DTO for checking the incoming request.
export class AdminDto {
#IsEmail()
email: string;
}
In your controller, you would check the incoming request with your AdminDto.
#Controlller('route')
export class AdminController {
constructor(private yourService: yourserviceClass)
#Post('/api')
async createSomething(#Body('email', ValidationPipe) email: string){ //Your request body //would be checked against your DTO
this.yourService.createMethod(email)
}
Hope this answers your question.