I have a SlingServlet that implements a JSON query service (very similar to the AEM Query Builder servlet). It creates a SyntheticResource to respond with a JSP "view", which is the JSON to be served. This JSP uses a to include each result object, which are cq:Page nodes.
When the tries to include a node which can not be rendered, it results in a 400 error, but in a peculiar way. The error appears first, followed by the expected JSON (which is malformed, because the value that could not be included is missing). I am able to override the 400 error text, so as to return "null", but I can not get the error to appear inline.
Consider:
response.api.json.jsp
{
"metadata": {
"page": ${pageIndex},
},
"data": [
<%
String[] results = (String[])slingRequest.getAttribute("results");
int i = 0;
if (results != null) {
for (String path : results) {
if (i > 0) {
%>
,
<%
}
%>
<sling:include path="<%=path%>"/>
<%
i++;
}
}
%>
]
}
the current response
<400.jsp error message up here>
{
"metadata": {
"page": 0,
},
"data": [
, // <- failed to include the path
{
"key1": , // <- failed to include the value string/object/etc
"key2": "value2"
}
]
}
the expected response
{
"metadata": {
"page": 0,
},
"data": [
<400.jsp error message>, // <- failed to include the path
{
"key1": <400.jsp error message>, // <- failed to include the value string/object/etc
"key2": "value2"
}
]
}
the expected response (assuming 400.jsp contains only "null")
{
"metadata": {
"page": 0,
},
"data": [
null, // <- failed to include the path
{
"key1": null, // <- failed to include the value string/object/etc
"key2": "value2"
}
]
}
Is there any way to get the error page (400.jsp) to appear inline, so that I can return null?
More info
response.api.json.jsp (main "view" for the service response)
{
"metadata": {
"page": ${pageIndex},
},
"data": [
<%
String[] results = (String[])slingRequest.getAttribute("results");
int i = 0;
if (results != null) {
for (String path : results) {
if (i > 0) {
%>
,
<%
}
%>
<sling:include path="<%=path%>"/>
<%
i++;
}
}
%>
]
}
page/api.json.jsp (data[] nodes included by the response.api.json.jsp)
{
"uid": <%=getFormattedString("jcr:uuid", properties)%>,
"pageTitle": <sling:include path="pageTitle"/>, // text component
"body": <sling:include path="body"/> // parsys component
}
text/api.json.jsp (pageTitle node included by each page/api.json.jsp)
{
"text": <%=getFormattedString("text", properties)%>,
"lastModified": <%=getFormattedDate("jcr:lastModified", properties)%>,
"resourceType": <%=getFormattedString("sling:resourceType", properties)%>
}
parsys/api.json.jsp (body node included by each page/api.json.jsp)
{
"resourceType": "${properties['sling:resourceType']}",
"children": [
<%
NodeIterator children = currentNode.getNodes();
int i = 0;
if (children != null && children.getSize() > 0) {
while (children.hasNext()) {
Node child = children.nextNode();
if (i > 0) {
%>
,
<%
}
%>
<sling:include path="<%=child.getPath()%>"/> // any component resource type
<%
i++;
}
}
%>
]
}
Don't use sling:include to get content from the paths, this will raise some recursion selector error. You should read the message node from the paths to get appropriate content value for your json. Here is an example. You can replace "/content/geometrixx-outdoors/en/women/jcr:content/par/banner" with your path.
<%# include file="/libs/foundation/global.jsp" %>
<%# page session="false" import="org.apache.sling.api.resource.Resource" %>
<%# page session="false" import="javax.jcr.Node" %>
<%
String yourMessage = "";
try {
Resource rs = resourceResolver.getResource("/content/geometrixx-outdoors/en/women/jcr:content/par/banner");
if (rs != null) {
Node node = rs.adaptTo(Node.class);
if (node.hasProperty("yourContentNode")) {
yourMessage = node.getProperty("yourContentNode").getString();
}
}
} catch (Exception e) {
}
%>
{
"metadata": {
"page": 1,
},
"data": [
<%= yourMessage %>,
{
"key1": "value 1",
"key2": "value2"
}
]
}
Related
I'm trying to create an API to validate a promocode. I have minimal experience with mongo and the backend in general so I'm a bit confused in what is the best approach to do what I'm trying to accomplish.
I have this PromoCode form in the client. When a user types a promocode I would like for my backend to
verify if the code exists in one of the docs.
if it exists then return that code, the value for that code and the couponId
if the code doesn't exist then return an error.
My db is structured like this. The user will type one of those codes inside the codes: []
{
"_id": {
"$oid": "603f7a3b52e0233dd23bef79"
},
"couponId": "rate50",
"value": 50,
"codes": ["K3D01XJ50", "2PACYFN50", "COKRHEQ50"]
},
{
"_id": {
"$oid": "603f799d52e0233dd23bef78"
},
"couponId": "rate100",
"value": 100,
"codes": ["rdJ2ZMF100", "GKAAYLP100", "B9QZILN100"]
}
My route is structure like this:
router.post('/promoCode', (req, res, next) => {
const { promoCode } = req.body;
console.log('this is the req.body.promoCode on /promoCode', promoCode)
if (!promoCode) {
throw new Error('A promoCode needs to be passed')
}
promoCodesModel
.validatePromoCode(req.body.promoCode)
.then((response) => {
console.log('response inside /promoCode', response)
res.status(200).json({ data: response })
})
.catch((error) => {
res.status(400).json({ result: 'nok', error: error })
})
})
The validatePromoCode function is the following:
const validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{"codes": code},
{_id: 0, codes: { $elemMatch: { $eq: code }} })
console.log('This is the promocode', promoCode)
return promoCode
} catch (err) {
throw new Error (err.stack)
}
}
All this seems to sort of work since I get the following response when the code is typed correctly
{
"data": [
{
"codes": [
"COKRHEQ50"
]
}
]
}
when typed incorrectly I get
{
"data": []
}
What I would like to get back is. (How can I accomplish this ?). Thanks
// when typed correctly
{
"data": { value: 50, couponId: "rate50", code: "COKRHEQ50" }
}
// when typed incorrectly
{
"error": "this is not valid code"
}
TL;DR: I would like to return a formatted query with specific values from a mongo query or an error object if that value does not exist on the document object.
Ok just figured it out
To be able to get the this responsed (what I wanted):
{
"data": [
{
"codes": [
"K3D01XJ50"
],
"couponId": "rate50",
"value": 50
}
]
}
I ended up having to do this on validatePromoCode
onst validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{ codes: code },
{ _id: 0, codes: { $elemMatch: { $eq: code } }, couponId: 1, value: 1 },
)
return promoCode
} catch (err) {
throw new Error(err.stack)
}
}
But is there a better way on doing this ? Thanks
I am trying to get the list of issues by their ids from Github using graphql, but looks like I am missing something or its not possible.
query ($ids:['517','510']!) {
repository(owner:"owner", name:"repo") {
issues(last:20, states:CLOSED) {
edges {
node {
title
url
body
author{
login
}
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
The above query is giving me response as below,
{
"errors": [
{
"message": "Parse error on \"'\" (error) at [1, 14]",
"locations": [
{
"line": 1,
"column": 14
}
]
}
]
}
Kindly help me identify if its possible or that I am doing something wrong here.
You can use aliases in order to build a single request requesting multiple issue object :
{
repository(name: "material-ui", owner: "mui-org") {
issue1: issue(number: 2) {
title
createdAt
}
issue2: issue(number: 3) {
title
createdAt
}
issue3: issue(number: 10) {
title
createdAt
}
}
}
Try it in the explorer
which gives :
{
"data": {
"repository": {
"issue1": {
"title": "Support for ref's on Input component",
"createdAt": "2014-10-15T15:49:13Z"
},
"issue2": {
"title": "Unable to pass onChange event to Input component",
"createdAt": "2014-10-15T16:23:28Z"
},
"issue3": {
"title": "Is it possible for me to make this work if I'm using React version 0.12.0?",
"createdAt": "2014-10-30T14:11:59Z"
}
}
}
}
This request can also be simplified using fragments to prevent repetition:
{
repository(name: "material-ui", owner: "mui-org") {
issue1: issue(number: 2) {
...IssueFragment
}
issue2: issue(number: 3) {
...IssueFragment
}
issue3: issue(number: 10) {
...IssueFragment
}
}
}
fragment IssueFragment on Issue {
title
createdAt
}
The request can be built programmatically, such as in this example python script :
import requests
token = "YOUR_TOKEN"
issueIds = [2,3,10]
repoName = "material-ui"
repoOwner = "mui-org"
query = """
query($name: String!, $owner: String!) {
repository(name: $name, owner: $owner) {
%s
}
}
fragment IssueFragment on Issue {
title
createdAt
}
"""
issueFragments = "".join([
"""
issue%d: issue(number: %d) {
...IssueFragment
}""" % (t,t) for t in issueIds
])
r = requests.post("https://api.github.com/graphql",
headers = {
"Authorization": f"Bearer {token}"
},
json = {
"query": query % issueFragments,
"variables": {
"name": repoName,
"owner": repoOwner
}
}
)
print(r.json()["data"]["repository"])
I don't think you can fetch for issues and pass in an array of integers for their ids.
But you can search for a single issue by id like so (this works for me)
query ($n: Int!) {
repository(owner:"owner", name:"repo-name") {
issue (number: $n) {
state
title
author {
url
}
}
}
}
where $n is {"n": <your_number>} defined.
If you have an array of ids, then you can just make multiple queries to GitHub.
Sadly, with this approach, you cannot specify what the state of the issue to be. But I think the logic is that once you know the issue Id, you shouldn't care what state it is, since you have that exact id.
I'm trying to group a list of users based on their specialties. For example: I want to group all Family Medicine providers and display their names:
Family Medicine:
- List item
- List item
- List item
This is my js controller:
exports.provider_list = function(req, res, next) {
provider.find()
.sort([['SpecialtyName', 'ascending']])
.exec(function (err, list_providers) {
if (err) { return next(err); }
//Successful, so render
res.render('provider_list', { title: 'Provider List', list_providers: list_providers});
});
};
Pug list:
extends layout
block content
h1= title
ul.list-group
each val in list_providers
li
a(href=val.url) #{val.SpecialtyName}
| #{val.ProviderName}
else
li There are no provider.
As I understand , you want to list all provider names grouped by specialty names.
And I guess your data (list_providers) looks like that :
[{
"ProviderName": "P1",
"SpeacialyName": "S1",
"url" : "url_1"
}, {
"ProviderName": "P2",
"SpeacialyName": "S2"
}, {
"ProviderName": "P3",
"SpeacialyName": "S3"
}, {
"ProviderName": "P3",
"SpeacialyName": "S1"
}, {
"ProviderName": "P4",
"SpeacialyName": "S2"
}]
If your data is like above. You can modify your data convert it to like this :
[{
"SpeacialyName": "S1",
"url": "url_1",
"ProviderNames": ["P1", "P3"]
}, {
"SpeacialyName": "S2",
"ProviderNames": ["P2", "P4"]
}, {
"SpeacialyName": "S3",
"ProviderNames": ["P3"]
}
]
And here is convertion code for backend :
//Successful, so render
var providers = {}
for (var i = 0; i < list_providers.length; i++) {
var item = list_providers[i];
if (!providers[item.SpeacialyName]) {
providers[item.SpeacialyName] = item;
providers[item.SpeacialyName].ProviderNames = [item.ProviderName];
} else {
providers[item.SpeacialyName].ProviderNames.push(item.ProviderName)
}
delete providers[item.SpeacialyName].ProviderName;
}
//convert object to array
var providersArray = [];
for (const item in providers) {
providersArray.push(providers[item])
}
res.render('provider_list', { title: 'Provider List', list_providers: providersArray });
Finally, here is pug file to list
ul.list-group
each val in list_providers
li
a(href=val.url) #{val.SpeacialyName}
ul
each name in val.ProviderNames
li
a(href="")=name
else
li There are no speacialy.
else
li There are no provider.
I am using the following Publication to aggregate active trials for my product:
Meteor.publish('pruebasActivas', function() {
var pruebasActivas = Clientes.aggregate([
{
$match: {
'saldoPrueba': {
'$gt': 0
}
}
}, {
$group: {
_id: {
id: '$_id',
cliente: '$cliente'
},
totalPruebas: {
$sum: '$saldoPrueba'
}
}
}
]);
});
if (pruebasActivas && pruebasActivas.length > 0 && pruebasActivas[0]) {
return this.added('aggregate3', 'dashboard.pruebasActivas', pruebasActivas);
}
Which throws the following object as a result
{
"0": {
"_id": {
"id": "YByiuMoJ3shBfTyYQ",
"cliente": "Foo"
},
"totalPruebas": 30000
},
"1": {
"_id": {
"id": "6AHsPAHZhbP3fCBBE",
"cliente": "Foo 2"
},
"totalPruebas": 20000
},
"_id": "dashboard.pruebasActivas"
}
Using Blaze how can I iterate over this Array with Objects in order to get "cliente" and "totalPruebas" to show?
Make yourself a helper that converts the object into an array of objects, using only the top level keys that are not named _id:
Template.myTemplate.helpers({
pruebasActivas: function(){
var ob = myCollection.findOne(); // assuming your collection returns a single object
var clientes = [];
for (var p in ob){
if (ob.hasOwnProperty(p) && p !== "_id"){
// here we flatten the object down to two keys
clientes.push({cliente: ob[p]._id.cliente, totalPruebas: ob[p].totalPruebas});
}
}
return clientes;
}
});
Now in blaze you can just do:
<template name="myTemplate">
{{#each pruebasActivas}}
Cliente: {{cliente}}
Total Pruebas: {{totalPruebas}}
{{/each}}
</template>
See iterate through object properties
I use wysihtml5.
When you insert an image
<img alt src="src" media_img_id="123" data-title="title" data-author="author" />
The result is
<img alt src="src" />
Rules for img
"img": {
"remove": 0,
"check_attributes": {
"width": "numbers",
"alt": "alt",
"src": "url", // if you compiled master manually then change this from 'url' to 'src'
"height": "numbers",
"media_img_id": "numbers"
},
"add_class": {
"align": "align_img"
}
},
How to make the attributes generally not removed?
I have the same task today to extend abilities of this editor.
You should add your attributes in special object:
I'm using additionaly bootstrap3-wysihtml5 - https://github.com/schnawel007/bootstrap3-wysihtml5 . The object that should be added with new attributes for element:
var defaultOptions = $.fn.wysihtml5.defaultOptions = {
/../
"img": {
"check_attributes":
{
"width": "numbers",
"alt": "alt",
"data-encid": "alt", <<-here is my custom attribute
"src": "url",
"height": "numbers"
}
},
/../
}
and in wysihtml5.js you should add condition in which your src attribute is differs from classical source (that this plugin expected) "http://example.png".
line 4922:
if (checkAttributes) {
for (attributeName in checkAttributes) {
method = attributeCheckMethods[checkAttributes[attributeName]];
if (!method) {
continue;
}
newAttributeValue = method(_getAttribute(oldNode, attributeName));
if (typeof(newAttributeValue) === "string") {
attributes[attributeName] = newAttributeValue;
}
}
}
replace with:
if (checkAttributes) {
for (attributeName in checkAttributes) {
method = attributeCheckMethods[checkAttributes[attributeName]];
if (!method) {
continue;
}
newAttributeValue = (attributeName == "src" && checkAttributes["data-encid"])
? oldNode.src
: method(_getAttribute(oldNode, attributeName));
if (typeof(newAttributeValue) === "string") {
attributes[attributeName] = newAttributeValue;
}
}
}
Here I just copy the src attribute value without checking through wysihtml5.js core.
Hope this helps!