Best practice for defining global variables/enums in Sails.JS? - node.js

I'm using helpers/log.js to manage all my console logs and I want to define a LogTypes enum so that it can be accessed from any controller or model when logged.
// Usage
await log('info', util.format('Attempting to create a new user with UserId: %s', req.body.userId));
// helpers/log.js
var moment = require("moment");
var util = require("util");
module.exports = {
friendlyName: 'Log',
description: 'Log something.',
inputs: {
logType:{
type: 'string',
isIn: ['info', 'warn', 'err'],
defaultsTo: 'info'
},
message: {
friendlyName: 'Log message',
description: 'The log message to output to the console.',
type: 'string',
defaultsTo: ''
}
},
exits: {
},
fn: async function (inputs, exits) {
sails.log(util.format('%s: %s: %s', moment(), inputs.logType,
inputs.message));
// All done.
return exits.success();
}
};

Related

Ajv and Ajv-errors

I am using the ajv errors plugin for fastify to throw schema errors for required properties however every error is prefixed with 'body' then my error message. Is there any way to remove the schema prefix from errors?
example:
body: {
type: 'object',
properties: {
title: {
type: 'string',
description: "The title of the thing",
},
required: ['title'],
errorMessage: {
required: {
title: 'BEEP BOOP TITLE NEEDED!',
},
},
},
my fastify config:
const app = fastify({
ajv: {
customOptions: {
allErrors: true,
},
plugins: [(ajv) => AjvErrors(ajv, { singleError: false, keepErrors: false })],
},
});
Expected error for missing title in a request: 'BEEP BOOP TITLE NEEDED!'
Actual: 'body BEEP BOOP TITLE NEEDED!''
fastify: "4.1.0"
ajv-errors: "3.0.0"
What you can do is a simple trick with fastify ;)
fastify.setErrorHandler(function (error, request, reply) {
// Log error
this.log.error(error);
const err = error;
if (error?.validation?.length) {
err.message = error.validation[0].message;
}
// Send error response
return reply.status(error.statusCode || 400).send(err);
});
There is the schemaErrorFormatter option
const fastify = Fastify({
schemaErrorFormatter: (errors, dataVar) => {
// errors = ajv errors
// dataVar = `body` string
return new Error(myErrorMessage)
}
})

Casting error while saving in the database

I am using Angular as frontend and NodeJS for the backend.
I have a route that saves the data received from the frontend to the database. When I execute the save() method, I get prompted the following error:
err : ValidationError: conf.0: Cast to [Boolean] failed for value "[ {
name: 'v', percentage: 2, type: false, status: true } ]" (type string)
at path "conf.0"
Below is the route that stores the data:
app.post("/api/submitTaxCollection", (req, res) => {
console.log(req.body);
const submitTaxSchema = new addTaxesSchema(req.body);
try {
submitTaxSchema.save(function (err) {
if (err) return console.log("err : " + err);
});
} catch (error) {
console.log("ERROR : " + error);
return res.send(error);
}
});
and this is the schema.ts file:
var mongoose = require("mongoose");
//Define a schema
var taxSchema = mongoose.Schema;
var AddTaxSchema = new taxSchema({
parentId: String,
conf: [
{
name: String,
percentage: Number,
type: Boolean,
status: Boolean,
},
],
});
var newTaxesSchema = mongoose.model("addTaxSchema", AddTaxSchema);
module.exports = newTaxesSchema;
In Angular, model is setup as below:
export class TaxRatesConfigurationsModel {
name: string = "";
percentage: number = 0;
type: boolean = false;
status: boolean = true;
}
export class TaxRatesModel {
parentId: string = "";
conf: TaxRatesConfigurationsModel[] = [];
}
and I am calling the API as below:
this._httpService
.post(environment.url + "/api/submitTaxCollection", request)
.subscribe((data) => {
console.log(data);
});
when I console.log(req.body);, I get the following printed to the console (Nodejs):
{
parentId: '23948923nur8cw9yicnyu',
conf: [ { name: 'v', percentage: 2, type: false, status: true } ]
}
and the error occurs in Nodejs
What is causing this weird issue?

How to execute a mutation in GraphQL?

In GraphQL we have basically two types of operations: queries and mutations. While queries are well described in the documentation and there are many examples of them, I'm having a hard time to understand how to execute a mutation. Mutations obviously are update methods.
I've created very simple Node.js server:
var express = require("express");
var graphqlHTTP = require("express-graphql");
var graphql = require("graphql");
var inMemoryDatabase = require("./inMemoryDatabase").inMemoryDatabase;
var _ = require("lodash-node");
var userType = new graphql.GraphQLObjectType({
name: "User",
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
}
});
var queryType = new graphql.GraphQLObjectType({
name: "Query",
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: function(parent, { id }) {
return _.find(inMemoryDatabase, { id: id });
}
}
}
});
var mutationType = new graphql.GraphQLObjectType({
name: "Mutation",
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
},
resolve: function(parent, { id, name }) {
var index = _.findIndex(inMemoryDatabase, { id: id });
inMemoryDatabase.splice(index, 1, { id: id, name: name });
return _.find(inMemoryDatabase, { id: id });
}
}
}
});
var schema = new graphql.GraphQLSchema({
query: queryType,
mutation: mutationType
});
var app = express();
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
graphiql: true
})
);
var port = 9000;
if (process.env.PORT) {
port = process.env.PORT;
}
app.listen(port);
console.log("Running a GraphQL API server at localhost:" + port + "/graphql");
In memory database is just in an array of User objects {id, name}:
var inMemoryDatabase = [
{
id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8",
name: "Mark"
},
{
id: "2fb6fd09-2697-43e2-9404-68c2f1ffbf1b",
name: "Bill"
}
];
module.exports = {
inMemoryDatabase
};
Executing query to get user by id looks as follows:
{
user(id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8"){
name
}
}
How would the mutation changing user name look like?
Hey may completely be missing what you are saying, but the way that I look at a mutation is like this
I get some arguments and a field, that is the same thing as params and a path in rest, with those i do something (in your case lookup the user and update the attribute based on the arguments passed in
After That, i return something from the resolve function that will fulfill the type you specify in the type of the mutation
var mutationType = new graphql.GraphQLObjectType({
name: "Mutation",
fields: {
user: {
// You must return something from your resolve function
// that will fulfill userType requirements
type: userType,
// with these arguments, find the user and update them
args: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
},
// this does the lookup and change of the data
// the last step of your result is to return something
// that will fulfill the userType interface
resolve: function(parent, { id, name }) {
// Find the user, Update it
// return something that will respond to id and name, probably a user object
}
}
}
});
Then with that as a context, you pass some arguments and request back a user
mutation updateUser {
user(id: "1", name: "NewName") {
id
name
}
}
In a normal production schema you would also normally have something like errors that could be returned to convey the different states of the update for failed/not found
#Austio's answer was pretty close, but the proper way is:
mutation updateUser {
user(id: "31ce0260-2c23-4be5-ab78-4a5d1603cbc8", name: "Markus") {
id
name
}
}
if we connect directly with MongoDB below will help you.
mutation {
taskTrackerCreateOne
(
record:
{
id:"63980ae0f019789eeea0cd33",
name:"63980c86f019789eeea0cda0"
}
)
{
recordId
}
}

TypeScript, Mongoose, Jasmine - requiring schemas

I've been struggling for days with how to build a proper test workflow. I realise that tests should mock database dependencies but the case is that I need to test the whole process, together with real db queries.
In the following code I need to require my model so that I could perform operations on db such as deleting test data and pushing test data.
The questions are:
What is the proper way of referencing the schema/model?
Or is there a way of writing Jasmine tests using typescript?
The code does not work, as it says that BucketConfigS.remove is not a function:
'use strict';
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema');
describe('Bucket config collection:', () => {
describe('GetAll service -', () => {
describe('Given that there are no configs', function () {
beforeEach(done => {
done();
});
afterEach(done => {
BucketConfigS.remove({}, done);
done();
});
it('should return an empty array', function () {
// test case
});
});
});
});
I also tried the following require line:
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema').default;
However it brokes the whole test suite (no test results are written out).
The schema file looks like this:
"use strict";
var DataAccess_1 = require("./../common/DataAccess");
var mongoose = DataAccess_1.DataAccess.mongooseInstance;
var mongooseConnection = DataAccess_1.DataAccess.mongooseConnection;
var BucketConfigSchema = (function () {
function BucketConfigSchema() {
}
Object.defineProperty(BucketConfigSchema, "schema", {
get: function () {
var schema = mongoose.Schema({
AppName: {
type: String,
required: true
},
Platform: {
type: String,
required: true
},
Segment: {
type: String,
required: true
},
UpdateTime: {
type: Date,
default: Date.now
}
});
return schema;
},
enumerable: true,
configurable: true
});
return BucketConfigSchema;
}());
var BucketConfig = mongooseConnection.model("BucketConfig", BucketConfigSchema.schema);
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = BucketConfig;
This is a compilation result of:
import { DataAccess } from "./../common/DataAccess";
import { IBucketConfig } from "./IBucketConfig";
let mongoose = DataAccess.mongooseInstance;
let mongooseConnection = DataAccess.mongooseConnection;
class BucketConfigSchema {
static get schema() {
let schema = mongoose.Schema({
AppName: {
type: String,
required: true
},
Platform: {
type: String,
required: true
},
Segment: {
type: String,
required: true
},
UpdateTime: {
type: Date,
default: Date.now
}
});
return schema;
}
}
let BucketConfig = mongooseConnection.model<IBucketConfig>("BucketConfig", BucketConfigSchema.schema);
export default BucketConfig;
Not sure why, but
let BucketConfigS = require('../dist/app/BucketConfig/BucketConfigSchema').default;
started working...

GraphQL - passing an ObjectType a parameter

I'm using GraphQL and it's working great, however, I can't seem to figure out how to pass a parameter into the fields section of my Event GraphQLObjectType.
I would like to be able to pass in the currentUserId (which is given to me through a token) into the Event GraphQLObjectType so I can add in an isAttending attribute.
I've attached code with comments of what I'm basically trying to do:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: (currentUserId) => { // currentUserId is the parameter I would like to pass in
return {
id: {
type: GraphQLInt,
resolve (event) {
return event.id;
}
},
title: {
type: GraphQLString,
resolve (event) {
return event.title;
}
},
attendees: {
type: new GraphQLList(User),
resolve (event) {
return event.getAttendees()
}
},
// this is what I would like to do
isAttending: {
type: GraphQLBool,
resolve (event) {
return event.getAttendees({
where: {
id: currentUserId // that's the parameter I would like pass in
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
)};
}
}
// end of what I'm trying to do //
};
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => {
return {
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
// here is the parameter I would like to pass to the event object
let currentUserId = root.userId;
////////
return Db.models.event.findAll({ where: args });
}
},
...
Update
The reason I can't just do data.currentUserId = root.userId, is because it's not visible when I'm returned a collection of event objects, since what is passed into my Event GraphQLOBjectType is only the {event} object.
What it looks like when I do data.currentUserId and there is an array of objects inside data is this:
[{objects}, currentUserId: 1]
As opposed to what we want which is this:
[{object, currentUserId: 1}, {anotherObject, currentUserId: 1}]
If I wanted to have access to the currentUserId in the Event GraphQLObject, the only thing I can think of is to loop through every object and add the currentUserId onto it like this:
return events.map(event => {
event.currentUserId = currentUserId;
return event;
});`
Is this the best solution?
I'm afraid you can't do that. fields doesn't recieve any parameters, so you won't send any either.
Fortunately, you can achieve that in more convenient way.
Everything your parent type (Query) returns in resolve function is visible in child resolve's root parameter.
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => ({
events: {
type: new GraphQLList(Event),
args: {
id: {
type: GraphQLInt
}
},
resolve (root, args) {
return Db.models.event.findAll({ where: args })
.then(data => {
// pass the parameter here
data.currentUserId = root.userId;
return data;
});
}
},
...
Then your Event object would look like this:
const Event = new GraphQLObjectType({
name: 'Event',
description: 'This represents an Event',
fields: () => ({
...
isAttending: {
type: GraphQLBool,
resolve: (event) => {
return event.getAttendees({
where: {
id: event.currentUserId // that's the parameter you've passed through parent resolve
}
}).then(attendee => {
return (attendee.length > 0 ? true : false);
});
}
}
})
});

Resources