I'm new to Sails (and a jr. dev to boot) and have been working through the Sailsjs in Action book. Given some previous experience with TypeScript I wanted to experiment with the framework in that capacity. However I've hit a road block when trying to return response statuses and information via my API.
Every time I try to use the res in my controller I receive a:
TypeError: Cannot read property 'ok' of undefined. Based on the book and the documentation, I'm under the impression that would get set automatically per this example from the docs:
await User.create({name:'Finn'});
return res.ok();
and this example from the book:
signup: function(req, res) {
var options = {**grab request data**};
User.create(options).exec(function(err, createdUser){
if(err){
return res.negotiate(err);
}
return res.json(createdUser);
}
So I feel like I'm missing something pretty obvious but I'm not sure what. The project compiles just fine and I've got the documented typescript libraries installed/configured. Even matching that function there returns the same TypeError to me.
Another set of eyes would be greatly appreciated. Controller code below.
declare const sails: any;
import { boatInterface } from '../../interfaces/boat';
module.exports = {
friendlyName: 'new-boat',
description: 'create a new boat model',
inputs: {
modelName:{
required: true,
type: 'string',
},
yearBuilt:{
required: true,
type: 'number'
}
},
exits: {
success: {
description: 'New boat model was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided boat info was invalid.'
},
modelAlreadyCreated: {
statusCode: 409,
description: 'The provided boat model has already been
created.',
},
},
fn: async function (req: any, res: any){
console.log('building boat');
let boatRequest: boatInterface = {
modelName: req.modelName.toLowerCase(),
yearBuilt: req.yearBuilt
}
//confirming data has been formatted correctly
console.log(boatRequest);
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
//confirming new boat exists
console.log(newBoat);
console.log("request successful");
//res remains undefined and throws an error on attempted return
console.log(res);
return res.ok();
}
};
Here's the error with some console logs included. Thanks in advance!
building boat
{ modelName: 'kraken', yearBuilt: 1337 } <-- Request formats correctly
{ createdAt: 1566173040652,
updatedAt: 1566173040652,
id: 6,
modelName: 'kraken',
yearBuilt: 1337 } <-- new db entry is returned via fetch()
request successful
undefined <-- attempt to log the res returns undefined
(node:3738) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'ok' of undefined
at Object.<anonymous> (/Users/pitterpatter/Repos/learn/sails-learn/freeform/api/controllers/boat/new-boat.ts:67:20)
It looks like you're using actions2.
Your handler function will be given 2 parameters - inputs and exits as you've defined in the objects above it.
fn: async function (inputs: any, exits: any) {
inputs will be an object that contains parmeters given by a user that you've defined in the inputs part of your action (form data/query parameters/route parameters).
In this case it'd contain a modelName and yearBuilt, something like
{ modelName: 'lemon', yearBuilt: 2012.3 }
exits will contain a few default methods - exits.success() and exits.error(), as well as the invalid and modelAlreadyCreated which you've defined.
TLDR try this
fn: async function (inputs: any, exits: any) {
let boatRequest: boatInterface = {
modelName: inputs.modelName.toLowerCase(),
yearBuilt: inputs.yearBuilt
}
let newBoat = await sails.models.boat.create(boatRequest)
.intercept('E_UNIQUE', 'modelAlreadyCreated')
.fetch();
return exits.success(newBoat);
}
You can access the "raw express response object" deely that you're looking for by using this.res.ok(...) or this.req.something, but it's recommended you use the appropriate "exit" instead.
Related
I'm maintaining a bot for Wikipedia using npm mwbot, but now I'm migrating to mwn. I'm experiencing some strange behaviour of mwn; that is, a POST request fails because of a 414 error.
Let's say you have some list of usernames from a wiki project and you want to check if any of the users is blocked from editing. This can be done by mediawiki action API list=blocks with the "bkusers" parameter specified with a pipe- (or "|"-) separated list of usernames, and bots can query 500 usernames with one API call. The following TypeScript code should do the job, but the relevant query fails because of 414. (Since it's a POST request, I have absolutely no idea why.)
import { mwn } from "mwn";
(async () => {
const mw: mwn = await mwn.init({
apiUrl: 'https://en.wikipedia.org/w/api.php',
username: 'YourBotUsername',
password: 'YourBotPassword',
userAgent: 'myCoolToolName 1.0 ([[link to bot user page or tool documentation]])'
});
interface DynamicObject {
// eslint-disable-next-line #typescript-eslint/no-explicit-any
[key: string]: any
}
interface ApiResponseListLogeventsItem {
logid: number,
ns: number,
title: string,
pageid: number,
logpage: number,
params: DynamicObject,
type: string,
action: string,
user: string,
anon?: boolean,
timestamp: string,
comment: string
}
// eslint-disable-next-line #typescript-eslint/no-empty-interface
interface ApiResponseListLogevents extends Array<ApiResponseListLogeventsItem> {}
// Get 500 (5000 if you're a bot) usernames from the latest logs of account creation
const response = await mw.continuedQuery({
action: 'query',
list: 'logevents',
leprop: 'ids|title|timestamp',
letype: 'newusers',
lelimit: 'max',
}, 1).then(res => res).catch(err => console.log(err.info));
if (!response) return;
const resLgev: ApiResponseListLogevents = response.filter((obj: DynamicObject) => obj && obj.query && obj.query.logevents).map(obj => obj.query && obj.query.logevents).flat();
const users = resLgev.filter(obj => obj.title).map(obj => obj.title.replace(/^User:/, ''));
console.log(users.length);
// Check if any of the users is blocked
const r = await mw.request({ // => Returns 414 'URI Too Long' although this is a POST request
action: 'query',
list: 'blocks',
bklimit: 'max',
bkusers: users.slice(0, 500).join('|'), // Pipe-separated list of users
bkprop: 'user|timestamp|expiry|restrictions|flags',
formatversion: '2'
}, {method: 'POST'}).then(res => res).catch(err => console.log(err));
if (!r || !r.query || !r.query.blocks) return console.log('!');
console.log(r.query.blocks.length);
})();
The first half of the code isn't that important because it's just there to fetch some random usernames from the API. The problem is with mw.request(). An equivalent code with the mwbot framework works so this doesn't seem to be a server-side issue. I would appreciate any comments on what'd be wrong.
I don't know if this is relevant but the issue might come from axios, which mwn uses for HTTP requests. Yesterday I wrote a function to scrape a webpage, and at first used axios. But it constantly spit "Package subpath './lib/defaults' is not defined by 'exports'" error so I uninstalled it and am now using node:https to fetch a website's html. But as I just said above, I don't know if this is relevant in any way.
I have a similar to this question, however I don't see how it could be resolved using this other syntax currently in my Apollo project.
The issue is '0000-00-00' values coming from SQL db to my Apollo GraphQL server, thus throwing errors of invalid DateTime type. I'd like to write a resolver for the datetime field in question, like:
import gqlDate from 'graphql-iso-date';
const MyType = new gql.GraphQLObjectType({
name: 'Type',
description: 'This is a GraphQL type.',
fields: () => {
return {
datetime: {
type: gqlDate.GraphQLDateTime,
description: 'The datetime field of my type.',
resolve: (record) => {
return record === '0000-00-00 00:00:00' ? null : record;
}
}
};
}
});
However I cannot adjust this example to this other syntax my project is setup to:
import { gql } from 'apollo-server'
export const schema = gql`
extend type Query {
users(id: ID): [User!]
}
type User {
id: ID!
first_name: String!
middle_name: String
birthday: String!
}
import graphqlFields from 'graphql-fields'
export const usersResolver = {
Query: {
users: async (_, params, context, info) => {
const requestedColumns = Object.keys(graphqlFields(info))
return context.dataSources.db.getUsers(params, requestedColumns)
},
},
}
I played around with adding something like this to the resolver, but I'm not sure if it's the right thing to do and how exactly to implement it. Since the field resolves to Scalar, my guess is the below code won't be reached.
birthday: (parent, params, context, info) => { ... },
I also tried adding a separate query for the field, but am not sure how to connect it to the original users one.
My final resort is to just query the birthday as String and handle it on the front end, but that seems to be totally inappropriate compared to resolving it in GraphQL.
I'm trying to write a unit test to the following saga :
function * verifyCode(api){
let action = yield take(LoginTypes.VERIFY_CODE)
const phoneNumber = yield select(phoneNumberSelector)
try{
const response = yield call(api.verifyCode, phoneNumber, action.code)
if (response.ok){
let token = response.data.token
yield put(LoginActions.verifyCodeSuccess(response.data.token))
}
else {
yield put(LoginActions.verifyCodeFailure(response.error))
}
}
catch(error){
yield put(LoginActions.verifyCodeFailure(error))
}
}
All the tests pass up until the 'yield call' part, using the following (using test from 'tape'):
test('verify code flow', (assert) => {
let number = '0000000'
let code = '00000'
const gen = verifyCode(api)
assert.deepEqual(
gen.next({code: code}).value,
take(LoginTypes.VERIFY_CODE),
'wait for verify code action from user',
)
assert.deepEqual(
gen.next().value,
select(phoneNumberSelector),
'get phoneNumber from login state'
)
assert.deepEqual(
gen.next().value,
call(api.verifyCode, number, code),
'call verify code'
)
assert.end()
})
The error I get for the failure of this test is
operator: deepEqual
expected: |-
{ '##redux-saga/IO': true, CALL: { context: null, fn: [Function: verifyCode], args: [ '0000000', '00000' ] } }
actual: |-
{ '##redux-saga/IO': true, PUT: { channel: null, action: { type: 'VERIFY_CODE_FAILURE', error: [TypeError: Cannot read property 'code' of undefined] } } }
What is the correct way to write to an api call using the 'call' effect?
How can I test the different possible flows depending on the ''response I get?
This is the correct way to use the call effect on an API
To test different flows (Success | Failure) you can use the following pattern (in Jest):
expect(generator.next().value.PUT.action).toMatchObject({type: types.VERIFY_CODE_SUCCESS, payload: {}, meta: { section }});
expect(generator.throw('error').value.PUT.action).toMatchObject({ type: VERIFY_CODE_FAILURE, payload: 'error'});
BTW: It seems that your test fails due to an error thrown when you called your API endpoint. Make sure you get the desired response from your endpoint.
Good Luck!
I've setup a simple decorator for my graphql mutations, all it's supposed todo is run an auth check on the request to see if it's authorised or not. I've got it to run the check & return an error if everything goes to plan, but when there is no error (request is authorised), it just hangs.
Here is my decorator code:
function checkAuth(target) {
target.mutateAndGetPayload = (input, { rootValue }) => {
if (!rootValue.request.user) {
return {
error: {
message: `Invalid authorization header`,
type: "invalid_auth",
client_message: `You do not have permission to access this resource`,
}
};
}
return target.mutateAndGetPayload;
};
return target;
}
And here is my mutation code (just a simple mutation):
const AddMedicalRecord = mutationWithClientMutationId({
name: 'AddMedicalRecord',
description: 'AddMedicalRecord',
inputFields: {
...fields here...
},
outputFields: {
error: {
type: ErrorType, // Simply: { client_message, message, type }
description: 'Description of the error if anything',
resolve: payload => payload.error
},
success: {
type: GraphQLString,
description: 'Description of the success',
resolve: payload => payload.success
}
},
#checkAuth
mutateAndGetPayload: (input, { rootValue }) => {
console.log(input, 'passed decorator :D ') // It never reaches here with decorator
It never reaches that console.log IF the request is authorized and the decorator does not return an error. Any help is appreciated as I've only used decorators a handful of times. I feel like it's something todo with the way I'm returning inside the target.mutateAndGetPayload
I am using callbacks with socket.io
Client code :
socket.emit('someEvent', {data:1}, function(err, result) {
console.log(err.message);
});
Server code :
socket.on('someEvent', function(data, callback) {
callback(new Error('testing error'));
});
With the above code the client side always prints out undefined. If I change the server side code to the following I can see the error message.
socket.on('someEvent', function(data, callback) {
callback({message:'testing error'});
});
I can pass my own custom objects to the client just fine, just not the error object. Any ideas?
socket.io data is serialized as JSON, which can only represent plain objects. You will need to serialize any errors into a recognizable plain-object format, or let Socket.IO's standard serialization do its thing (which will result in a plain empty object for Error instances.
I also think it's odd that Socket.IO doesn't seem to provide explicit built in support for passing Error objects in a meaningful way.
If you want to have Error objects seralized correctly in Socket.IO callbacks you can define a method like this to specify how serialization with .toJSON for Error messages should be handled:
if (!('toJSON' in Error.prototype)) {
Object.defineProperty(Error.prototype, 'toJSON', {
value: function () {
let result = {}
Object.getOwnPropertyNames(this).forEach((key) => {
if (key === 'stack') return
result[key] = this[key];
}, this)
return result
},
configurable: true,
writable: true
})
}
If you are throwing messages from the server you will want to define this on the server, and if throwing errors from a client to the server you will need to define it on the client too.
Note: In this example it strips the 'stack' property when returning errors to the client (to avoid exposing internals to a client), but this may be something worth leaving in, at least for development mode.
Update: This is a lot easier with ES6
Adapted from a blog post I wrote recently on this:
https://medium.com/#iaincollins/error-handling-in-javascript-a6172ccdf9af
I'm happy to say this easier/cleaner with ES6.
If you define your class extending Error like this:
class ValidationError extends Error {
constructor(message) {
super(message)
this.name = 'ValidationError'
this.message = message
}
toJSON() {
return {
error: {
name: this.name,
message: this.message,
stacktrace: this.stack
}
}
}
}
Instead of this being the object that gets passed back:
{ name: 'ValidationError' }
It now looks something like this:
{
error: {
name: 'ValidationError',
message: 'A validation error',
stacktrace: '…'
}
}
You can see what to expect by doing this:
console.log(JSON.stringify(new ValidationError('A validation error'))
Here's a little router function that works with plain error objects. You might want to remove the stack variable if you're shy.
socket.on('api', async (method, args, callback) {
try {
const result = await api[method](...args)
callback(null, result)
} catch (error) {
const { name, message, stack } = error
callback(error.toJSON ? error : { name, message, stack })
}
})