Skip to content

dabit3/heard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Heard - An enterprise React Native Social Messaging App

Built with AWS AppSync & AWS Amplify

Todo

  • Add subscriptions for real time updates / messages in feed
  • Add user profile section
  • Add "follower" tab

Getting Started

Cloning the project & creating the services

  1. Clone the project
git clone 
  1. Install dependencies
yarn
# or
npm install
  1. Create new AWS Mobile Project
awsmobile init
  1. Add Authentication service
awsmobile user-signin enable
  1. Push configuration to AWS Mobile Hub
awsmobile push

Configuring the AWS AppSync API

  1. Create & configure a new AppSync API
  • Visit the AWS AppSync console and create a new API.
  • In Settings, set the Auth mode to Amazon Cognito User Pool and choose the user pool created in the initial service creation.
  1. In index.js on line 11, change <YOURAPPSYNCENDPOINT> to the endpoint given to you when you created the AppSync API.

  2. Attach the following Schema:

input CreateFollowingInput {
	id: ID
	followerId: ID!
	followingId: ID!
}

input CreateMessageInput {
	messageId: ID
	authorId: ID!
	createdAt: String
	messageInfo: MessageInfoInput!
	author: UserInput
}

input CreateUserInput {
	userId: ID!
	username: String!
}

input DeleteFollowingInput {
	id: ID!
}

input DeleteMessageInput {
	authorId: ID!
	createdAt: String!
}

input DeleteUserInput {
	userId: ID!
}

type Following {
	id: ID
	followerId: ID!
	followingId: ID!
}

type FollowingConnection {
	items: [Following]
	nextToken: String
}

type ListUserConnection {
	items: [User]
	nextToken: String
}

type Mutation {
	createMessage(input: CreateMessageInput!): Message
	updateMessage(input: UpdateMessageInput!): Message
	deleteMessage(input: DeleteMessageInput!): Message
	createUser(input: CreateUserInput!): User
	updateUser(input: UpdateUserInput!): User
	deleteUser(input: DeleteUserInput!): User
	createFollowing(input: CreateFollowingInput!): Following
	updateFollowing(input: UpdateFollowingInput!): Following
	deleteFollowing(input: DeleteFollowingInput!): Following
}

type Query {
	getMessage(authorId: ID!, createdAt: String!): Message
	listMessages(first: Int, after: String): MessageConnection
	listFollowing: [Following]
	getUser(userId: ID!): User
	listUsers(first: Int, after: String): ListUserConnection
	queryMessagesByAuthorIdIndex(authorId: ID!, first: Int, after: String): MessageConnection
}

type Subscription {
	onCreateMessage(messageId: ID, authorId: ID, createdAt: String): Message
		@aws_subscribe(mutations: ["createMessage"])
	onUpdateMessage(messageId: ID, authorId: ID, createdAt: String): Message
		@aws_subscribe(mutations: ["updateMessage"])
	onDeleteMessage(messageId: ID, authorId: ID, createdAt: String): Message
		@aws_subscribe(mutations: ["deleteMessage"])
	onCreateUser(userId: ID, username: String): User
		@aws_subscribe(mutations: ["createUser"])
	onUpdateUser(userId: ID, username: String): User
		@aws_subscribe(mutations: ["updateUser"])
	onDeleteUser(userId: ID, username: String): User
		@aws_subscribe(mutations: ["deleteUser"])
	onCreateFollowing(id: ID, followerId: ID, followingId: ID): Following
		@aws_subscribe(mutations: ["createFollowing"])
	onUpdateFollowing(id: ID, followerId: ID, followingId: ID): Following
		@aws_subscribe(mutations: ["updateFollowing"])
	onDeleteFollowing(id: ID, followerId: ID, followingId: ID): Following
		@aws_subscribe(mutations: ["deleteFollowing"])
}

type Message {
	messageId: ID!
	authorId: ID!
	messageInfo: MessageInfo!
	author: User
	createdAt: String
}

type MessageConnection {
	items: [Message]
	nextToken: String
}

type MessageInfo {
	text: String!
}

input MessageInfoInput {
	text: String!
}

input UpdateFollowingInput {
	id: ID!
	followerId: ID
	followingId: ID
}

input UpdateMessageInput {
	messageId: ID
	authorId: ID!
	createdAt: String!
}

input UpdateUserInput {
	userId: ID!
	username: String
}

type User {
	userId: ID!
	username: String
	messages(limit: Int, nextToken: String): MessageConnection
	following(limit: Int, nextToken: String): UserFollowingConnection
	followers(limit: Int, nextToken: String): UserFollowersConnection
}

type UserConnection {
	items: [User]
	nextToken: String
}

type UserFollowersConnection {
	items: [User]
	nextToken: String
}

type UserFollowingConnection {
	items: [User]
	nextToken: String
}

input UserInput {
	userId: ID!
	username: String!
}
  1. Create the following DynamoDB Tables
  • HeardMessageTable
  • HeardFollowingTable
  • HeardUserTable
  1. Add the following indexes:
  • In HeardMessageTable, create an authorId-index with the authorId as the primary / partition key.
  • In HeardFollowingTable, create a followingId-index with the followingId as the primary / partition key.
  • In HeardFollowingTable, create a followerId-index with the followerId as the primary / partition key.

To create an index, click on the table you would like to create an index on, click on the indexes tab, then click Create Index .

  1. Create the following resolvers:

Message author: User: HeardUserTable

// request mapping template
{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "userId": $util.dynamodb.toDynamoDBJson($ctx.source.authorId),
    }
}

// response mapping template
$util.toJson($ctx.result)

ListUserConnection items: [User]: HeardUserTable

// request mapping template
{
    "version" : "2017-02-28",
    "operation" : "Scan",
}

// response mapping template
$util.toJson($ctx.result.items)

Query getUser(...): User: HeardUserTable

// request mapping template
{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "userId": $util.dynamodb.toDynamoDBJson($ctx.args.userId),
  },
}

// response mapping template
$util.toJson($context.result)

Query listUsers(...): ListUserConnection: HeardUserTable

// request mapping template
{
    "version" : "2017-02-28",
    "operation" : "Scan",
    "limit": $util.defaultIfNull(${ctx.args.limit}, 20),
    "nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.nextToken, null))
}

// response mapping template
$util.toJson($ctx.result.items)

Query listFollowing: [Following]: HeardFollowingTable

// request mapping template
{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index" : "followerId-index",
    "query" : {
        "expression": "followerId = :id",
        "expressionValues" : {
            ":id" : {
                "S" : "${ctx.identity.sub}"
            }
        }
    }
}

// response mapping template
$util.toJson($ctx.result.items)

Query queryMessagesByAuthorIdIndex(...): MessageConnection: HeardMessageTable

// request mapping template
{
  "version": "2017-02-28",
  "operation": "Query",
  "query": {
    "expression": "#authorId = :authorId",
    "expressionNames": {
      "#authorId": "authorId",
    },
    "expressionValues": {
      ":authorId": $util.dynamodb.toDynamoDBJson($ctx.args.authorId),
    },
  },
  "index": "authorId-index",
  "limit": $util.defaultIfNull($ctx.args.first, 20),
  "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.after, null)),
  "scanIndexForward": true,
  "select": "ALL_ATTRIBUTES",
}

// response mapping template
$util.toJson($context.result)

Mutation createFollowing(...): Following: HeardFollowingTable

// request mapping template
{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
     ## If object "id" should come from GraphQL arguments, change to $util.dynamodb.toDynamoDBJson($ctx.args.id)
    "id": $util.dynamodb.toDynamoDBJson($util.autoId()),
  },
  "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
  "condition": {
    "expression": "attribute_not_exists(#id)",
    "expressionNames": {
      "#id": "id",
    },
  },
}

// response mapping template
$util.toJson($context.result)

Mutation deleteFollowing(...): Following: HeardFollowingTable

// request mapping template
{
  "version": "2017-02-28",
  "operation": "DeleteItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id),
  },
}

// response mapping template
$util.toJson($context.result)

Mutation createUser(...): User: HeardUserTable

// request mapping template
{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
    "userId": $util.dynamodb.toDynamoDBJson($ctx.args.input.userId),
  },
  "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
  "condition": {
    "expression": "attribute_not_exists(#userId)",
    "expressionNames": {
      "#userId": "userId",
    },
  },
}

// response mapping template
$util.toJson($context.result)

Mutation createMessage(...): Message: HeardMessageTable

// request mapping template
#set($time = $util.time.nowISO8601())

#set($attribs = $util.dynamodb.toMapValues($ctx.args.input))
#set($attribs.createdAt = $util.dynamodb.toDynamoDB($time))
#set($attribs.messageId = $util.dynamodb.toDynamoDB($util.autoId()))

{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
    "authorId": $util.dynamodb.toDynamoDBJson($ctx.args.input.authorId),
    "createdAt": $util.dynamodb.toDynamoDBJson($time),
  },
  "attributeValues": $util.toJson($attribs),
  "condition": {
    "expression": "attribute_not_exists(#authorId) AND attribute_not_exists(#createdAt)",
    "expressionNames": {
      "#authorId": "authorId",
      "#createdAt": "createdAt",
    },
  },
}

// response mapping template
$util.toJson($context.result)

User messages(...): MessageConnection: HeardMessageTable

// request mapping template
{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index" : "authorId-index",
    "query" : {
        "expression": "authorId = :id",
        "expressionValues" : {
            ":id" : {
                "S" : "${ctx.source.userId}"
            }
        }
    },
    "limit": $util.defaultIfNull(${ctx.args.first}, 20),
    "nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))
}

// response mapping template
$util.toJson($ctx.result)

User following(...): UserFollowingConnection: HeardFollowingTable

// request mapping template
{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index" : "followerId-index",
    "query" : {
        "expression": "followerId = :id",
        "expressionValues" : {
            ":id" : {
                "S" : "${ctx.source.userId}"
            }
        }
    }
    ## ,
    ## "limit": $util.defaultIfNull(${ctx.args.first}, 20),
    ## "nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))
}

// response mapping template
## Pass back the result from DynamoDB. **
## $util.qr($util.error($ctx.result))
$util.toJson($ctx.result)

UserFollowingConnection items: [User]: HeardUserTable

// request mapping template
## UserFollowingConnection.items.request.vtl **
 
#set($ids = [])
#foreach($following in ${ctx.source.items})
    #set($map = {})
    $util.qr($map.put("userId", $util.dynamodb.toString($following.get("followerId"))))
    $util.qr($ids.add($map))
#end
 
{
    "version" : "2018-05-29",
    "operation" : "BatchGetItem",
    "tables" : {
        "HeardUserTable": {
           "keys": $util.toJson($ids),
           "consistentRead": true
       }
    }
}

// response mapping template
## Pass back the result from DynamoDB. **
#if( ! ${ctx.result.data} )
  $util.toJson([])
#else
  $util.toJson($ctx.result.data.HeardUserTable)
#end

## $util.toJson($ctx.result.data.HeardUserTable)