I am using Graphene-Django and trying to query a mutation.
The client side is like this:
sendMessage = async () => {
const mutation = `mutation sendMessage($text: String!) {
createMessage(text: $text roomSlug: "${this.state.slug}") {
ok
message {
slug
createdDate
text
creator {
firstName
lastName
username
profilePictures {
file
pictureNumber
}
}
}
}
}
`;
const { message, ok } = await apolloClient.mutate(mutation, {text: this.state.text})
.then(result => result.data.createMessage);
The server side is like this:
class CreateMessage(graphene.Mutation):
class Arguments:
text = graphene.String(required=True)
room_slug = graphene.String(required=True)
ok = graphene.Boolean()
message = graphene.Field(MessageType)
def mutate(root, info, text, room_slug):
if info.context.user.is_authenticated is False:
raise PermissionDenied('Login required')
ok = False
room = get_object_or_404(models.Room, slug=room_slug)
message = models.Message(room=room, creator=info.context.user, text=text)
message.save()
ok = True
return CreateMessage(ok=ok, message=message)
I have no idea what things got wrong here. The mutation works when I open a built-in graphql admin view and send it through it. However, it does not work when I send the mutation through the real app and throw this error:
Exception: Received incompatible instance “User Model 1”
So I assumed it should be a client error, but apparently, the error comes from Graphene-Python, which is the server side.
I defintely have both
class MessageType(DjangoObjectType):
class Meta:
model = models.Message
and
class UserType(DjangoObjectType):
class Meta:
model = models.User
UserType is for a creator and MessageType is for a message.
Plus: the UserType works fine in every other queries and mutations. The only place it does not work is this specific mutation.
If anyone runs into this problem, change
message = graphene.Field(MessageType)
to
message = graphene.List(MessageType)
it should work.
Related
I'm creating an application that is using Nestjs with websockets, but now I need to add rate limit on the sockets, but analyzing the documentation documentation link and implementing what it says in it, when I use #UseGuards(MyGuard) an error occurs in the application.
My Guard:
#Injectable()
export class NewThrottlerGuard extends ThrottlerGuard {
protected async handleRequest(
context: ExecutionContext,
limit: number,
ttl: number,
): Promise<boolean> {
console.log('Request');
const client = context.switchToWs().getClient();
const ip = client.conn.remoteAddress;
const key = this.generateKey(context, ip);
const ttls = await this.storageService.getRecord(key);
if (ttls.length >= limit) {
throw new ThrottlerException();
}
await this.storageService.addRecord(key, ttl);
return true;
}
}
Websocket:
#UseGuards(NewThrottlerGuard)
#SubscribeMessage('sendMessage')
sendMessage(
#ConnectedSocket() client: Socket,
#MessageBody() message: string,
) {
client.rooms.forEach((room) => {
if (room !== client.id) {
client.broadcast.to(room).emit('message', message);
}
});
}
Error in console:
/node_modules/#nestjs/common/utils/validate-each.util.js:22
throw new InvalidDecoratorItemException(decorator, item, context.name);
^
Error: Invalid guard passed to #UseGuards() decorator (ChatGateway).
at validateEach
The file in: #nestjs/common/utils/validate-each.util.js:22
function validateEach(context, arr, predicate, decorator, item) {
if (!context || !context.name) {
return true;
}
console.log(context, arr)
const errors = arr.some(str => !predicate(str));
if (errors) {
throw new InvalidDecoratorItemException(decorator, item, context.name);
}
return true;
}
i put some console.log then in the terminal it show:
[Function: ChatGateway] [ undefined ]
In Github Throttler documentation they say: You cannot bind the guard with APP_GUARD or app.useGlobalGuards() due to how Nest binds global guards.
So, im using #UseGuards()
The guard itself was written correctly, but it was put in a location that importing it made a circular reference between files, so when #UseGuards() was used it became #UseGuards(undefined) which caused the cryptic error message. Moving the guard to a dedicated file will fix the error
I follow your github reference settings and it doesn't work,The following is my code, where is my setting wrong, and the request to ws is not intercepted(In the handleRequest method)
NestJs:
How can I use validation like this:
#UsePipes(ValidationPipe)
in provider?
in file task.serive.ts tried to use #UsePipes(ValidationPipe) before method, but it doesn't appear
you can use like this,
export class LoginDto {
// validation decorator to check for an email field!
#IsEmail()
readonly email: string;
// validation decorators for password field!
#IsNotEmpty()
#IsString()
readonly password: string;
constructor(credentials: CredentialsInterface) {
if (credentials) {
this.email = credentials.email;
this.password = credentials.password;
}
}
}
in your service,
import { validate } from 'class-validator';
const credentials = new LoginDto(req.body);
const errors = await validate(credentials);
if (errors.length) {
throw new BadRequestException({
statusCode: HttpStatus.BAD_REQUEST,
error: HttpErrors.BAD_REQUEST,
message: errors,
});
}
You can't use Nest Enhancers from providers. They only bind to Controller,s Resovlers, and Gateways, as that is where the request enters and exists the server from. To get around this, at least class-validator, you can instantiate your own Validator class and run the validations.
I'm developing a bot using the bot framework for node.js v4. Imagine the following scenario:
user: Hello
bot: How can I help you?
user: What is the deadline for completing the transfer?
bot: What is the value of the transfer?
user: $ 5,000
At this time, I am executing the textprompt to request the value of the transfer and I need to validate if the user entity ($ 5,000) has been identified as the money entity.
This is the dialog stack:
this.addDialog(new WaterfallDialog(DUVIDA_NIVEL_APROVACAO_DIALOG, [
this.initializeStateStep.bind(this),
this.moneyStep.bind(this),
this.captureMoney.bind(this),
this.endConversation.bind(this)
]));
this.addDialog(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput));
And the validate method:
async validateMoneyInput(validatorContext) {
const value = validatorContext.recognized.value; //how to get entities?
if (value == 'money') {
return VALIDATION_SUCCEEDED;
} else {
await validatorContext.context.sendActivity(`What is the value of the transfer?`);
return VALIDATION_FAILED;
}
}
However, in the callback to validate the textprompt, I have only the text sent by the user.
How can I get the entities extracted by Luis within the textprompt validation method?
To get any LUIS results into the dialog waterfall, you first need to capture the results on the turnContext, like so:
if (turnContext.activity.type === ActivityTypes.Message) {
// Returns LUIS matched results
const results = await this.luisRecognizer.recognize(turnContext);
// Results are assigned to the turnContext object and is passed into the dialog stack
turnContext.topIntent = results.luisResult.topScoringIntent;
turnContext.topIntent.entities = results.luisResult.entities;
turnContext.topIntent.value = results.luisResult.query;
// Create a dialog context object.
const dc = await this.dialogs.createContext(turnContext);
const utterance = (turnContext.activity.text || '').trim().toLowerCase();
if (utterance === 'cancel') {
if (dc.activeDialog) {
await dc.cancelAllDialogs();
await dc.context.sendActivity(`Ok... canceled.`);
} else {
await dc.context.sendActivity(`Nothing to cancel.`);
}
}
// If the bot has not yet responded, continue processing the current dialog.
await dc.continueDialog();
// Start the sample dialog in response to any other input.
if (!turnContext.responded) {
await dc.beginDialog(DUVIDA_NIVEL_APROVACAO_DIALOG);
}
}
Now that the results have been passed in, you can access the results via the step.context object, like so:
this.dialogs.add(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput.bind(this)));
async moneyStep(step) {
await step.prompt(MONEY_PROMPT, `What is the value of the transfer?`,
{
retryPrompt: 'Try again. What is the value of the transfer?'
}
);
}
async validateMoneyInput(step) {
// LUIS results passed into turnContext are retrieved
const intent = step.context.topIntent['intent'];
const entity = step.context.topIntent.entities;
console.log(entity);
// Validation based on matched intent
if (intent == 'Money') {
return await step.context.sendActivity('Validation succeeded');
} else if (intent != 'Money') {
return await step.context.sendActivity('Validation failed');
}
}
I also assigned the entities value to a variable for accessing since you were asking about it.
Hope of help!
It seems like I'm not getting something fundamental with graphql.
I am trying to get a user by it's id which is in turn the result of another query. In this case a query on some session data.
I don't understand why the error occurs.
Here's my code:
{
session(key: "558fd6c627267d737d11e758f1ae48cae71fc9b584e2882926ad5470c88d7c3ace08c9c7") {
userId
expires
user(id: userId) {
name
}
}
}
And I get
Unknown argument "id" on field "user" of type "Session"
My schema looks like this:
type Session {
userId: String,
expires: String,
user: User
}
type User {
_id: String
name: String
email: String
firstName: String
lastName: String
}
type Query {
session(key: String!): Session
user(id: String!): User
}
Addendum Feb 23 2017
I apologize that I wasn't sufficiently explicit about the corresponding resolvers in my initial post. Yes, I my resolvers are defined and e. g. the query works for session if I don't add users.
Here's my root:
{
Query: {
async user(parentValue, args, req) {
let user = await adapters.users.byId(args.id);
return user;
},
async session(parentValue, args, req) {
let session = await adapters.session(args.key);
let userId = session.session.userId;
let expires = session.expires;
return {userId: userId, expires: expires};
}
}
}
You need to create some resolver function on field user in type Session, because GraphQL does not know how to return the user. In graphql-js it would look like that
const Session = new GraphQLObjectType({
name: 'Session',
fields: {
userId: { type: GraphQLString },
expires: { type: GraphQLString },
user: {
type: User, // it is your GraphQL type
resolve: function(obj, args, context){
// obj is the session object returned by ID specified in the query
// it contains attributes userId and expires
// however graphql does not know you want to use the userId in order to retrieve user of this session
// that is why you need to specify the value for field user in type Session
return db.User.findById(obj.userId).then(function(user){
return user;
});
}
}
}
});
It is just a simple example from Node.js to show you how you could return the user instance. You have the possibility of specifying how to retrieve values of every field in each GraphQL type (each field can have it's own resolve function).
I suggest you read the GraphQL documentation concerning root and resolvers
EDIT
I will show you an example how you can deal with such a situation. Let's consider two types: User and Session
type User {
_id: String
name: String
}
type Session {
expires: String
user: User
}
If you want your query to return Session object together with it's User, you do not need to include the userId field in Session type.
You can solve this situation in two ways. First one is to use single resolver for the session query.
{
Query: {
async session(parentValue, args, req) {
let session = await adapters.session(args.key);
// now you have your session object with expires and userId attributes
// in order to return session with user from query, you can simply retrieve user with userId
let user = await adapter.users.byId(session.userId);
return { expires: session.expires, user: user }
}
}
}
This is your first option. With that solution you can return session object with user assigned to it via single query and without extra resolve methods on any fields of Session type. The second solution is the one I have shown previously, where you use resolve method on field user of Session type - so you simply tell GraphQL how it should obtain the value for this field.
How does one customize the user feedback form in Sentry?
By default is says something along the lines of "It looks like we're having some internal issues."
Is it possible to change the message, data collected, etc.?
Thanks.
There is an API here, but you have to get an event_id which is basically generated by an error or an exception in you app... I'm still trying to figure out how use this functionality without the default integrated form.
Sentry has a built-in front-end user feedback form which you can trigger and customize to some extent. A basic implementation in Angular4+ might look like this:
import { ErrorHandler } from '#angular/core';
import { Config } from '../config/config';
import * as Sentry from '#sentry/browser';
Sentry.init({
dsn: 'https://0123456789abcdef.sentry.io/000001',
release: Config.SENTRY_RELEASE
});
export class SentryErrorHandler implements ErrorHandler {
handleError(error: any): void {
const eventId = Sentry.captureException(error.originalError || error);
Sentry.showReportDialog({
eventId,
title: 'Aw, Snap! You broke the internet!',
subtitle: 'Send us an email and we will fix it.',
subtitle2: 'Please refresh your cache to continue (cmd+R / ctrl+R)'
});
}
}
Here's a link to the showReportDialog API
It seems the current way is this:
make your own UI
generate and enqueue a Sentry event (it's OK if the even has not been sent yet)
POST to the special endpoint that Sentry's report collector uses
The details are described in https://github.com/getsentry/sentry-javascript/issues/3111
const dsn = SENTRY_DSN;
const eventId = captureMessage("User report");
const query = Object.entries({dsn, eventId})
.map(([k, v]) => `${ k }=${ encodeURIComponent(v) }`).join("&");
const body = new FormData();
Object.entries(
{name: "Alex Foo", email: "a.foo#example.com", comments: "Long text goes here"})
.map(([k, v]) => body.append(k, v));
fetch(`https://sentry.io/api/embed/error-page/?${ query }`,
{method: "POST", body});