ERROR [ExceptionsHandler] Cannot query across many-to-many for property permissions - nestjs

I have a project that I have made the project with nestjs and now I have a problem in method's update and the relation is Many-To-Many when I call Put Api nest gives me below error Note: I have 2 entity Role and Permission that they have many-to-many relation together.
I have a update method in role-service that I commented on it that works well but I have made a abstract class and role-service extended it but the method update doesn't work and give me bellow error
request api => url/api/role/id Body => {name:"admin",permissions:[{"id":1},{"id":2}]}
ERROR [ExceptionsHandler] Cannot query across many-to-many for property permissions
-Role Entity
import { Permission } from 'src/permission/model/permission.entity';
import {
Column,
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
#Entity('roles')
export class Role {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany((_Type) => Permission, { cascade: true })
#JoinTable({
name: 'role_permissions',
joinColumn: { name: 'role_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'permission_id', referencedColumnName: 'id' },
})
permissions: Permission[];
}
Permission Entity
#Entity('permissions')
export class Permission {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
}
Role Controller
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
UseGuards,
} from '#nestjs/common';
import { AuthGuard } from 'src/auth/auth.guard';
import { RoleCreateDto } from './models/role-create.dto';
import { RoleUpdateDto } from './models/role-update.dto';
import { Role } from './models/role.entity';
import { RoleService } from './role.service';
#UseGuards(AuthGuard)
#Controller('roles')
export class RoleController {
constructor(private roleService: RoleService) {}
#Put(':id')
async update(#Param('id') id: number, #Body() body: RoleUpdateDto) {
await this.roleService.update(id,body);
return await this.roleService.findOne({ id });
}
}
Role service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { AbstractService } from 'src/common/abstract.service';
import { Repository } from 'typeorm';
import { RoleCreateDto } from './models/role-create.dto';
import { Role } from './models/role.entity';
#Injectable()
export class RoleService extends AbstractService {
constructor(
#InjectRepository(Role) private readonly roleRepository: Repository<Role>,
) {
super(roleRepository)
}
async findOne(condition): Promise<Role> {
return await this.roleRepository.findOne({ where: condition , relations:["permissions"]});
}
// async update(id: number, data:any): Promise<any> {
// console.log(id);
// const role = await this.findOne({id});
// console.log(role);
// role.name = data.name;
// role.permissions= data.permissions;
// const r = await this.roleRepository.preload(role)
// console.log("role",r);
// return await this.roleRepository.save(r);
// }
// async delete(id: number): Promise<any> {
// return await this.roleRepository.delete(id);
// }
}
abstract service
import { Injectable } from '#nestjs/common';
import { Repository } from 'typeorm';
import { PaginatedResult } from './pagibated-result.interface';
#Injectable()
export abstract class AbstractService {
protected constructor(protected readonly repository: Repository<any>) {}
async all(): Promise<any[]> {
return await this.repository.find();
}
async paginate(page = 1): Promise<PaginatedResult> {
const take = 1;
const [data, total] = await this.repository.findAndCount({
take,
skip: (page - 1) * take,
});
return {
data: data,
meta: {
total,
page,
last_page: Math.ceil(total / take),
},
};
}
async create(data): Promise<any> {
return await this.repository.save(data);
}
async findOne(condition): Promise<any> {
return await this.repository.findOne({ where: condition });
}
async update(id: number, data): Promise<any> {
return await this.repository.update({id},data);
}
async delete(id: number): Promise<any> {
return await this.repository.delete(id);
}
}

Related

NesteJS with TypeORM - hooks and listeners not working

Solution
With the solution posted below my create function player.service.ts now looks like this:
async create(createPlayerDto: CreatePlayerDto): Promise<Player> {
const newPlayer = this.playerRepository.create(createPlayerDto);
return await this.playerRepository.save(newPlayer);
}
The hook within my player.entity.ts:
#BeforeInsert()
async hashPassword() {
console.log('Hash password!');
this.password = await bcrypt.hash(this.password, this.saltOrRounds);
}
Problem
For my project with NestJS I have created a Player entity (player.entity.ts), which has the following columns and one hook. I have connected a MySQL8.0 database via the TypeORM package.
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
BeforeInsert,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
#Entity({ name: 'players' })
export class Player {
readonly saltOrRounds = 10;
#PrimaryGeneratedColumn()
id: number;
#Column({
type: 'varchar',
unique: true,
})
username: string;
#Column()
password: string;
#Column({
unique: true,
type: 'varchar',
})
email: string;
#CreateDateColumn({
name: 'created_at',
type: 'datetime',
})
created_at: 'datetime';
#UpdateDateColumn({
name: 'updated_at',
type: 'datetime',
})
updated_at: 'datetime';
#BeforeInsert()
async hashPassword() {
return (
this.password && (await bcrypt.hash(this.password, this.saltOrRounds))
);
}
}
As you can see the #BeforeInsert() hook should take the password, hash it and then return the hashed password.
The relevant route for the creation of a new player is placed within the players.controller.ts:
import { Body, Controller, Delete, Get, Param, Post } from '#nestjs/common';
import { PlayersService } from './players.service';
import { CreatePlayerDto } from './dto/create-player.dto';
import { Player } from './interfaces/player.interface';
#Controller('players')
export class PlayersController {
constructor(private playerService: PlayersService) {}
#Post()
async create(#Body() createPlayerDto: CreatePlayerDto) {
return this.playerService.create(createPlayerDto);
}
}
The controller utilizes the player.service.ts and makes uses the EntityManager to perform the create/insert operation:
import { Injectable } from '#nestjs/common';
import { InjectEntityManager, InjectRepository } from '#nestjs/typeorm';
import { Player } from './entities/player.entity';
import { EntityManager, Repository } from 'typeorm';
import { CreatePlayerDto } from './dto/create-player.dto';
#Injectable()
export class PlayersService {
constructor(
#InjectEntityManager()
private entityManager: EntityManager,
#InjectRepository(Player)
private playerRepository: Repository<Player>,
) {}
async create(createPlayerDto: CreatePlayerDto): Promise<Player> {
return this.entityManager.save(Player, createPlayerDto);
}
}
I've also tried to use the Repository with the same result. A new player is created everytime I make a POST request to the /create endpoint. But unfortunatelly none of the used hooks and/or listeners work.
Instantiate the Entity, assign the attributes to it and then save.
async create(attributes: DeepPartial<T>) {
const playerEntity = Object.assign(new Player(), attributes);
return this.repository.save(playerEntity);
}
or you could use the create method on the repository and then save it.
const record = playerRepository.create(attributes);
await playerRepository.save(record);

Is there a way to insert current user into fields in base entity before inserting data

How to to insert current logged in user to createdBy & lastChangedBy fields after creating/updating entity?
In my BaseEntity i've tried
#BeforeInsert()
async insertUser(#GetAuthUserPayload() userPayload: User) {
const user = await this.usersService.findOne({
where: { username: userPayload.username },
});
this.createdBy = user;
this.lastChangedBy = user;
}
But i've found out decorators work only in controllers(in entity they return undefined). Is there any other way than updating DTO in controller or using session?
Since i am using #nestjsx/crud i haven't found any other method than updating DTO. I've managed to solve this issue by creating BaseService:
import { TypeOrmCrudService } from '#nestjsx/crud-typeorm';
import { InjectRepository } from '#nestjs/typeorm';
import { Inject, Injectable, Scope, Type } from '#nestjs/common';
import { CrudRequest, Override } from '#nestjsx/crud';
import { DeepPartial } from 'typeorm';
import { REQUEST } from '#nestjs/core';
import { User } from '../users/entities/user.entity';
export interface IBaseService<T> {}
type Constructor<I> = new (...args: any[]) => I;
export function BaseService<T>(entity: Constructor<T>): Type<IBaseService<T>> {
#Injectable({
scope: Scope.REQUEST,
})
class BaseServiceHost extends TypeOrmCrudService<T> implements IBaseService<T> {
constructor(#InjectRepository(entity) repo, #Inject(REQUEST) readonly request: any) {
super(repo);
}
#Override()
createOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.createOne(req, this.addCreatedByToDTO(dto));
}
#Override()
replaceOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.replaceOne(req, this.addLastChangedByToDTO(dto));
}
#Override()
updateOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.updateOne(req, this.addLastChangedByToDTO(dto));
}
private addCreatedByToDTO(dto: DeepPartial<T>): DeepPartial<T> {
const userUUID: Partial<User> = this.request.user.userUUID;
return { ...dto, createdBy: userUUID };
}
private addLastChangedByToDTO(dto: DeepPartial<T>): DeepPartial<T> {
const userUUID: Partial<User> = this.request.user.userUUID;
return { ...dto, lastChangedBy: userUUID };
}
}
return BaseServiceHost;
}
Later on i just extend my service like:
#Injectable()
export class ExampleService extends BaseService(ExampleEntity) {}

NestJS swagger module not working properly with generic crud controller

I'm trying to create a generic crud controller that use a generic crud service in a NestJS application. It works properly but the swagger module doesn't generate the documentation about the REST services parameters correctly.
This is the service:
import { Model, Document } from "mongoose";
export abstract class CrudService<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly model: Model<CrudModel>) {}
async findAll(): Promise<CrudModel[]> {
return this.model.find().exec();
}
async create(dto: CreateDto): Promise<CrudModel> {
const createdDto = new this.model(dto);
return createdDto.save();
}
async update(id: any, dto: UpdateDto): Promise<CrudModel> {
return this.model.findOneAndUpdate({ _id: id }, dto, { new: true });
}
async delete(id: any): Promise<boolean> {
const deleteResult = await this.model.deleteOne({ _id: id });
return deleteResult.ok === 1 && deleteResult.deletedCount === 1;
}
}
This is the controller:
import { Body, Delete, Get, Param, Post, Put } from "#nestjs/common";
import { Document } from "mongoose";
import { CrudService } from "./crud-service.abstract";
export abstract class CrudController<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly service: CrudService<CrudModel, CreateDto, UpdateDto>) {}
#Get()
async findAll(): Promise<CrudModel[]> {
return this.service.findAll();
}
#Post()
async create(#Body() dto: CreateDto): Promise<CrudModel> {
return this.service.create(dto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() dto: UpdateDto): Promise<CrudModel> {
return this.service.update(id, dto);
}
#Delete(':id')
async delete(#Param('id') id: string): Promise<boolean> {
return this.service.delete(id);
}
}
I found this issue on Github repo: https://github.com/nestjs/swagger/issues/86
The last comment mentions a solution using mixins but I can't figure it out how to adapt it to my needs
I eventually went to describing my own schema.
Here is the custom decorator (NestJs example)
import { applyDecorators } from '#nestjs/common';
import { ApiOkResponse, getSchemaPath } from '#nestjs/swagger';
export const OpenApiPaginationResponse = (model: any) => {
return applyDecorators(
ApiOkResponse({
schema: {
properties: {
totalPages: {
type: 'number'
},
currentPage: {
type: 'number'
},
itemsPerPage: {
type: 'number'
},
data: {
type: 'array',
items: { $ref: getSchemaPath(model) }
}
}
}
})
);
};
And here is a example of how it is applied to a controller
#OpenApiPaginationResponse(DTOHere)
public async controllerMethod() {}
Hope this helps you out
I had the same problem recently, I found the solution with Decorators in Nestjs because for native properties Swagger can't recognize our Generic Type Class < T >.
I will show you how I could implement my solution with a Parameterized Pagination Class.
Specify our Pagination Class
export class PageDto<T> {
#IsArray()
readonly data: T[];
#ApiProperty({ type: () => PageMetaDto })
readonly meta: PageMetaDto;
constructor(data: T[], meta: PageMetaDto) {
this.data = data;
this.meta = meta;
}
}
Our parametrized type is data.
Create our Decorator class, that will map our parametrized type data
export const ApiOkResponsePaginated = <DataDto extends Type<unknown>>(
dataDto: DataDto,
) =>
applyDecorators(
ApiExtraModels(PageDto, dataDto),
ApiOkResponse({
schema: {
allOf: [
{ $ref: getSchemaPath(PageDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(dataDto) },
},
},
},
],
},
}),
);
In this decorator we used the definition of SwaggerDocumentation for specify what will be our class that Swagger will be mapped.
Add our Decorator ApiOkResponsePaginated class in our Controller.
#Get('/invoices')
#ApiOkResponsePaginated(LightningInvoice)
async getAllInvoices(
#Auth() token: string,
#Query() pageOptionsDto: any,
): Promise<any> {
return this.client.send('get_invoices', {
token,
pageOptionsDto,
});
}
And that's how you can visualize in Swagger the representation of PageDto<LightningInvoice> response.
I hope this answer help you in your code.

how to inject service when using resolver class inheritance

I try to use resolver class inheritance in TypeGraphQL working with Nestjs, but don't know how to inject PostService into BaseResolver
import { ClassType } from 'type-graphql';
import { Query, Resolver, Args } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { PostService } from './post.service';
import { GqlAuthGuard } from 'src/account/auth/auth.guard';
export const createPostBaseResolver = <T extends ClassType>(
suffix: string,
objectTypeCls: T,
) => {
#Resolver(() => objectTypeCls, { isAbstract: true })
abstract class PostBaseResolver {
constructor(private readonly postService: PostService) {}
#Query(() => objectTypeCls, { name: `${suffix}` })
#UseGuards(GqlAuthGuard)
async getPostById(#Args('id') id: number) {
// here postService is undefined
return await this.postService.get(id);
}
}
return PostBaseResolver as any;
};

validate nested objects using class-validator in nest.js controller

I want to validate body payload using class-validator in a nest.js controller. My currency.dto.ts file is like this:
import {
IsNotEmpty,
IsString,
ValidateNested,
IsNumber,
IsDefined,
} from 'class-validator';
class Data {
#IsNotEmpty()
#IsString()
type: string;
#IsNotEmpty()
#IsNumber()
id: number;
}
export class CurrencyDTO {
#ValidateNested({ each: true })
#IsDefined()
data: Data[];
}
and in my nest.js controller, I use it like this.
#Post()
#UseGuards(new AuthTokenGuard())
#UsePipes(new ValidationPipe())
addNewCurrency(#Req() req, #Body() data: CurrencyDTO) {
console.log('data', data);
}
my validation pipe class is like this:
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { validate, IsInstance } from 'class-validator';
import { plainToClass, Exclude } from 'class-transformer';
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata) {
if (value instanceof Object && this.isEmpty(value)) {
throw new HttpException(
`Validation failed: No Body provided`,
HttpStatus.BAD_REQUEST,
);
}
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errorsList = await validate(object);
if (errorsList.length > 0) {
const errors = [];
for (const error of errorsList) {
const errorsObject = error.constraints;
const { isNotEmpty } = errorsObject;
if (isNotEmpty) {
const parameter = isNotEmpty.split(' ')[0];
errors.push({
title: `The ${parameter} parameter is required.`,
parameter: `${parameter}`,
});
}
}
if (errors.length > 0) {
throw new HttpException({ errors }, HttpStatus.BAD_REQUEST);
}
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
private isEmpty(value: any) {
if (Object.keys(value).length > 0) {
return false;
}
return true;
}
}
This validation pipe works fine for all except for nested objects. Any idea what am I doing wrong here?
My body payload is like this:
{
"data": [{
"id": 1,
"type": "a"
}]
}
Try specifying the nested type with #Type:
import { Type } from 'class-transformer';
export class CurrencyDTO {
#ValidateNested({ each: true })
#Type(() => Data)
data: Data[];
}
For a nested type to be validated, it needs to be an instance of a class not just a plain data object. With the #Type decorator you tell class-transformer to instantiate a class for the given property when plainToClass is called in your VaildationPipe.
If you are using the built-in ValidationPipe make sure you have set the option transform: true.
At least in my case, the accepted answer needed some more info. As is, the validation will not run if the key data does not exist on the request. To get full validation try:
#IsDefined()
#IsNotEmptyObject()
#ValidateNested()
#Type(() => CreateOrganizationDto)
#ApiProperty()
organization: CreateOrganizationDto;

Resources