how to inject service when using resolver class inheritance - nestjs

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

Related

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

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

How to inject repository into nestjs-rate-limiter guard

I've tried to inject a repository into the guard which extends from RateLimiterGuard nestjs-rate-limiter but I got an error when call super.canActivate(ctx). It said that this.reflector.get is not a function. Is there any mistake that I have?
Here is my code:
import { RateLimiterGuard, RateLimiterOptions } from 'nestjs-rate-limiter';
import type { Request } from 'express';
import config from 'config';
import { ExecutionContext, Injectable } from '#nestjs/common';
import { MockAccountRepository } from '../modules/mock/mock-accounts/accounts-mock.repository';
import { Reflector } from '#nestjs/core';
import { UserIdentifierType } from 'src/modules/users/user.types';
const ipHeader = config.get<string>('server.ipHeader');
#Injectable()
export class ForwardedIpAddressRateLimiterGuard extends RateLimiterGuard {
constructor(
reflector: Reflector,
options: RateLimiterOptions,
private readonly mockAccRepo: MockAccountRepository,
) {
super(options, reflector);
}
protected getIpFromRequest(request: Request): string {
return request.get(ipHeader) || request.ip;
}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const phoneNumber = req.body?.phoneNumber || req.params?.phoneNumber;
// If mock phone number, always allow
if (
await this.mockAccRepo.findOne({
identifier: phoneNumber,
identifierType: UserIdentifierType.PHONE_NUMBER,
})
) {
return true;
}
// Otherwise, apply rate limiting
return super.canActivate(ctx);
}
}

How to workraound this TypeORM error, "EntityRepository is deprecated , use Repository.extend function instead"?

However, I can't find any Repository.extend method in Repository class and there's nothing about it in the documentation. How to solve this?
typeorm version: "^0.3.0"
I'm using nest js and trying to create a custom repository.
First of all:
npm install #nestjs/typeorm#next
NOTE
In my project #nestjs/typeorm version is 9.0.0-next.2 and typeorm version is 0.3.6
Create a folder named database in the src of your project then create two files in (typeorm-ex.decorator.ts and typeorm-ex.module.ts)
// typeorm-ex.decorator.ts
import { SetMetadata } from "#nestjs/common";
export const TYPEORM_EX_CUSTOM_REPOSITORY = "TYPEORM_EX_CUSTOM_REPOSITORY";
export function CustomRepository(entity: Function): ClassDecorator {
return SetMetadata(TYPEORM_EX_CUSTOM_REPOSITORY, entity);
}
And next file
// typeorm-ex.module.ts
import { DynamicModule, Provider } from "#nestjs/common";
import { getDataSourceToken } from "#nestjs/typeorm";
import { DataSource } from "typeorm";
import { TYPEORM_EX_CUSTOM_REPOSITORY } from "./typeorm-ex.decorator";
export class TypeOrmExModule {
public static forCustomRepository<T extends new (...args: any[]) => any>(repositories: T[]): DynamicModule {
const providers: Provider[] = [];
for (const repository of repositories) {
const entity = Reflect.getMetadata(TYPEORM_EX_CUSTOM_REPOSITORY, repository);
if (!entity) {
continue;
}
providers.push({
inject: [getDataSourceToken()],
provide: repository,
useFactory: (dataSource: DataSource): typeof repository => {
const baseRepository = dataSource.getRepository<any>(entity);
return new repository(baseRepository.target, baseRepository.manager, baseRepository.queryRunner);
},
});
}
return {
exports: providers,
module: TypeOrmExModule,
providers,
};
}
}
Open your AppModule and modify it like the following:
#Module({
imports: [
TypeOrmModule.forRoot({
type: 'mssql',
...
entities: [Photo],
}),
TypeOrmExModule.forCustomRepository([PhotoRepository]),
...
],
controllers: [AppController],
providers: [
AppService
],
})
export class AppModule { }
You can create your customer repository like the following :
#CustomRepository(Photo)
export class PhotoRepository extends Repository<Photo> {
public async getAllPhoto() {
const query = this.createQueryBuilder('photo')
.where('photo.isPublished = :isPublished', { isPublished: true })
const photos = await query.getMany()
return photos
}
}
Everything works perfectly.
Thanks to #anchan828
In the new version of TypeORM (0.3.*), I am changing custom repositories to services.
Based on connection to multiple official documentation databases.
https://docs.nestjs.com/techniques/database#multiple-databases
Custom repository
#EntityRepository(Person)
export class PersonRepository extends Repository<Person> {...}
Custom repository as Service
#Injectable()
export class PersonRepository {
constructor(private dataSource: DataSource) { }
exampleQueryBuilder() {
return this.dataSource
.getRepository(Person)
.createQueryBuilder() ...
}
Repository injection
#Injectable()
export class PersonService {
constructor(
#Inject(PersonRepository)
private readonly personRepository: PersonRepository,
) {}
With the current version of TypeORM, it's possible to implement a custom repository in the following way utilizing DataSource.
// user.repository.ts
#Injectable()
export class UsersRepository extends Repository<UsersEntity> {
constructor(private dataSource: DataSource) {
super(UsersEntity, dataSource.createEntityManager());
}
async getById(id: string) {
return this.findOne({ where: { id } });
}
// ...
}
The repository is then injected into the service.
// user.service.ts
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
async getById(id: string): Promise<User> {
return this.userRepository.getById(id);
}
// ...
}
and the module has imports for the feature and the repository as a provider.
// user.module.ts
#Module({
imports: [
TypeOrmModule.forFeature([UserEntity])],
// ...
],
providers: [UserService, UserRepository],
// ...
})
export class UserModule {}
The way I could solve this error is the following:
First, I created the file datasource.ts, I also declared the datasource, it follows the same structure as the ormconfig.js file:
import { DataSource } from 'typeorm';
require('dotenv').config();
export const AppDataSource = new DataSource({
type: 'mongodb',
url: process.env.MONGO_URI,
useNewUrlParser: true,
synchronize: true,
logging: true,
database: process.env.DB_DATABASE,
entities: ['dist/entities/*.js'],
useUnifiedTopology: true,
});
AppDataSource.initialize()
.then(() => {
console.log('Data Source has been initialized!');
})
.catch((err) => {
console.error('Error during Data Source initialization', err);
});
And, after that, I created a Custom Repository:
import { AppDataSource } from '#CustomDataSource';
import { Product } from '#Entities';
import { CreateProductDto, UpdateProductDto } from '#Products';
const dataSource = AppDataSource;
export const ProductRepository =
dataSource.getMongoRepository(Product).extend({
async findOneById(id: number): Promise<Product> {
const product = await ProductRepository.findOne({ where: { id }});
return product;
},
async findMany(): Promise<Product[]> {
const products = await ProductRepository.find();
return products;
},
});
In this way, you don't have to add the repositories in TypeORMModule.forFeature([]), for each module in which you are working.
Note that now, we don't call this, we call ProductRepository and the method directly.
As refer to the related PR, the migration is work in progress. You can checkout the latest progress by using:
yarn add #nestjs/typeorm#next
You can use custom dynamic module as a workaround, like this gist for now.
I created a utility method provideCustomRepository to simplify overriding a repository with nestjs / typeorm 0.3.x
https://gist.github.com/rhutchison/a530d89c37f1978a48dcee4bf2418cb7
import { Provider, Type } from '#nestjs/common';
import { getDataSourceToken, getRepositoryToken } from '#nestjs/typeorm';
import { DataSource, DataSourceOptions, Repository } from 'typeorm';
export function provideCustomRepository<T>(
entity: Type<T>,
repository: Type<Repository<T>>,
dataSource?: DataSource | DataSourceOptions | string
): Provider {
return {
provide: getRepositoryToken(entity),
inject: [getDataSourceToken(dataSource)],
useFactory(dataSource: DataSource) {
const baseRepository = dataSource.getRepository(entity);
return new repository(
baseRepository.target,
baseRepository.manager,
baseRepository.queryRunner
);
},
};
}
<script src="https://gist.github.com/rhutchison/a530d89c37f1978a48dcee4bf2418cb7.js"></script>
There is no need to create a separated repository file and the entity will be injected to the service just like that:
import { Injectable, NotFoundException } from '#nestjs/common';
import { Task } from './task.entity';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
#Injectable()
export class TasksService {
constructor(
#InjectRepository(Task)
private taskRepository: Repository<Task>
) {}
async getTasksById(id: number): Promise<Task> {
const found = await this.taskRepository.findOne({ where: { id: id
}});
}
}
I hope this helps.
The process, that have worked for me:
a)Define your Entity: (task.entity.ts)
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class Task {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
title: string;
}
b)Define your repository (tasks.repository.ts)
import { Injectable, NotFoundException } from '#nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Task } from './task.entity';
#Injectable()
export class TasksRepository extends Repository<Task> {
constructor(private dataSource: DataSource) {
super(Task, dataSource.createEntityManager());
}
async getById(id: string): Promise<Task> {
const found = await this.findOneBy({ id });
if (!found) {
throw new NotFoundException(`Task with ID "${id}" not found`);
}
return found;
}
}
c)Controller (tasks.controller.ts)
import {
Controller,
Get,
Param,
} from '#nestjs/common';
import { TasksService } from './tasks.service';
import { Task } from './task.entity';
#Controller('tasks')
export class TasksController {
constructor(private tasksService: TasksService) {}
#Get('/:id')
getTaskById(#Param('id') id: string): Promise<Task> {
return this.tasksService.getTaskById(id);
}
}
d)Define module (tasks.module.ts)
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Task } from './task.entity';
import { TasksController } from './tasks.controller';
import { TasksRepository } from './tasks.repository';
import { TasksService } from './tasks.service';
#Module({
imports: [TypeOrmModule.forFeature([Task])],
controllers: [TasksController],
providers: [TasksService, TasksRepository],
})
export class TasksModule {}
e)And in the root module (app.module.ts) - NOT FOR PRODUCTION
import { Module } from '#nestjs/common';
import { TasksModule } from './tasks/tasks.module';
import { TypeOrmModule } from '#nestjs/typeorm';
#Module({
imports: [
TasksModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'task-management',
autoLoadEntities: true,
synchronize: true,
}),
],
})
export class AppModule {}
Note: autoLoadEntities, loads automatically any entity defined. This must follow the name name_entity.entity.ts
Hope it helps
TypeORM CHANGELOG.md
In order to extend UserRepository functionality you can use .extend method of Repository class
Example
// user.repository.ts
export const UserRepository = dataSource.getRepository(User).extend({...})

NestJS y TypeORM: built in Repository undefined

i did Inject Repository(User) but it did not work for me .
i want to call : this.users Repository.create
but give this error :
Type Error: this.users Repository.create is not a function
......
i did Inject Repository(User) but it did not work for me .
i want to call : this.users Repository.create
but give this error :
Type Error: this.users Repository.create is not a function
Service :
import { HttpException, HttpStatus, Inject, Injectable,forwardRef } from '#nestjs/common';
import { AuthenticationService } from 'src/authentication/authentication.service';
import { Repository } from 'typeorm';
import CreateUserDto from './dto/create-user.dto';
import { InjectRepository } from '#nestjs/typeorm';
import User from './entities/user.entity';
#Injectable()
export class UserService {
constructor(
#Inject(forwardRef(() => AuthenticationService))
// #Inject(User)
// private usersRepository: Repository<User>
#InjectRepository(User) private usersRepository: Repository<User>,
private readonly authenticationService: AuthenticationService,
) {}
async getByEmail(email: string) {
const user = await this.usersRepository.findOne({ email });
if (user) {
return user;
}
throw new HttpException('User with this email does not exist', HttpStatus.NOT_FOUND);
}
async getById(id: number) {
const user = await this.usersRepository.findOne({ id });
if (user) {
return user;
}
throw new HttpException('User with this id does not exist', HttpStatus.NOT_FOUND);
}
async create(userData: CreateUserDto) {
const newUser = await this.usersRepository.create(userData);
await this.usersRepository.save(newUser);
return newUser;
}
}
Module :
import { Module,forwardRef } from '#nestjs/common';
import { UserService } from './users.service';
import { TypeOrmModule } from '#nestjs/typeorm';
import User from './entities/user.entity';
import { UsersController } from './users.controller';
import { AuthenticationService } from 'src/authentication/authentication.service';
import { ConfigModule, ConfigService } from '#nestjs/config';
import { JwtModule, JwtService } from '#nestjs/jwt';
#Module({
imports: [TypeOrmModule.forFeature([User]),JwtModule.register({})],
providers: [UserService,AuthenticationService,ConfigService],
exports: [UserService,AuthenticationService,ConfigService],
controllers:[UsersController]
})
export class UsersModule {}
Entity :
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
#Entity('User')
class User {
#PrimaryGeneratedColumn()
public id?: number;
#Column({ unique: true })
public email: string;
#Column()
public name: string;
#Column()
public password: string;
}
export default User;
I found a solution by creating a custom repository and extend it with built in works fine.
Here is link :
https://clownhacker.tistory.com/250
I think it happened because I did changes with my entity class, I hope it will help someone.
In my case in this.repository, this was not pointing to correct instance. Used bind to fix it.

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

Resources