Strapi graphql mutation Syntax Error: Unterminated string - vue.js

I always get Syntax Error: Unterminated string when I try to update my database using javascript strapi sdk. this.chapter.content is a html string generated by ckeditor. How can I escape this string to update my database using graphql?
async updateChapter() {
const q = `
mutation {
updateChapter(input: {
where: {
id: "${this.$route.params.chapterId}"
},
data: {
content: "${this.chapter.content.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/(?:\r\n|\r|\n)/g, '\n')}"
title: "${this.chapter.title}"
}
}) {
chapter{
title
id
content
}
}
}
`;
const res = await strapi.request("post", "/graphql", {
data: {
query: q
}
});
this.chapter = res.data.chapter;
}

Technically you could use block string notation to get around this issue. However, you really should supply dynamic input values using variables instead of string interpolation. This way you can easily provide any of sort of values (strings, numbers, objects, etc.) and GraphQL will parse them accordingly -- including strings with line breaks.
const query = `
mutation MyMutation ($chapterId: ID!, $content: String!, $title: String!) {
updateChapter(input: {
where: {
id: $chapterId
},
data: {
content: $content
title: $title
}
}) {
chapter{
title
id
content
}
}
}
`
const variables = {
chapterId: '...',
content: '...',
title: '...',
}
const res = await strapi.request("post", "/graphql", {
data: {
query,
variables,
},
})
Note that $chapterId may need to be of the type String! instead if that's what's called for in the schema. Since variables can also be input object types, instead of providing 3 different variables, you could also provide a single variable to be passed to the input argument instead:
const query = `
mutation MyMutation ($input: SomeInputObjectTypeHere!) {
updateChapter(input: $input) {
chapter{
title
id
content
}
}
}
`
const variables = {
input: {
where: {
id: '...',
},
data: {
content: '...',
title: '...',
},
},
}
Again, just replace SomeInputObjectTypeHere with the appropriate type in your schema.

Another solution maybe help
Code with issue: For example mainReason and actionTaken fields are text inputs and data contains some white spaces. This action give error: Unterminated string
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason: "${ticketData.mainReason}"
actionTaken: "${ticketData.actionTaken}"
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}
Fix this problem with JSON.stringify method
mutation { updateApplicationForm(input:{ where:{id:"${ticketData.id}"}
data:{
mainReason:${JSON.stringify(ticketData.mainReason)}
actionTaken:${JSON.stringify(ticketData.actionTaken)}
appStatus: ${ticketData.appStatus}
action: "${ticketData.action}"
}

Related

Can rollup-plugins access the AST created by previous plugins in the plugin chain?

We use multiple rollup-plugins that parse their input to an AST. As they run on the same files, each file is parsed multiple times. Can this be optimized, so that each file is parsed only once? Minimal example:
// rollup.config.js
import {createFilter} from '#rollup/pluginutils';
import {simple} from 'acorn-walk';
import {attachComments} from 'astravel';
import {generate} from 'astring';
export default {
input: 'src/main.js',
output: {file: 'bundle.js', format: 'cjs'},
plugins: [{
name: 'plugin1',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
Identifier(n) {
// rewrite wrong to right
if (n.name === 'wrong') n.name = 'right';
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}, {
name: 'plugin2',
transform(code, id) {
const comments = [];
const ast = this.parse(code, {onComment: comments});
attachComments(ast, comments);
simple(ast, {
CallExpression(n) {
// rewrite mylog(...) to console.log(...)
if (n.callee.type === 'Identifier' && n.callee.name === 'mylog') {
n.callee = {
type: 'MemberExpression',
object: {type: 'Identifier', name: 'console', start: n.start, end: n.end},
property: {type: 'Identifier', name: 'log', start: n.start, end: n.end},
computed: false,
start: n.start,
end: n.end
}
}
}
});
return {
code: generate(ast, {comments: true}),
ast,
map: null /* minimal example, won't create a source map here */
};
}
}]
};
Now I understand that transform() can return an AST, so that parsing doesn't have to happen twice. And I understand that this.parse() uses the rollup-internal acorn instance. My simple mind thought that this.parse() could return the AST created by previous transform() calls, if available. But I assume that all sorts of demons await on that road, e.g. when this.parse() was called with different options.
Is there a different way achieve what I described? A different hook maybe?
I would love to not have all plugins in one and switching them on and off via options (I see that this would be a solution, but a really cumbersome one).

How to find object length in vue

I am trying to write a form validation using VueJS.
I keep testing for the length of the error object. I keep getting undefined when I log it console.
I used this.errors.length to refer to it. It seems to treat .length as a key in errors.
data(){
return {
name: null,
price: null,
description: null,
roomTypes: {},
errors: {},
}
},
methods: {
addRoomType: function(){
if(this.name && this.description && this.price && this.this.description>10){
axios.post('/admin/room-types',{
name: this.name,
price: this.price,
description: this.description
}).then((response)=>{
this.errors = {};
this.roomTypes.push(response.data);
}).catch((error)=>{
this.errors = error.response.data;
console.error(error.response.data);
});
}
//this.errors = {};
if(!this.name){
this.errors.name = "Please enter a name.";
console.log(this.errors.name);
}
if(!this.description){
this.errors.description = "Please enter a description.";
console.log(this.errors.description);
}
if(!this.price){
this.errors.price = "Please enter a price.";
console.log(this.errors.price);
}
if(this.errors.length){
console.log(this.errors.length);};
I want to be able to get the size of the errors object so i can check if it is empty.
By using this.errors.length you are trying to access a this.errors key.
In order to check a Javascript object length you can use Object.keys
Something like that:
if (Object.keys(this.errors).length) {
//your code here
}
Try using Object.keys(this.errors).length.
Though for better management, I would recommend making errors an array and storing errors as an array of objects.
Something like:
const myErrors = [
{ name: ‘First name’, message: ‘Name is required’ },
{ name: ‘Email’, message: ‘Email must be valid’ }
]
This is a pseudo example but doing errors as an array allows you to loop them easily and avoids name collisions thay might come from object keys. Just an idea!
First, .length only applies to arrays, but errors is an object, not an array.
Second, I think, the assignments of errors or room types will not work in this part of the code:
axios.post('/admin/room-types',{
name: this.name,
price: this.price,
description: this.description
}).then((response)=>{
this.errors = {};
this.roomTypes.push(response.data);
}).catch((error)=>{
this.errors = error.response.data;
console.error(error.response.data);
});
The response- and the error handler are own functions, which likely don't have this defined to the same Vue-object as your method. Instead, keep a reference to the Vue-object in a variable self, and use that in the handlers to assign the values:
var self = this;
axios.post('/admin/room-types',{
name: this.name,
price: this.price,
description: this.description
}).then((response)=>{
self.errors = {};
self.roomTypes.push(response.data);
}).catch((error)=>{
self.errors = error.response.data;
console.error(error.response.data);
});

GraphQL queries with tables join using Node.js

I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
The relations are one-to-many, where a user can have many comments.
I have built the schema like this:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
And the query:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
Executing the query for a user and his comments is done with 1 request, like so:
{
User(id:"1"){
Comments{
content
}
}
}
As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?
The concept you are refering to is called batching. There are several libraries out there that offer this. For example:
Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"
join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."
To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>()
.Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
q.Enqueue(new Tuple<string, Field>
(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
else
{
yield return node.Item1;
}
}}}
Lets say we have the following query:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
We can create a variable in "resolve" method of the field:
var include = context.GetIncludeString();
which generates the following string:
"Product.Category.SubCategory,Product.Category.SubAnything"
and pass it to Entity Framework:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}

How do I operate the m.withAttr tutorials code?

A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.

How use dynamic routes to find people in my mongoose database with node.js

app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
id: param
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
I have this route, and this EJS:
<% for(var i = 0; i < data.length; i++) { %>
<td> <a href= <%="/admin/reservas/" + data[i].id + ""%>><%= data[i].id %></a></td>
<td> <a href= <%="/admin/reservas/" + data[i].name + ""%>><%= data[i].name %></a></td>
<% } %>
And is fine, when i click in the id, i find what the id in the db that i want, but I want to be able to find by the name too.
So i was trying to change the routes:
app.get('/names/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
id: param,
name: param
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
but is returning anything, both the id and the name
MongoDB query arguments are by default always a logical and condition. In order to use a logical or there is the $or operator:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
"$or": [
{ id: param },
{ name: param }
]
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
The $or operator takes an array of query documents to consider the conditions of. It is a "short circuit" match where the first condition to evaluate as true makes the statement true.
At least that would be true if not of a specific problem here. See _id and it's id alias is a special value to both MongoDB and Mongoose. By default this will try to "cast" to an ObjectID type but it cannot if the string supplied is invalid, such as "fred" for example.
This would cause an exception as the query arguments are supplied. So the bottom line rule is your cannot "mix types" in an $or condition in this way with something that would not survive the conversion from String. You would have to do it "logically" in a different way by testing the value
for what it is:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
var query = {};
try {
var id = mongoose.mongo.ObjectID(param);
query = { "id": id };
} catch {
query = { "name": param };
}
mongoose.model('Something').findOne(
query, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
That means that the query is now built in a way that mongoose will parse it considering the schema "types" to apply then there is no error since the decision of which field to query on was made elsewhere.
Alternately of course you can "bypass" the mongoose method behavior and just use the raw driver:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
var id ="";
try {
id = mongoose.mongo.ObjectID(param);
}
mongoose.model('Something').collection.findOne(
{
"$or": [
{ id: id },
{ name: param }
]
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
But of course you need to do the type conversion yourself and not only for "testing" otherwise even a correct "String" value representing an ObjectID would not work.