Proxy request to graphql server through AWS appsync - aws-appsync

I have a graphql server on one of my EC2 instances running. I also have AWS appsync running, but at the moment it's integrated only with couple of lambdas.
I'd like to connect my Appsync with graphql server, so Appsync will behave as a proxy for specific queries/mutations.
So from client side, it will look like this:
Client sends a query to the appsync, lets say that it looks like that:
{
user {
id
}
}
Appsync has defined a user query, it's configured to proxy the query the graphql server, without any changes
Graphql server is able to handle following query:
{
user {
id
}
}
and returns a result:
"data": {
"user": {
"id": "123456789"
}
}
finally Appsync proxies response back to the client
Am I able to configure Appsync in a way that given scenario is possible? Is it the right pattern to use Appsync for?
Update 1. After #mparis response
I was able to proxy my request through AppSync, and reach my graphql server, with following resolver configuration:
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/graphql",
"params":{
"body": {
"query": "$util.escapeJavaScript($ctx.info.getSelectionSetGraphQL())"
}
}
}
But this still does not work as expected - it's different from description in docs, and I see at least two issues:
If I have a query with arguments and nested paylod, $ctx.info.getSelectionSetGraphQL() cuts out the query name and arguments part, and gives me only nested payload, so this query:
{
user(id: "1") {
picture {
url
}
}
becomes following one, once I call $ctx.info.getSelectionSetGraphQL():
{
picture {
url
}
}
But I'd like to use whole query, same as described in docs:
"selectionSetGraphQL": "{\n getPost(id: $postId) {\n postId\n title\n secondTitle: title\n content\n author(id: $authorId) {\n authorId\n name\n }\n secondAuthor(id: \"789\") {\n authorId\n }\n ... on Post {\n inlineFrag: comments {\n id\n }\n }\n ... postFrag\n }\n}"
Lets say I have a query, and I've defined resolver for user query in appsync, the query looks like this:
{
user(id: "1") {
picture {
url
}
}
and I call my graphql backend through appsync, in my graphql logs I see following response logged:
{
"data: {
"user": {
"picture": {
"url": "example.com/link/to/a/picture"
}
}
}
}
but appsync returns me given response instead:
{
"data: {
"user": {
"picture": null
}
}
}
I've figured out, that appsync expects from me that I will define resolvers for both user and picture query. Appsync wants to first call user and then picture query, but what I wanna do is simply proxy the response through appsync, without calling any additional queries except user. Everything is evaluated in my graphql backend and should be just put to the response body, but appsync wants to evaluate everything one more time.
Is there any solution for no 1. and 2.?

As of writing, you can use the $ctx.info object to get the selection set of queries from within the resolvers and send the relevant data via an HTTP resolver to your downstream service. Go here and look for the info field on the $ctx object. To make this work, you will need to mirror the schema of your downstream API within your AppSync API.
Thanks for bringing this up. The team is aware of these use cases and this helps prioritization.

You can use the following resolver function:
I used the openly available StarWarsAPI as data source: https://swapi-graphql.netlify.app
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/.netlify/functions/index",
"params": {
"headers": {
"Content-Type": "application/json"
},
"body": {
"query": "query { starship (id: \"$ctx.args.id\") $util.escapeJavaScript($ctx.info.getSelectionSetGraphQL()) }"
}
}
}
And then you have to get the data from the graphql response using this reponse mapping:
$util.toJson($util.parseJson($ctx.result.body).data.starship)
(You have to duplicate the schema in appsync)

Related

Document AI Contract Processor - batchProcessDocuments ignores fieldMask

My aim is to reduce the json file size, which contains the base64 image sections of the documents by default.
I am using the Document AI - Contract Processor in US region, nodejs SDK.
It is my understanding that setting fieldMask attribute in batchProcessDocuments request filters out the properties that will be in the resulting json.
I want to keep only the entities property.
Here are my call parameters:
const documentai = require('#google-cloud/documentai').v1;
const client = new documentai.DocumentProcessorServiceClient(options);
let params = {
"name": "projects/XXX/locations/us/processors/3e85a4841d13ce5",
"region": "us",
"inputDocuments": {
"gcsDocuments": {
"documents": [{
"mimeType": "application/pdf",
"gcsUri": "gs://bubble-bucket-XXX/files/CymbalContract.pdf"
}]
}
},
"documentOutputConfig": {
"gcsOutputConfig": {
"gcsUri": "gs://bubble-bucket-XXXX/ocr/"
},
"fieldMask": {
"paths": [
"entities"
]
}
}
};
client.batchProcessDocuments(params, function(error, operation) {
if (error) {
return reject(error);
}
return resolve({
"operationName": operation.name
});
});
However, the resulting json is still containing the full set of data.
Am I missing something here?
The auto-generated documentation for the Node.JS Client Library is a little hard to follow, but it looks like the fieldMask should be a member of the gcsOutputConfig instead of the documentOutputConfig. (I'm surprised the API didn't throw an error)
https://cloud.google.com/nodejs/docs/reference/documentai/latest/documentai/protos.google.cloud.documentai.v1.documentoutputconfig.gcsoutputconfig
The REST Docs are a little more clear
https://cloud.google.com/document-ai/docs/reference/rest/v1/DocumentOutputConfig#gcsoutputconfig
Note: For a REST API call and for other client libraries, the fieldMask is structured as a string (e.g. text,entities,pages.pageNumber)
I haven't tried this with the Node Client libraries before, but I'd recommend trying this as well if moving the parameter doesn't work on its own.
https://cloud.google.com/document-ai/docs/send-request#async-processor

How to generate automatic Id with Commit or Batch Document Firestore REST

Hi I am creating documents with commit like this way:
{
"writes": [
{
"update": {
"name": "projects/projectID/databases/(default)/documents/test/?documentId=",
"fields": {
"comment": {
"stringValue": "Hello World!"
}
}
}
},
{
"update": {
"name": "projects/projectID/databases/(default)/documents/test/?documentId=",
"fields": {
"comment": {
"stringValue": "Happy Birthday!"
}
}
}
}
]
}
The parameter ?documentId= dosen´t work like when creating a single document, if I left empty I get an error that I must specify the name of the document so how I can generate an automatic id for each document?
Unfortunately, batch commits with auto generated documentId are not possible in the Firestore REST API. As you can see in this documentation, the Document object should be provided with a full path, including the documentID:
“Name:string
The resource name of the document, for example projects/{project_id}/databases/{databaseId}/documents/{document_path}.”
And if it was possible to omit the documentID, it would be mentioned in this documentation.
If you would like to have this implemented in the Firestore REST API, you can create a feature request in Google’s Issue Tracker so that they can consider implementing it.
I just came across the same problem and discovered that it is still not implemented.
I created a feature request for it here: https://issuetracker.google.com/issues/227875470.
So please go star it if you want this to be added.

Ejabberd api endpoint private_set won't save the data

I'm trying to manipulate the private storage on ejabberd server, I send the POST with the json to the endpoint private_set. The answer of the call private_set it's 200 and the body 0, means all ok and then I call the other endpoint private_get to query the private storage, and when I do this last call the storage is not updated.
{
"user": "example",
"host": "localhost",
"element": "<query xmlns='jabber:iq:private'><storage xmlns='storage:bookmarks'><conference autojoin='false' jid='test#conference.localhost' name='test'><nick>example</nick></storage></query>"
}
I don't know with older versions, but at least with ejabberd 19.02, the Query element must not be included in the argument. So, there's a mistake in the documentation of the command example argument. Also, you forgot to close the Conference element.
Try with this:
{
"user": "example",
"host": "localhost",
"element": "<storage xmlns='storage:bookmarks'><conference autojoin='false' jid='test#conference.localhost' name='test'><nick>example</nick></conference></storage>"
}

Get all organizations in Azure DevOps using REST API

I am trying to retrieve all the organizations in my account but in the documentation an organization is always required in the API call.
https://dev.azure.com/{organization}/_apis/...
If you load the current landing page, it displays all your organizations tied to your account. I assumed it had to get that information some way. I captured the network traffic and I believe you could get to the data you want using a system API call. However, it might change or might become unsupported without notice, so use at your own discretion.
You can get the information you want using this API:
Post https://dev.azure.com/{organization1}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1
Body:
{
"contributionIds": ["ms.vss-features.my-organizations-data-provider"],
"dataProviderContext":
{
"properties":{}
}
}
Response:
{
"dataProviderSharedData": {},
"dataProviders": {
"ms.vss-web.component-data": {},
"ms.vss-web.shared-data": null,
"ms.vss-features.my-organizations-data-provider": {
"organizations": [
{
"id": "{redacted id}",
"name": "{organization1}",
"url": "https://{organization1}.visualstudio.com/"
},
{
"id": "{redacted id}",
"name": "{organization2}",
"url": "https://dev.azure.com/{organization2}/"
}
],
"createNewOrgUrl": "https://app.vsaex.visualstudio.com/go/signup?account=true"
}
} }
you can do it simply by making a call to get all the account you are member/ owner of. However for that you need your id, which can be easily fetched by making get profile call. Here are steps below:
Make a VSTS API call to get profile details using Bearer token or PAT
https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=5.1
This will return you, your Id:
{
"displayName": "xxxx",
"publicAlias": "xxx",
"emailAddress": "xxx",
"coreRevision": xxx,
"timeStamp": "2019-06-17T09:29:11.1917804+00:00",
"id": "{{We need this}}",
"revision": 298459751
}
Next, make a call to get all the accounts you are member of or owner of:
https://app.vssps.visualstudio.com/_apis/accounts?api-version=5.1&memberId={{Your Id}}
Response:
{
"count": 1,
"value": [
{
"accountId": "xxx",
"accountUri": "xxx",
"accountName": "xxx",
"properties": {}
}
]
}
It will return list of accounts you are associated with.
A REST API request/response pair can be separated into five components:
The request URI, in the following form:
VERB https://{instance}[/{team-project}]/_apis[/{area}]/{resource}?api-version={version}
instance:
The Azure DevOps Services organization or TFS server you're sending the request to.
They are structured as follows:
Azure DevOps Services: dev.azure.com/{organization}
The REST API's are organization specific. This is not documented at present. You could submit a feature request here: https://developercommunity.visualstudio.com/spaces/21/index.html
Our PM and product team will kindly review your suggestion. Sorry for any inconvenience.
As a workaround, you could use the API which captured from network traffic just as Matt mentioned.
We've been using "https://app.vssps.visualstudio.com/_apis/accounts" without specifying any API version and this returns all our accountnames
This is still working for us, but because of some other issues we have I'm adding the api version to all our api calls, however. For this I also run into the fact that https://learn.microsoft.com/en-us/rest/api/azure/devops/account/accounts/list?view=azure-devops-rest-5.0 requires an member or owner id.
Retrieving that needs an account/organization so it is a bit of a catch 22 situation.
For now I'll stay with just "https://app.vssps.visualstudio.com/_apis/accounts" I guess
I'm getting a sign-in response for both "app.vssps.visualstudio.com/_apis/accounts" and
Post https://dev.azure.com/{organization1}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1
StatusCode : 203
StatusDescription : Non-Authoritative Information
EDIT:
Nevermind, it worked using the static MSA clientid and replyURL:
internal const string clientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1"; //change to your app registration's Application ID, unless you are an MSA backed account
internal const string replyUri = "urn:ietf:wg:oauth:2.0:oob"; //change to your app registration's reply URI, unless you are an MSA backed account
//PromptBehavior.RefreshSession will enforce an authn prompt every time. NOTE: Auto will take your windows login state if possible
result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, new Uri(replyUri), promptBehavior).Result;
Console.WriteLine("Token expires on: " + result.ExpiresOn);
var bearerAuthHeader = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Headers
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("User-Agent", "ManagedClientConsoleAppSample");
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.Authorization = authHeader;
//Get Organizations
client.BaseAddress = new Uri("https://app.vssps.visualstudio.com/");
HttpResponseMessage response1 = client.GetAsync("_apis/accounts").Result;

Can't post node that requires a pre assigned value with services api

I have setup a content type with a subject field that has pre assigned values in a dropdown field.
I am using the services api to post new content from a polymer app.
When I POST to the api I send the field structure and value in json but get and error.
"406 (Not Acceptable : An illegal choice has been detected. Please contact the site administrator.)"
Even though the object I am sending matches one of the required values in the field.
Do I need to prefix the value with something? I assume I'm posting to the right place to get that response but don't know why it would accept anything other than the string value.
Here is what I sent to the api which is picked up by my Charles proxy.
{
"node": {
"type": "case",
"title": "my case",
"language": "und",
"field_subject": {
"und": {
"0": {
"value": "subject1"
}
}
},
"body": {
"und": {
"0": {
"value": "my details of subject"
}
}
}
}
}
And here is an example of what I have setup in my Drupal field
subject1| first
subject2| second
subject3| third
subject4| forth
For anyone else with the same problem, this subject is poorly documented, but the answer is simple, my subject did not need the value key despite devel suggesting thats how it would be formatted.
"field_subject": {
"und": [
"subject1"
]
}
I could also shorten my code with "und" being an array.