Appsync - Masking a field in mutation response which is required by subscription - amazon-web-services

I'm building a chat based application using Appsync with the following mutation and subscription.
However, the chat room is moderated and depending on the chat settings (which is controlled by a moderator), the messages can be either visible to everyone or visible to the moderator alone.
For security reasons, I don't want to send the "receiverId" in the response of the createMessage mutation.
But, I'm not able to create a subscription with filter on "receiverId" if this field is not present in the response of the mutation.
Is there any alternative way to achieve this chat behaviour ?
mutation createMessage {
createMessage(input: {
chatId: "chatId1",
userId: "userA",
content: "sample message"
}) {
id,
content,
chatId,
userId,
receiverId
}
}
and a corresponding subscription
subscription onCreateMessage {
onCreateMessage(input: {receiverId: "userB"
}) {
id,
content,
chatId,
userId,
receiverUserId
}
}

Related

How can I generate graphql response from raw json?

I have a graphql server build on websocket transport protocol in order to support subscription. I need to publish some data to subscribers implemented by Apollo client.
What I don't know is how I can construct the published data payload that Apollo client expects.
Below is my graphql schema:
type Message {
msg: String!
id: ID!
topic: String!
}
type Mutation {
sendChat(topic: String!, message: String!): ID!
}
type Query {
getChat(id: ID!): Message!
}
type Subscription {
# Subscribe to events on the given topic.
event(
# Name of the topic to subscribe to.
on: String!
): Message!
}
There is a subscription api in above schema. My backend is built on AWS lambda and dynamodb. It needs to publish all dynamodb change to clients. When the lambda receives a data change, it needs to push the data to a websocket API gateway. I am able to make it work by manually construct the payload.
For example, if the raw data changed from dynamodb is { msg: 'hello', topic: 'xxxx' }, I can make it work by wrapping this data as:
{
data: { event: { msg: 'hello', topic: 'xxxx' } }
}
Then the client will receive this data.
But the problem I have is how I can construct the __typename in the response payload. And also it is not a good idea to manually construct it.
What is the right way to do that in go? Is there a library do that work?

AppSync GraphQL map 1-1 to an existing GraphQL API

I have an existing GraphQL API that exposes a schema like this:
type AType {
id: Int!
name: String!
children: [BType]
}
type BType {
id: Int!
name: String!
}
type Query {
a(id: ID): A
as: [A]
}
plus some other omitted scalar fields in both. There are a couple of such API endpoints and I want to aggregate them all under a single AppSync instance, so that the front can query one endpoint with a single schema for all the data they need.
What I would like to do is if the front sends a query
as {
id,
name,
children {
id,
name
}
}
it would get relayed exactly to my existing GraphQL endpoint. The AWS examples linked on the AppSync page talk about GraphQL endpoints but the only linked code example is this, and it shows a resolver like this:
#**
Make an arbitrary HTTP call when this field is listed in a query selection set.
The "relativePath" is the resource path relative to the root URL provided when you
created the HTTP data source. Pass a "params" object to forward headers, body,
and query parameters to the HTTP endpoint.
*#
#if($context.identity.sub == $context.args.userId)
#set($payload = "query ListOrders {
listOrders(
userId: ""$context.args.userId""
orderDateTimeStatus: {
le : {
orderDateTime: ""$context.args.orderDateTime""
}
}
limit: 10
) {
items {
userId
status
orderDateTime
details
orderId
}
nextToken
}
}")
{
"version": "2018-05-29",
"method": "POST",
"resourcePath": "/graphql",
"params":{
"body": {
"query": "$util.escapeJavaScript($payload)"
},
"headers":{
"Content-Type": "application/json",
"x-access-token" : "$context.request.headers.x-access-token"
}
}
}
#else
$utils.unauthorized()
#end
This uses only the arguments passed into the query, not the query structure. If front requests a subset of fields of A, I only want to make a request for that subset of fields, not a request for all that then gets cut down by AppSync. Also it doesn't even contain nested objects -- would I have to fetch all the children every time just so that they get later ignored by AppSync?
Assume there are 4-5 microservices that have their own GraphQL endpoints that have different queries in their schemas. I'd like to map the requests 1-1 to them. How would I write such a resolver? AppSync doesn't really have a good playground environment where I could debug the resolver so I can't just look at the $ctx object and reverse-engineer the structure of the original query (AFAIK).
I found this question that asks just that, but the solution there doesn't work as intended, as mentioned by the author, and it seems to be dead.

Appsync Subscription (with alias) not receiving updates

Using GraphQL alias feature, no updates were received when mutation was invoked.
subscription OnPutItem($id: ID!) {
item: onPutItem(id: $id) { // <---------- with alias
id
data
}
}
mutation PutItem($data: String!) {
item: PutItem(data: $data) { // <-------- with alias
id
data
}
}
It works normally without the item alias.
Tested in Appsync console and app using aws-amplify.
I didn't see any restrictions for alias not to be used in subscription.
Any thoughts?
Thanks for bringing this to our attention. The fix for this has now been deployed, so you should see updates for mutations and subscriptions with aliases.

AWS AppSync - Subscription to a mutation does not return the desired fields

I try to subscribe to mutations in a DynamoDB table in AWS AppSync. The schema briefly looks like follows:
type Post {
id: ID!
userId: String!
title: String
body: String!
}
input UpdatePostInput {
id: ID!
title: String
body: String
}
type Mutation {
updatePost(input: UpdatePostInput!): Post
}
type Subscription {
onUpdatePost(id: ID!): Post
#aws_subscribe(mutations: ["updatePost"])
}
Given the ID of the post, when I want to get the changes in the body of that post I tried making use of that subscription above as:
subscription OnUpdatePost {
onUpdatePost(id: "some-id") {
id
body ## This line should make the trick, but it does not
}
}
The subscription is fired -which is fine. However, the result contains only the ID and __typename, NOT the body:
{
"data": {
"onUpdatePost": {
"id": "some-id",
"__typename": "Post"
}
}
}
Having body among the fields should be enough following the guide here.
Am I missing something with this subscription setup?
Note:
The mutation works i.e. the body can be updated in the table behind the scenes.
I did not attach a resolver to the subscription entry, but there is one for the mutation. It should be this way afaik.
Subscriptions in AWS AppSync are invoked as a response to a mutation. Subscriptions are triggered from mutations and the mutation selection set is sent to subscribers.
I suspect that you aren't returning body in your updatePost mutation selection set. Add that field and the subscription will contain body e.g.
mutation {
updatePost(input: { id: "some-id" }) {
id
body
}
}

Setting Up Apollo Server with subscriptions-transport-ws?

It seems like I have my server set up according to the Apollo docs at http://dev.apollodata.com/tools/apollo-server/setup.html. In my server/main.js file:
//SET UP APOLLO INCLUDING APOLLO PUBSUB
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers,
connectors: Connectors,
logger: console,
});
const GRAPHQL_PORT = 8080;
const graphQLServer = express();
// `context` must be an object and can't be undefined when using connectors
graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
schema: executableSchema,
context: {}, //at least(!) an empty object
}));
graphQLServer.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
graphQLServer.listen(GRAPHQL_PORT, () => console.log(
`GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
));
//SET UP APOLLO INCLUDING APOLLO PUBSUB
It prints out "GraphQL Server is now running on http://localhost:8080/graphql" to the terminal log indicating that the server was successfully initialized.
But at the top of my main_layout component, when I run this code:
import { Client } from 'subscriptions-transport-ws';
const wsClient = new Client('ws://localhost:8080');
...I get this console message:
WebSocket connection to 'ws://localhost:8080/' failed: Connection closed before receiving a handshake response
What am I missing?
You need to create a dedicated websocket server. It will run on a different port and the code to set it up is provided on the subscriptions-transport-ws package.
Take a look on the following code from GitHunt-API example:
https://github.com/apollostack/GitHunt-API/blob/master/api/index.js#L101-L134
Also you would see that this code is dependent on a class called SubscriptionManager. It is a class from a package called graphql-subscriptions also by the apollo team, and you can find an example of how to use it here:
https://github.com/apollostack/GitHunt-API/blob/master/api/subscriptions.js
TL;DR: You can use graphql-up to quickly get a GraphQL server with subscriptions support up and ready. Here's a more detailed tutorial on using this in combination with Apollo and the websocket client subscriptions-transport-ws.
Obtain a GraphQL Server with one click
Let's say you want to build a Twitter clone based on this GraphQL Schema in IDL syntax:
type Tweet {
id: ID!
title: String!
author: User! #relation(name: "Tweets")
}
type User {
id: ID!
name: String!
tweets: [Tweet!]! #relation(name: "Tweets")
}
Click this button to receive your own GraphQL API and then open the Playground, where you can add some tweets, query all tweets and also test out subscriptions.
Simple to use API
First, let's create a user that will be the author for all coming tweets. Run this mutation in the Playground:
mutation createUser {
createUser(name: "Tweety") {
id # copy this id for future mutations!
}
}
Here's how you query all tweets and their authors stored at your GraphQL server:
query allTweets {
allTweets {
id
title
createdAt
author {
id
name
}
}
}
Subscription support using websockets
Let's now subscribe to new tweets from "Tweety". This is the syntax:
subscription createdTweets {
Message(filter: {
mutation_in: [CREATED]
node: {
author: {
name: "Tweety"
}
}
}) {
node {
id
text
createdAt
sentBy {
id
name
}
}
}
}
Now create a new tab in the Playground and create a new Tweet:
mutation createTweet {
createTweet(
title: "#GraphQL Subscriptions are awesome!"
authorId: "<id-from-above>"
) {
id
}
}
You should see a new event popping up in your other tab where you subscribed before.
Here is a demo about using Apollo GraphQL, React & Hapi: https://github.com/evolastech/todo-react. It's less overwhelmed than GitHunt-React & GitHunt-API
Seems like you aren't actually making the websocket server. use SubscriptionServer. Keep in mind that it is absolutely NOT true that you have to have a dedicated websocket port (I thought this once too) as davidyaha says. I have both my normal queries and subs on the same port.
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { schema } from './my-schema';
// All your graphQLServer.use() etc setup goes here, MINUS the graphQLServer.listen(),
// you'll do that with websocketServer:
// Create WebSocket listener server
const websocketServer = createServer(graphQLServer);
// Bind it to port and start listening
websocketServer.listen(3000, () => console.log(
`Server is now running on http://localhost:3000`
));
const subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server: websocketServer,
path: '/subscriptions',
},
);