How to access post parameters from bodyParser using moleculer.js - node.js

I am digging into moleculer.js the only thing i am finding difficult to understand;how to get parameters inside actions of a service
below given is my code
const ApiGateway = require("moleculer-web");
module.exports = {
name: "api",
mixins: [ApiGateway],
settings: {
port: process.env.PORT || 3000,
bodyParsers: {
json: true,
urlencoded: { extended: true }
},
routes: [{
path: "/api",
whitelist: [
"**"
]
}],
assets: {
folder: "public"
}
},
};
Below is my user service where i want to get post parameters
module.exports = {
name: "users",
dependencies: ["guard"],
actions: {
create: {
restricted: [
"api"
],
async handler(ctx,route, req, res) {
this.logger.info(req);
this.logger.info("'users.create' has been called.");
const token=await ctx.call("guard.generate",{service:"abc"});
what i want is
const token=await ctx.call("guard.generate",{service:req.body.name});
instead of
const token=await ctx.call("guard.generate",{service:"abc"});
const verify=await ctx.call("guard.check",{token:token});
return [token,verify,req];
}
},
}

MoleculerĀ“s Actions has the following signature: <actionName> (ctx) {// logic} or <actionName>: { handler (ctx) { // logic}}.
So what you have to do is this:
module.exports = {
name: "users",
actions: {
welcome: {
handler(ctx) {
console.log(ctx.params) // Print the request params
// Call other actions ctx.call('serviceName.actionName`, ...data...)
return ctx.params
}
}
}
}
More info about Actions: https://moleculer.services/docs/0.13/actions.html
The function signaturehandler(ctx,route, req, res) is a route hook that is used only in API gateway.
More info about Route hooks: https://moleculer.services/docs/0.13/moleculer-web.html#Route-hooks
Also, the req and res can't be passed to other services because these objects are not serializable.
Anyway, you might consider checking the video tutorial: https://www.youtube.com/watch?v=t4YR6MWrugw
It covers Moleculer's core concepts and shows how to call actions

Related

How to get random records from Strapi v4 ? (I answered this question)

Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html

testing a vue3 component with <router-link> with jest

I got this test from another site. They are injecting a mock route. I think I need to mock router itself or pass a real one into the test so the page can run. There is a way to do this in vue 2, but I haven't found an example for vue 3.
import { mount } from "#vue/test-utils";
import Nav from "./Nav.vue";
test("it displays a menu item", () => {
const mockRoute = {
params: {
id: 1,
},
};
const mockRouter = {
push: jest.fn(),
};
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
});
expect(wrapper.find("#navLabel_0").text()).toEqual("Appointments");
});
The component I'm testing has tags.
The test fails with:
Failed to resolve component: router-link
You have to pass the router-link as a stub: stubs: ['router-link'] when you mount the component:
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
stubs: ['router-link'] });

Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId

I want to do inheritance in Hyperledger Fabric Chaincode using NodeJs.
I have created two classes CommonContract and AdminContract. CommonContract is the base class and AdminContract child class. But I got error when I invoke getLastPatiendId function from AdminContract. Error is as follows.
Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId
[nodemon] app crashed - waiting for file changes before starting...
Even though getLastPatientId function is written in contract it is giving error function does not exists.
Below are the code of AdminContract, CommonContract, index.js file for chaincode and the server API which invoke transaction
CommonContract.js
This is CommonContract smart contract which is a base class.
'use strict';
const { Contract } = require('fabric-contract-api');
class CommonContract extends Contract {
async initLedger(ctx) {
console.info('============= START : Initialize Ledger ===========');
const initData = [
{
"firstName": "ABC",
"middleName": "D",
"lastName": "BCA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
},
{
"firstName": "Five",
"middleName": ".H.",
"lastName": "CDA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
}
]
for (let i = 0; i < initData.length; i++) {
initData[i].docType = 'patient';
await ctx.stub.putState('PID' + i, Buffer.from(JSON.stringify(initData[i])));
console.log('Data Added:---', initData[i]);
}
}
async getPatient(ctx, patientId){
const patient = await ctx.stub.getState(patientId);
if(patient.length || patient.length > 0)
console.log(patient);
let data = JSON.parse(patient.toString());
return data;
}
async getAllPatient(ctx){
const startKey = '';
const endKey = '';
const allResults = [];
for await (const {key, value} of ctx.stub.getStateByRange(startKey, endKey)){
const strValue = Buffer.from(value).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
} catch (err) {
console.log(err);
record = strValue;
}
allResults.push({ Key: key, Record: record });
}
console.info(allResults);
return JSON.stringify(allResults);
}
}
module.exports = CommonContract;
AdminContract.js
This is an AdminContract smart contract that inherits CommonContract and has a single function getLastPatientId() it generally returns the patient id of the last patient created in the ledger.
'use strict';
let Patient = require('./PatientAssets.js');
const CommonContract = require('./CommonContract.js');
class AdminContract extends CommonContract {
async getLastPatientId(ctx) {
let result = await getAllPatient(ctx);
console.log(result);
return result[result.length - 1].patiendId;
}
}
module.exports = AdminContract;
index.js
This file is main entry point all smart contract.
'use strict';
const CommonContract = require('./lib/CommonContract.js');
const AdminContract = require('./lib/AdminContract.js');
module.exports.contracts = [CommonContract, AdminContract];
admin.js
This is server side route file which invoke the getLastPatientId function which is in AdminContract.js smart contract file.
router.get('/getAllPatient', async (req, res) => {
try {
// load the network configuration
const ccpPath = path.resolve(__dirname, '..', '..', 'network-config', 'organizations', 'peerOrganizations', 'doctor.hospital_network.com', 'connection-doctor.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('appUser1');
if (!identity) {
console.log('An identity for the user "appUser" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'appUser1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('hospital');
// Get the contract from the network.
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('getLastPatientId');
const data = JSON.parse(result);
console.log(`Transaction has been evaluated, result is: ${result}`);
// Disconnect from the gateway.
await gateway.disconnect();
return data;
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
});
I found out that evalutateTransaction() invokes the function in CommonContract but was not able to invoke AdminContract functions. Please help me out. What is the error in my code ?
This is because you need to specify the contract name when calling transactions, except for the first contract which is treated as a default. For example, you should be able to successfully call initLedger, CommonContract:initLedger, and AdminContract:getLastPatientId but getLastPatientId will fail because there is no such transaction on the default contract.
You can see what transactions are available, and which contract is the default, by getting the metadata for the contract. You can get the metadata using the org.hyperledger.fabric:GetMetadata transaction, where org.hyperledger.fabric is the system contract and GetMetadata is the transaction. The ccmetadata utility will call that get metadata transaction if that helps.
You can also customise the contract names using a constructor. For example, to call an Admin:getLastPatientId transaction, add the following constructor:
class AdminContract extends Contract {
constructor() {
super('Admin');
}
//...
}
Note: I don't think it's related to your current problem but I'm not sure why you want to use inheritance in this case. It might cause other problems so I would stick to extending Contract as shown above.
First I was implementing multiple contracts.
By getting the metadata for the contracts there is one default contract.
{
CommonContract: {
name: 'CommonContract',
contractInstance: { name: 'CommonContract', default: true },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
AdminContract: {
name: 'AdminContract',
contractInstance: { name: 'AdminContract' },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
DoctorContract: {
name: 'DoctorContract',
contractInstance: { name: 'DoctorContract' },
transactions: [ [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
PatientContract: {
name: 'PatientContract',
contractInstance: { name: 'PatientContract' },
transactions: [ [Object], [Object] ],
info: { title: '', version: '' }
},
'org.hyperledger.fabric': {
name: 'org.hyperledger.fabric',
contractInstance: { name: 'org.hyperledger.fabric' },
transactions: [ [Object] ],
info: { title: '', version: '' }
}
}
Now in the admin.js file, looking at the line where we getting the contract
const contract = network.getContract('basic');
Here this statement was using default contract i.e. CommonContract, But I was implementing multiple contracts so I have to give the reference of that contract, so I make this change.
const contract = network.getContract('basic', 'AdminContract');
and now when I invoke the contract
const result = await contract.evaluateTransaction('getLastPatientId');
or
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('AdminContract:getLastPatientId');
It works...

Custom formatter for fastify

I want to add a custom schema formatter for fastify.
import fastify from 'fastify'
import AjvCompiler from '#fastify/ajv-compiler'
const ajvFormatter = AjvCompiler(ajv);
ajvFormatter.addFormat('new-format', /hello/);
const app = fastify({
schemaController: {
compilersFactory: {
buildValidator: ajvFormatter
}
}
})
I add the format but still gives the error:
Failed building the validation schema for POST: /hey, due to error unknown format
"new-format" ignored in schema at path
I guess latest fastify does not support this functionality.
You are using in the wrong way the #fastify/ajv-compiler module. It does not accept an ajv input parameter at all. not it exports an addFormat method.
You need to use the customOption option:
const fastify = require('fastify')
const app = fastify({
logger: true,
ajv: {
customOptions: {
formats: {
'new-format': /hello/,
},
},
},
})
app.get(
'/:hello',
{
schema: {
params: {
type: 'object',
properties: {
hello: {
type: 'string',
format: 'new-format',
},
},
},
},
},
async (request, reply) => {
return request.params
}
)
app.listen(8080, '0.0.0.0')
// curl http://127.0.0.1:8080/hello
// curl http://127.0.0.1:8080/hello-foo

Node.js - Joi schema: Check req.value & ip not working

I have this joi schema to validate email and password that was sent via body as json. In addition I now want to check the ip address. I tried it like below but I get this:
{
"isJoi": true,
"name": "ValidationError",
"details": [
{
"message": "\"ip\" is required",
"path": [
"ip"
],
"type": "any.required",
"context": {
"key": "ip",
"label": "ip"
}
}
],
"_object": {
"email": "loremlipsum02#abc.at",
"password": "kyuser2923?"
}
}
Here my joi schema in routehelpers.js
const Joi = require('joi');
module.exports = {
validateSignup: (schema) => {
return (req, res, next) => {
const result = Joi.validate(req.body, schema);
if (result.error) {
return res.status(400).json(result.error);
}
if (!req.value) { req.value = {}; }
req.value['body']= result.value;
next();
}
},
schemas: {
signupSchema: Joi.object().keys({
email: Joi.string().email().required(),
password: Joi.string().required(),
ip: Joi.string().ip({
version: [
'ipv4',
'ipv6'
],
cidr: 'required'
}).required()
})
}
}
When I insert in my controller:
const curIP = req.ip;
console.log('IP: '+curIP);
I do get the ip in the console:
Server running at http://127.0.0.1:4002/
IP: 127.0.0.1
But how in addition can I validate the ip from the server requesting a signup?
I suppose it is because it is checking everything in req.value.body . But how do I need to alter validateSignup: (schema) to also check the ip that is requesting signup?
Looks like you are trying to match ip property in body while it's a property of req object.
Try:
const result = Joi.validate(Object.assign(req.body, { ip: req.ip }), schema);
Although I think better approach would be using a library like proxy-addr. That is going to extract correct client IP even when your server runs behind a reverse proxy.

Resources