I need to create a relationship with NestJs, GraphQL and prisma - nestjs

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}})
}
}

Related

typescript nestjs Mongoose query, find() object inside an object

In my products schema, the product information is kept in a separate object. In the code below, I am trying to fetch all the products with the trademark Apple, but the code does not work.
async getProductWithStore(productInfoDto: ProductInfoDto) {
try {
const product = await this.productModel
.find({productInfo: productInfoDto})
.populate('store')
return product;
} catch (err) {
return err;
}
}
Here is the request
productInfo.dto.ts
import { IsNotEmpty, IsOptional } from "class-validator";
export class ProductInfoDto {
#IsNotEmpty()
trademark: string;
#IsOptional()
releaseYear: string;
#IsOptional()
model: string;
#IsOptional()
size: string;
#IsOptional()
gender: string;
#IsOptional()
ram: string;
#IsOptional()
screenSize: string;
#IsOptional()
storage: string;
}
product.schema.ts
import { Document, Types } from 'mongoose';
import {Prop, Schema, SchemaFactory} from '#nestjs/mongoose';
import {Store} from '../../store/schemas/store.schema';
import { Category } from 'src/category/schemas/category.schema';
import { ProductInfoDto } from '../dto/product-info.dto';
export type ProductDocument = Product & Document;
#Schema({timestamps:true})
export class Product {
#Prop({required: true})
productName: string;
#Prop({required: true})
description: string;
#Prop({required:true})
stock: number;
#Prop({required:true, type: Types.ObjectId, ref:'Category'})
category: Category;
#Prop({required:true, type:Object})
productInfo: ProductInfoDto
#Prop({required:true})
image:string;
#Prop({required:true, type: Types.ObjectId, ref:'Store'})
store: Store;
#Prop()
sales: number;
#Prop()
rating: number;
#Prop([String])
comments: string[]
}
export const ProductSchema = SchemaFactory.createForClass(Product);
I tried a lot of things from manually typing the information into the code but none of them worked
I found the solution
I replaced the .find code with the code below
async getProductWithStore(productInfoDto: ProductInfoDto) {
try {
const product = await this.productModel
.find({'productInfo.trademark':"Apple"})
return product;
}catch(err){
return err
}
}

NestJS - TypeORM save() not updating document in the database

I'm in the process of learning NestJS and TypeORM with a simple project where users can book offices. I have defined the office and bookings types and entities, and written a function that searches for an existing office and adds a booking subdocument to its bookings array. Save() returns the office document with the updated bookings field, but for some reason the database is not updated.
The office type looks like this:
import { Field, ID, ObjectType } from '#nestjs/graphql';
import { BookingDto } from '../dto/booking.dto';
#ObjectType()
export class OfficeType {
#Field(() => ID)
id: string;
#Field()
title: string;
#Field()
description: string;
#Field(() => [BookingDto], { nullable: true })
bookings: string[];
#Field()
price: number;
#Field()
owner: string;
}
The booking type inside the 'bookings' field:
import { Field, ObjectType } from '#nestjs/graphql';
import { IsString, IsUUID } from 'class-validator';
#ObjectType()
export class BookingDto {
#IsUUID()
#Field()
id: string;
#IsUUID()
#Field()
officeId: string;
#IsUUID()
#Field()
userId: string;
#IsString()
#Field()
date_from: string;
#IsString()
#Field()
date_until: string;
}
And the function for booking an office:
async bookOffice(bookingInput: BookingInput): Promise<Office> {
const { officeId, userId, date_from, date_until } = bookingInput;
const booking = { id: uuid(), officeId, userId, date_from, date_until };
const office = await this.officeRepository.findOne({
id: officeId,
});
if (!office) {
throw new NotFoundException(`Office not found`);
}
office.bookings
? office.bookings.push(booking)
: (office.bookings = [booking]);
return await this.officeRepository.save(office);
}
According to the TypeORM docs, save() can be used to update documents, so I'm not sure what the problem is here.
You have to save first the booking object and then save the office with the bookings.
booking = await this.bookingRepository.save(booking);
office.bookings ? office.bookings.push(booking)
: (office.bookings = [booking]);
this.officeRepository.save(office);

Exclude() method is not working for post method in Nestjs

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

Nestjs - invalid input syntax for type integer: "don#gmail.com"

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

How to define ObjectId in DTO & what is the right query to get relational data in NestJS Mongoose?

I'm setting up an app using NestJS, Angular, GraphQL and MongoDB and new to these stacks
I have 2 collections called Tasks & Statuses with sample data
Tasks
[{
"_id": {
"$oid": "5f9138f71163a739c43fc9b3"
},
"TaskDesc": "Meeting with Brian",
"StartedDate": "2020-10-22T07:42:40Z",
"EndDate": "2020-10-22T10:42:40Z",
"StatusId": "5f91375d1163a739c43fc9af"
}]
Statuses
[{
"_id": {
"$oid": "5f91375d1163a739c43fc9af"
},
"StatusDesc": "Done"
}]
Here are schemas defined in NestJS
import { Schema, Types } from 'mongoose';
import { MongooseModule } from '#nestjs/mongoose';
export const TaskSchema = new Schema({
TaskDesc: String,
StartedDate: String,
EndDate: String,
StatusId:
{
type: Types.ObjectId,
ref: 'Statuses'
}
});
export const StatusSchema = new Schema({
StatusDesc: String
});
export const SchemaGroups = MongooseModule.forFeature([
{name: 'Tasks', schema: TaskSchema, collection: 'Tasks'},
{name: 'Statuses', schema: StatusSchema, collection: 'Statuses'}
]);
The DTO
import { ObjectType, Field, ID } from '#nestjs/graphql';
#ObjectType()
export class TasksDTO {
#Field(() => ID)
id?: string;
#Field()
TaskDesc: string;
#Field()
StartedDate: string;
#Field()
EndDate: string;
#Field()
StatusId: string;
}
#ObjectType()
export class StatusDTO {
#Field(() => ID)
readonly id?: string;
#Field()
readonly StatusDesc: string;
}
The model
import { Document, Schema } from 'mongoose';
export interface Tasks extends Document {
readonly TaskDesc : string,
readonly StartedDate: string,
readonly EndDate: string,
readonly StatusId: Schema.Types.ObjectId
}
export interface Status extends Document {
readonly StatusDesc : string
}
The resolver
#Resolver('Tasks')
export class ListTodoResolver {
constructor(private readonly todoItemsService: TodolistService){
}
#Query(() => [TasksDTO])
async Tasks(): Promise<TasksDTO[]> {
return await this.todoItemsService.getAllTasks();
}
#Query(() => [StatusDTO])
async Statuses(): Promise<StatusDTO[]>{
return await this.todoItemsService.getAllStatuses();
}
}
The service
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
#Injectable()
export class TodolistService {
constructor(
#InjectModel('Tasks') private readonly todoItemsModel: Model<Tasks>,
#InjectModel('Statuses') private readonly statusItemsModel: Model<Status>
) { }
async getAllTasks() : Promise<Tasks[]>{
let tasks = await this.todoItemsModel.find().exec(); // how should we query in the service to get relational data from another collection?
console.log(tasks);
return tasks;
}
async getAllStatuses() : Promise<Status[]>{
return await this.statusItemsModel.find().exec();
}
}
And I had these errors
Type 'Tasks' is not assignable to type 'TasksDTO'. Types of property 'StatusId' are incompatible. Type 'ObjectId' is not assignable to type 'string'.
How do we define the ObjectId type in the DTO ?
My expected output for getAllTasks() method would be
[{
"_id": "5f9138f71163a739c43fc9b3",
"TaskDesc": "Meeting with Brian",
"StartedDate": "2020-10-22T07:42:40Z",
"EndDate": "2020-10-22T10:42:40Z",
"StatusDesc": "Done"
}]
The error is already show where you should fix it.
Type 'Tasks' is not assignable to type 'TasksDTO'. Types of property 'StatusId' are incompatible. Type 'ObjectId' is not assignable to type 'string'.
Which mean your DTO should be
#ObjectType()
export class TasksDTO {
#Field(() => ID)
id?: string;
#Field()
TaskDesc: string;
#Field()
StartedDate: string;
#Field()
EndDate: string;
#Field()
StatusId: objectId; // change here
}
Not sure the object Id type in your DTO, try to look up in the documentation to see if there are Object ID type for the DTO if not you should change StatusId for all place to string and that should work

Resources