kuzzle / react native - Cannot inherits class from BaseController - javascript

I'm trying to extends the KUZZLE JavaScript SDK in order to call some controllers on kuzzle servers, implemented via plugins.
I'm following that guide: add controller
Here is my controller which extends from the BaseController:
const { BaseController } = require('kuzzle-sdk');
export class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And here is where I specify the controller in order to use it further in the App, on the creation of the singleton.
const {UserController} = require('./UserController');
const { Kuzzle, WebSocket } = require('kuzzle-sdk');
class KuzzleService {
static instance = null;
static async createInstance() {
var object = new KuzzleService();
object.kuzzle = new Kuzzle(
new WebSocket('localhost'),{defaultIndex: 'index'}
);
object.kuzzle.useController(UserController, 'user');
await object.kuzzle.connect();
const credentials = { username: 'admin', password: 'pass' };
const jwt = await object.kuzzle.auth.login('local', credentials);
return object;
}
static async getInstance () {
if (!KuzzleService.instance) {
KuzzleService.instance = await KuzzleService.createInstance();
}
return KuzzleService.instance;
}
}
export default KuzzleService;
Somehow I'm getting the following error:
Controllers must inherit from the base controller
Is there something wrong with the imports ?

I've found out the solution to that issue. Firstly, I was not on the right version of the kuzzle SDK released recently (6.1.1) and secondly the controller class must be exported as default:
const { BaseController } = require('kuzzle-sdk');
export default class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And then the UserController needs to be importer that way:
import UserController from './UserController.js'
Then, as specified in the documentation, we need just inject the kuzzle object into the controller that way:
kuzzle.useController(UserController, 'user');

Related

How to call API from redux-saga by effect call when the function to call API is defined in class

I'm trying to call an API by effect call from redux-saga but when will start to call to api throw me an error. Cannot read property 'post' of null
Is really weird because I created a simple function to test my call working
example:
function requestGetUser() {
return axios.request({
method: 'get',
url: 'https://my-json-server.typicode.com/atothey/demo/user',
})
}
export function* fetchLoginSaga(action) {
const { data } = yield call(requestGetUser)
yield put(login({ id: data.id, name: data.firstName }))
}
export function* watcherSaga() {
yield takeLatest(fetchLogin.type, fetchLoginSaga)
}
and with this example above works
but when I tried to call a API from class inside of effect call doesn't works
// Depedencies
import axios from 'axios'
export default class BaseService {
/**
* Default http request instane
* #type { Axios.Instance }
*/
instance
/**
* Creates an instance of BaseService.
* #param { String } endpoint To manipulates the operations.
* #param { String } [baseUrl= ] The base url.
*/
constructor(endpoint, baseURL = process.env.BASE_URL) {
this.endpoint = endpoint
this.instance = axios.create({ baseURL })
this.instance.interceptors.response.use(this.responseInterceptor, this.handleResponseError)
}
/**
* Intercepts every response.
* #param { Object } response The response.
* #returns { Promise<Object> }
*/
responseInterceptor = response => response.data
/**
* Intercepts every error on response.
* #param { Object } error The respone error.
* #returns { Promise<Object> }
*/
handleResponseError = error => {
const {
response: {
data: { message },
},
} = error
return Promise.reject(new Error(message))
}
/**
* Make a post request with data.
* #param { Object } [data={}] The data to send as body.
* #param { Object } [requestParams={}] The params to make the request.
* #return { Promise<any> }
*/
post = (data, { url = this.endpoint, ...rest } = {}) => {
const response = this.instance.post(url, data, { ...rest })
return response
}
}
import BaseService from './baseService'
export class AuthService extends BaseService {
/**
* Initializes Auth Service.
*/
constructor() {
super('/auth/local')
}
/**
* Logs Current user.
* #param { String } identifier - User's Identifier
* #param { String } password - User´s password.
* #return { Promise<String> } jwt access token.
*/
async login(identifier, password) {
const user = await this.post({ password, identifier }, { url: '/auth/local' })// when I call return cant find post of null
return user
}
}
export default AuthService
import axios from 'axios'
import { call, takeLatest } from 'redux-saga/effects'
import { fetchLogin } from './authReducer'
import AuthService from 'api/authService'
const authService = new AuthService()
export function* fetchLoginSaga(action) {
const response = yield call(authService.login, 'email', 'password')
console.log({ response })// don't print
}
export function* watcherSaga() {
yield takeLatest(fetchLogin.type, fetchLoginSaga)
}
enter image description here
behind the scenes, redux-saga will call your function with .apply, missing the this context of the authService object,
detailed information in the repository issue:
https://github.com/redux-saga/redux-saga/issues/27
you have 2 ways to fix that:
Change the call signature to yield call([auth, 'login'], "email", "password");
You can use the apply effect instead - yield apply(auth, auth.login, ["email", "password"]);
or you can bind the parent object to the auth.login function with plain JavaScript:
yield call(authService.login.bind(authService), 'email', 'password')
I would recommend using the correct effect or effect signature for that instead! 😁
the documentation of "context"/"fn" can be found in redux-saga docs:
https://redux-saga.js.org/docs/api/#callcontext-fn-args

How to asynchronuosly contruct a DataSource?

I'm trying to create a DataSources class that interacts with Google Sheets via Sheets API
To start reading sheets, the API needs to do an asynchronous authorization process. How should I write my class since it relies exclusively on this async event?
The auth process goes something like this:
authorize(credentials).then(auth => {
// I have to init the api here because it needs an auth object returned asynchronously
const api = google.sheets({ version: "v4", auth });
});
How should I initialize the class so I can use api inside every method?
import { google } from "googleapis";
import * as fs from "fs";
import { authorize, CRED_PATH } from "../../sheetsAuth";
import { DataSource } from "apollo-datasource";
class SheetsAPI extends DataSource {
sheetID: any;
api: any;
constructor(sheetID) {
super();
this.sheetID = sheetID;
// ideally I initialize the api here, but is asynchronous
authorize(credentials).then(auth => {
this.api = google.sheets({ version: "v4", auth });
});
}
async getRows() {
// ideally I want to have access to the authorized api here but since its async it returns undefined
const r = await this.api.spreadsheets.values.get({
spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
range: "Class Data!A2:E",
});
if (r) {
const rows = r.data.values;
if (rows) {
if (rows.length) {
return rows;
}
}
}
}
}
export { SheetsAPI };
How can I overcome this?
Right now I decided to go with this approach:
Create a special method to initialize the authorization before every method call.
class SheetsAPI extends DataSource {
sheetID: any;
api: any;
constructor(sheetID) {
super();
this.sheetID = sheetID;
}
async init() {
//init returns a promise I can await so I'm sure this.api will be defined before I use it
const auth = authorize(JSON.parse(fs.readFileSync(CRED_PATH, "utf8"))).then(
(auth) => {
this.api = google.sheets({ version: "v4", auth });
}
);
}
async getRows() {
// I have to await this.init every time I need to use this.api
await this.init();
const r = await this.api.spreadsheets.values.get({
spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
range: "Class Data!A2:E",
});
if (r) {
const rows = r.data.values;
if (rows) {
if (rows.length) {
return rows;
}
}
}
}
}

Typescript Singleton Undefined Attribute

I'm trying to create a singleton that has a single amqp connection and when createChannel method is called, it must return a new channel from the same connection:
export interface IBroker {
createChannel(): Promise<IChannel>;
}
export default class Broker implements IBroker {
private static instance: Broker;
private conn: IConnection | undefined;
private constructor(public config: IRabbitMQConfig = new RabbitMQConfig()) {}
/**
* singleton
*/
public static getInstance(): Broker {
if (!this.instance) {
this.instance = new Broker();
}
return this.instance;
}
/**
* initiates configuration on infra service
*/
async createChannel(): Promise<IChannel> {
try {
if (!this.conn) {
this.conn = await this.config.init();
await this.createExchanges();
await this.createQueues();
await this.createBinds();
logger.info('Broker started successfully');
}
if (!this.conn) {
throw new InternalError('Error starting broker. Missing connection!');
}
return await this.conn.createChannel();
} catch (err) {
logger.error('Error trying to start broker', err);
throw new InternalError('Error trying to start broker', 500);
}
}
// code...
the call config.init() returns the amqp connection.
when I test the class like below, every time I call createChannel it creates a new connection!
const a = Broker.getInstance();
const b = Broker.getInstance();
console.log(a === b); // return true
a.createChannel(); // create a new connection
b.createChannel(); // creates another connection
this.conn of Broker class is always undefined when createChannel is called!
I think the issue is that the two synchronous calls to createChannel mean that the first one hasn't initialized the connection by the time the second is called, which leads to 2 connections being created.
If you want to make your createChannel "thread-safe" in terms of creating the connection, you could do something like this (untested):
interface IConnection {
connect: () => void
}
const initConnection = () : Promise<IConnection> => {
return Promise.resolve({
connect: () => {}
});
};
class Broker {
private connection : IConnection | undefined;
private pendingConnection : Promise<IConnection> | undefined;
async createChannel() : Promise<IConnection> {
if (this.connection) {
return this.connection;
}
if (this.pendingConnection) {
return this.pendingConnection;
}
this.pendingConnection = initConnection();
const conn = await this.pendingConnection;
// Do other setup stuff
this.connection = conn;
this.pendingConnection = undefined;
return conn;
}
}

Does extending a base class multiple times uses more memory than a single instance of that class?

I was wondering about the best practice to create an efficient web app. which one of this implementations is more efficient and which one is better in large scale applications?
Every class extends the base request class and has access to request methods:
class BaseRequestClass {
get(url) {
return Promise.resolve() // Some api request
}
}
class Users extends BaseRequestClass {
constructor() {
super()
this.users = []
}
}
class Admins extends BaseRequestClass {
constructor() {
super()
this.admins = []
}
}
const users = new Users();
const admins = new Admins();
users.get('users').then((response) => users.users = response)
admins.get('admins').then((response) => admins.admins = response)
or creating a single instance of BaseRequestClass and use it every time that we want to make a request like this one:
class BaseRequestClass {
get(url) {
return Promise.resolve() // Some api request
}
}
class Users {
constructor() {
this.users = []
}
}
class Admins {
constructor() {
this.admins = []
}
}
const request = new BaseRequestClass()
const users = new Users();
const admins = new Admins();
request.get('users').then((response) => users.users = response)
request.get('admins').then((response) => admins.admins = response)
I prefer to use the first approach, though I would house the specific to each Class within the class instead of passing in as a param.
class BaseRequestClass {
get(url) {
return Promise.resolve() // Some api request, using this.resource
}
}
class Users extends BaseRequestClass {
constructor() {
super()
this.resource = 'users' // <!-- here
this.users = []
}
getAll(){
...
}
}
meaning the usage becomes -
users.getAll()
etc

Nest js POST Request Not Recognizing DTO method

I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.

Categories

Resources