Cakephp 3: Plugin is not loading component from AppController - components

I load some components in app/Controller/AppController.php
class AppController extends Controller
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Auth');
}
}
beforeFilter() method is defined inside:
public function beforeFilter(Event $event)
{
if ($this->Auth->isAnonymous() && $this->request->query('token')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
When plugin is executed, I get 'Call to a member function isAnonymous() on boolean' error. It means that $this->Auth is false. It is because 'Auth' component is not loaded.
So, I have a plugin named Files where I defined AppController as:
use App\Controller\AppController as BaseController;
class AppController extends BaseController
{
}
It is empty.
This file is in plugin/src/Controller/AppController.
I don't understand the error. Is not it supposed to that if this controller extends of main AppController, components should be loaded?

Related

Nestjs: calling service functions from Model / Entity with sequelize hooks

In NestJS, I have to use a module service into an entity/model to populate data into elastic-search index. populating elastic search index logic is written in Job.service.ts.
I want to call that onCreate method from Job.service.ts from sequelize hooks present in models.
Here is code for Job.ts model/entity -
import { Table, Model, Column, AutoIncrement, PrimaryKey } from "sequelize-typescript";
#Table({ schema: "job", tableName: "job" })
export class Job extends Model<Job> {
#AutoIncrement
#PrimaryKey
#Column
id: number;
#Column
title: string;
#AfterCreate
static async jobAfterCreate(instance, options) {
// <--- need to call job service onCreate method here
}
#AfterUpdate
static async jobAfterUpdate() {}
#AfterDestroy
static async jobAfterDestroy() {}
}
and here is code for Job.service.ts -
//imports not added
#Injectable()
export class JobService {
constructor(
#Inject("SEQUELIZE")
private readonly sequelizeInstance: Sequelize,
#Inject(forwardRef(() => ElasticsearchService))
private readonly elasticsearchService: ElasticsearchService,
#InjectModel(Job)
private jobModel: typeof Job
) {}
// here will write logic for updating elastic search index
async onCreate(instance, options){
console.log("ON CREATE INSTANCE:", instance);
console.log("ON CREATE OPTIONS:", options);
}
async onDestroy(instance, options){
console.log("ON DESTROY INSTANCE:", instance);
console.log("ON DESTROY OPTIONS:", options);
}
}
I tried injecting service into Job model but it did not worked.
And I cannot write elastic search logic inside model directly because for that I need ElasticsearchService.
The Solution is To Override the provider
The primary way to inject information into the models is by overriding the injection behavior.
First, you would need to add a static property referencing the service in your model.
I am going to use the event emitter as an example here.
Your Model Class
import {Model, Table, Column, AfterCreate} from "sequelize-typescript";
import { EventEmitter2 } from "#nestjs/event-emitter";
#Table()
export class SomeModel extends <SomeModel> {
// this would be your referencing
public static EventEmitter: EventEmitter2;
#Column
public someColumn: string;
#AfterCreate
public static triggerSomeEvent(instance: SomeModel) {
SomeModel.EventEmitter.emit('YourEvent', instance);
}
}
The module where you are going to use the model
Now we are overriding the default injection process.
import { EntitiesMetadataStorage } from '#nestjs/sequelize/dist/entities-metadata.storage';
import {
getConnectionToken,
getModelToken,
SequelizeModule,
} from '#nestjs/sequelize';
import { EventEmitter2 } from '#nestjs/event-emitter';
// The provider override
const modelInjector: Provider = {
provide: getModelToken(AccountabilityPartnerModel, DEFAULT_CONNECTION_NAME),
useFactory: (connection: Sequelize, eventEmitter: EventEmitter2) => {
SomeModel.EventEmitter = eventEmitter;
if (!connection.repositoryMode) {
return SomeModel;
}
return connection.getRepository(SomeModelas any);
},
inject: [getConnectionToken(DEFAULT_CONNECTION_NAME), EventEmitter2],
};
// Updating the meta information of sequelize-typescript package to handle connection injection in to the model overridden.
EntitiesMetadataStorage.addEntitiesByConnection(DEFAULT_CONNECTION_NAME, [
SomeModel,
]);
// our custom module being used rather than the Sequelize.forFeature([SomeModel])
const someModelModule: DynamicModule = {
module: SequelizeModule,
providers: [modelInjector],
exports: [modelInjector],
};
#Module({
imports: [someModelModule],
providers: [SomeService],
})
export class SomeModule {
}
Inject your model into your service as you would do using Sequlize.forFeature and InjectModel indicated as below.
#Injectable()
export class SomeService {
constructor(#InjectModel(SomeModel) someModel: typeof SomeModel) {}
public someFunction(data: any) {
this.someModel.EventEmitter.emit('YourEvent', data);
}
}

I'd like to DI for repository interface and service interface like Spring using typedi

I'd like to DI for repository interface and service interface like Spring using typedi.
Below code (example code of DI for repository) is working correctly when calling api.
Repository
import { Service } from "typedi";
import { EntityRepository, Repository } from "typeorm";
import { User } from "../entity/User";
export interface IUserRepository {
findAllUsers();
findUserByUserId(id: number);
addUser(user: any);
removeUserByUserId(user: any);
}
#Service()
#EntityRepository(User)
export class UserRepository
extends Repository<User>
implements IUserRepository {
findAllUsers() {
return this.find();
}
findUserByUserId(id: number) {
return this.findOne({ id });
}
addUser(user: any) {
return this.save(user);
}
removeUserByUserId(user: any) {
return this.remove(user);
}
}
Service
import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";
export interface IUserService {
all();
one(id: any);
save(user: any);
remove(id: any);
}
#Service()
export class UserService implements IUserService {
#InjectRepository(User)
private userRepository: UserRepository;
async all() {
return this.userRepository.findAllUsers();
}
async one(id: any) {
let user = await this.userRepository.findUserByUserId(id);
if (typeof user === "undefined") {
throw new Error(`userId ${id} is not found.`);
}
return user;
}
async save(user: any) {
return this.userRepository.addUser(user);
}
async remove(id: any) {
let userToRemove = await this.userRepository.findUserByUserId(id);
if (typeof userToRemove === "undefined") {
throw new Error(`userId ${id} is not found.`);
}
return this.userRepository.removeUserByUserId(userToRemove);
}
}
However, when I'd like to inject repository using interface, it does not work correctly and occur the error message.
The build is succes. The error message is occur when calling api
In addition, error message are different for the first time and the second time later when call api.
like this
Repository
import { Service } from "typedi";
import { InjectRepository } from "typeorm-typedi-extensions";
import { User } from "../entity/User";
import { UserRepository } from "../repository/userRepository";
...
#Service()
export class UserService implements IUserService {
#InjectRepository(User)
private userRepository: UserRepository;
async all() {
return this.userRepository.findAllUsers();
}
...
}
Error message of first time.
{
"name": "CustomRepositoryNotFoundError",
"message": "Custom repository Object was not found. Did you forgot to put #EntityRepository decorator on it?",
"stack": "CustomRepositoryNotFoundError: Custom repository Object was not found. Did you forgot to put #EntityRepository decorator on it? (The following is omitted)"
}
Error message of second time later.
{
"name": "TypeError",
"message": "Cannot read property 'all' of undefined",
"stack": "TypeError: Cannot read property 'all' of undefined(The following is omitted)"
}
Service does not work well either.
Below code is success code.
Controller
import {
Get,
JsonController,
OnUndefined,
Param,
Post,
Body,
Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { UserService } from "../service/userService";
#Service()
#JsonController("/users")
export class UserRestController {
#Inject()
private userService: UserService;
#Get("/")
getAll() {
return this.userService.all();
}
#Get("/:id")
#OnUndefined(404)
getOne(#Param("id") id: number) {
return this.userService.one(id);
}
#Post("/")
add(#Body() user: any) {
return this.userService.save(user);
}
#Delete("/:id")
delete(#Param("id") id: number) {
return this.userService.remove(id);
}
}
But the below is not work well.
In this case, even the build does not work.
Controller
import {
Get,
JsonController,
OnUndefined,
Param,
Post,
Body,
Delete,
} from "routing-controllers";
import { Inject, Service } from "typedi";
import { IUserService } from "../service/userService";
#Service()
#JsonController("/users")
export class UserRestController {
#Inject()
private userService: IUserService;
#Get("/")
getAll() {
return this.userService.all();
}
#Get("/:id")
#OnUndefined(404)
getOne(#Param("id") id: number) {
return this.userService.one(id);
}
#Post("/")
add(#Body() user: any) {
return this.userService.save(user);
}
#Delete("/:id")
delete(#Param("id") id: number) {
return this.userService.remove(id);
}
}
Error Message
CannotInjectValueError: Cannot inject value into "UserRestController.userService". Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.
As described at the beginning, I'd like to DI for repository interface and service interface like Spring using typedi.
TypeDI cannnot using like this?
or my code is wrong?
Please help me.
Thank you.
Interfaces are ephemeral, they don't actually exist when your code is running doing its job, they exist only when you write the code. Classes, on the other hand, are pretty much tangible, they always exist. That's why when you use UserService class, it works, but when you use IUserService interface, it doesn't work.
The error you are getting tells you something useful:
Please make sure […] you don't use interfaces without service tokens as injection value.

NestJS with MongoDB and NestJsxAutomapper resulting in error 'cannot read property plugin of undefined'

I am working on an API with NestJS, and because I have DTO's I am using an AutoMapper (made by #nartc and/or nestjsx), I have tried to make my example as small as I could with the Foo example, because I use multiple files.
This is my module:
// foo.module.ts
import { Module } from "#nestjs/common";
import { MongooseModule } from "#nestjs/mongoose";
import { Foo, FooSchema } from "./foo.entity.ts";
import { FooController } from "./foo.controller.ts";
import { FooService } from "./foo.service.ts";
import { FooProfile } from "./foo.profile.ts";
#Module({
imports: [
MongooseModule.forFeature([
{
name: Foo.name,
schema: FooSchema,
collection: "foos",
}
])
// FooProfile <-- if I uncomment this, the program will give the error (shown at the bottom of this question)
],
controllers: [FooController],
providers: [FooProivder],
})
export class FooModule {}
This is my entity:
// foo.entity.ts
import { Schema, SchemaFactory, Prop } from "#nestjs/mongoose";
import { Document } from "mongoose";
#Schema()
export class Foo extends Document { // if I remove the `extends Document` it works just fine
#Prop({ required: true })
name: string;
#Prop()
age: number
}
export const FooSchema = SchemaFactory.createForClass(Foo);
This is my DTO:
// foo.dto.ts
export class FooDTO {
name: string;
}
This is my controller:
// foo.controller.ts
import { Controller, Get } from "#nestjs/common";
import { InjectMapper, AutoMapper } from "nestjsx-automapper";
import { Foo } from "./foo.entity";
import { FooService } from "./foo.service";
import { FooDTO } from "./dto/foo.dto";
#Controller("foos")
export class FooController {
constructor(
private readonly fooService: FooService
#InjectMapper() private readonly mapper: AutoMapper
) {}
#Get()
async findAll() {
const foos = await this.fooService.findAll();
const mappedFoos = this.mapper.mapArray(foos, Foo, FooDTO);
// ^^ this throws an error of the profile being undefined (duh)
return mappedFoos;
}
}
This is my profile:
// foo.profile.ts
import { Profile, ProfileBase, InjectMapper, AutoMapper } from "nestjsx-automapper";
import { Foo } from "./foo.entity";
import { FooDTO } from "./foo.dto";
#Profile()
export class FooProfile extends ProfileBase {
constructor(#InjectMapper() private readonly mapper: AutoMapper) {
// I've read somewhere that the `#InjectMapper() private readonly` part isn't needed,
// but if I exclude that, it doesn't get the mapper instance. (mapper will be undefined)
super();
this.mapper.createMap(Foo, FooDTO);
}
}
If I uncomment the line I highlighted in the module, it will result in the following error..
[Nest] 11360 - 2020-08-18 15:53:06 [ExceptionHandler] Cannot read property 'plugin' of undefined +1ms
TypeError: Cannot read property 'plugin' of undefined
at Foo.Document.$__setSchema ($MYPATH\node_modules\mongoose\lib\document.js:2883:10)
at new Document ($MYPATH\node_modules\mongoose\lib\document.js:82:10)
at new Foo($MYPATH\dist\foo\foo.entity.js:15:17)
I have also referred to this answer on stackoverflow, but that doesn't work for me either. I have also combined that with the documentation, but with no luck.. How would I get the AutoMapper to register my profiles?
Update
The error seems to originate from the foo entity, if I remove the extends Document and the Schema(), Prop({ ... }) from the class it works fine, it seems like I have to inject mongoose or something?
In your module, just import the path to the profile like below:
import 'relative/path/to/foo.profile';
By importing the path to file, TypeScript will include the file in the bundle and then the #Profile() decorator will be executed. When #Profile() is executed, AutoMapperModule keeps track of all the Profiles then when it's turn for NestJS to initialize AutoMapperModule (with withMapper() method), AutoMapperModule will automatically add the Profiles to the Mapper instance.
With that said, in your FooProfile's constructor, you'll get AutoMapper instance that this profile will be added to
#Profile()
export class FooProfile extends ProfileBase {
// this is the correct syntax. You would only need private/public access modifier
// if you're not going to use this.mapper outside of the constructor
// You DON'T need #InjectMapper() because that's Dependency Injection of NestJS.
// Profile isn't a part of NestJS's DI
constructor(mapper: AutoMapper) {
}
}
The above answer will solve your problems with AutoMapper. As far as your Mongoose problem, I would need a sample repro to tell for sure. And also, visit our Discord for this kind of question.
What worked for me.
1. Updated all the absolute paths for models, schemas, entities (is easy if you search for from '/src in your projects, and update all the routes to relative paths)
from:
import { User } from 'src/app/auth/models/user/user.entity';
to:
import { User } from './../../auth/models/user/user.entity';
2. mongoose imports:
from:
import mongoose from 'mongoose';
to:
import * as mongoose from 'mongoose';
3. Remove validation pipe if you don't use it. For some reason (I think i don't use them on the controller, I didn't investigate, I've removed from one controller the Validation Pipe) so If you have this try it:
from:
#Controller('someroute')
#UsePipes(new ValidationPipe())
export class SomeController {}
to:
#Controller('someroute')
export class SomeController {}
I hope my solution worked for you ^_^

Custom Repo without extends error: No repository for was found. Looks like this entity is not registered in current "default" connection?

When I use Custom Repository class without any extension then I run into the error: No repository for "MasterDataRepo" was found. Looks like this entity is not registered in current "default" connection?
#EntityRepository()
export class MasterDataRepo {
constructor(private manager: EntityManager) {
}
getDriveryByTerminal(terminalCode: string):Promise<Driver> {
return this.manager.findOne(Driver, { terminalCode });
}
}
The app works fine if I create repository class after extending Repository<Entity>
#EntityRepository(Driver)
export class MasterDataRepo extends Repository<Driver> {
getDriveryByTerminal(terminalCode: string):Promise<Driver> {
return this.manager.findOne(Driver, { terminalCode });
}
}
My MasterDataModule is:
import { Module } from '#nestjs/common';
import { MasterDataController } from './MasterData.controller';
import { MasterDataService } from './masterData.service';
import { TypeOrmModule } from '#nestjs/typeorm';
import { MasterDataRepo } from './MasterData.Repo';
#Module({
imports: [
TypeOrmModule.forFeature([MasterDataRepo])
],
controllers: [MasterDataController],
providers: [MasterDataService],
})
export class MasterDataModule {}
and AppModule is:
import { Module } from '#nestjs/common';
import { TasksModule } from './tasks/tasks.module';
import { TypeOrmModule } from '#nestjs/typeorm';
import { typeOrmConfig } from './config/typeorm.config';
import { MasterDataModule } from './masterData/MasterData.module';
import { Connection } from 'typeorm';
#Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
TasksModule,
MasterDataModule
],
})
export class AppModule {
constructor(private connection: Connection) {}
}
TypeOrmModule.forFeature([MasterDataRepo])
TypeOrmModule.forFeature accepts array of Entities and your MasterDataRepo is not an entity.
But when you use EntityRepository decorator with the User entity, it will add your User entity to the available entities list(which will be applied to the database connection).
Also extending the Repository class will make your MasterDataRepo as an extended version of an Entity.
https://github.com/typeorm/typeorm/blob/906d97fc8dbf1dba8f4e579a4f5bfead83af36ab/src/decorator/EntityRepository.ts
https://github.com/typeorm/typeorm/blob/906d97fc8dbf1dba8f4e579a4f5bfead83af36ab/src/repository/Repository.ts
Note:
There are two solutions:
Nestjs already provides #nestjs/typeorm module and you can inject a repository for an entity easily in your service.
For example:
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>
) {}
...
}
You can check the documentation here - https://docs.nestjs.com/techniques/database
You can just use Active Record pattern that doesn't require a repository.
What you need to do is just to make your entities extend BaseEntity of typeorm.
For example:
#Entity()
export class User extends BaseEntity {} // <- BaseEntity
...
const user = await User.find({}) // You just use the User class for executing a query.
user.name = 'something';
await user.save();
Thanks, #yash. I could find a workaround. Defined the repo as injecatable
#Injectable()
#EntityRepository()
export class MasterDataRepo {
constructor(private manager: EntityManager) {
}
getDriveryByTerminal(terminalCode: string):Promise<Driver> {
return this.manager.findOne(Driver, { terminalCode });
}
}
and the service as
#Injectable()
export class MasterDataService {
constructor(
private masterDataRepo: MasterDataRepo,
) {}
}
Do you see any concerns?
For those using TypeORM and who had the Injection configured correctly but still running into the error:
In my case, the issue was that my #Entity was not registered in the Postgres TypeOrmModuleOptions (more precisely the entity array of the BaseConnectionOptions, cf their git repo ). Those options allow to configure Nest's TypeOrmModule
Hope this helps someone else coming to this thread

In nestjs, is it possible to specify multiple handlers for the same route?

Is it possible to specify multiple handler for the same route?
Any HTTP GET request to the /test route should call the get handler unless the query string watch === '1', in which case it should call the watch handler instead.
import { Controller, Get } from '#nestjs/common';
#Controller('test')
export class TestController {
#Get()
get(){
return 'get'
}
#Get('?watch=1')
watch(){
return 'get with watch param'
}
}
As the framework does not seem to support this, I was hoping to be able to write a decorator to abstract this logic.
ie.
import { Controller, Get } from '#nestjs/common';
import { Watch } from './watch.decorator';
#Controller('test')
export class TestController {
#Get()
get(){
return 'get'
}
#Watch()
watch(){
return 'get with watch param'
}
}
Can this be done? Can anyone point me to the right direction?
I would try to keep the complexity low and simply implement two functions.
#Controller('test')
export class TestController {
myService: MyService = new MyService();
#Get()
get(#Query('watch') watch: number) {
if (watch) {
return myService.doSomethingB(watch);
} else {
return myService.doSomethingA();
}
}
}
export class MyService {
doSomethingA(): string {
return 'Do not watch me.'
}
doSomethingB(watch: number): string {
return 'Watch me for ' + watch + ' seconds.'
}
}

Resources