├── Chapter01
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── package-lock.json
├── package.json
├── public
│ ├── index.html
│ └── uploads
│ │ ├── avatar1.png
│ │ └── avatar2.png
├── src
│ └── client
│ │ ├── App.js
│ │ └── index.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter02
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ └── index.js
│ └── server
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ └── services
│ │ ├── graphql
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter03
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── dist
│ └── client
│ │ ├── bundle.css
│ │ ├── bundle.js
│ │ ├── bundle.js.LICENSE.txt
│ │ └── index.html
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ └── index.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ └── 20210512110054-create-message.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter04
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── apollo
│ │ │ └── index.js
│ │ └── index.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ └── 20210512110054-create-message.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter05
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ └── deletePost.js
│ │ │ └── queries
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ └── searchQuery.js
│ │ ├── components
│ │ │ ├── bar
│ │ │ │ ├── index.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ └── post
│ │ │ │ ├── content.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ ├── index.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ └── 20210512110054-create-message.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter06
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ └── signup.js
│ │ │ └── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ └── searchQuery.js
│ │ ├── components
│ │ │ ├── bar
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ └── post
│ │ │ │ ├── content.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ ├── index.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── auth.js
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter07
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ └── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ └── searchQuery.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ └── post
│ │ │ │ ├── content.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ ├── index.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── auth.js
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter08
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── Main.js
│ │ ├── User.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ └── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ ├── getUser.js
│ │ │ │ └── searchQuery.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── home.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ ├── post
│ │ │ │ ├── content.js
│ │ │ │ ├── feedlist.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ │ └── user
│ │ │ │ ├── header.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── router.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ └── services
│ │ ├── graphql
│ │ ├── auth.js
│ │ ├── index.js
│ │ ├── resolvers.js
│ │ └── schema.js
│ │ └── index.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
└── webpack.client.config.js
├── Chapter09
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── Main.js
│ │ ├── User.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ └── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ ├── getUser.js
│ │ │ │ └── searchQuery.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── home.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ ├── post
│ │ │ │ ├── content.js
│ │ │ │ ├── feedlist.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ │ └── user
│ │ │ │ ├── header.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── router.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ ├── services
│ │ ├── graphql
│ │ │ ├── auth.js
│ │ │ ├── index.js
│ │ │ ├── resolvers.js
│ │ │ └── schema.js
│ │ └── index.js
│ │ └── ssr
│ │ ├── apollo.js
│ │ ├── app.js
│ │ ├── index.js
│ │ └── template.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
├── webpack.client.config.js
└── webpack.server.config.js
├── Chapter10
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── Main.js
│ │ ├── User.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ ├── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ ├── getUser.js
│ │ │ │ ├── messageAdded.js
│ │ │ │ └── searchQuery.js
│ │ │ └── subscriptions
│ │ │ │ └── messageAdded.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── home.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── chat
│ │ │ │ └── item.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ ├── post
│ │ │ │ ├── content.js
│ │ │ │ ├── feedlist.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ │ └── user
│ │ │ │ ├── header.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── router.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ ├── services
│ │ ├── graphql
│ │ │ ├── auth.js
│ │ │ ├── index.js
│ │ │ ├── resolvers.js
│ │ │ └── schema.js
│ │ ├── index.js
│ │ └── subscriptions
│ │ │ └── index.js
│ │ └── ssr
│ │ ├── apollo.js
│ │ ├── app.js
│ │ ├── index.js
│ │ └── template.js
├── styleguide.config.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
├── webpack.client.config.js
└── webpack.server.config.js
├── Chapter11
├── .babelrc
├── .gitignore
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── babel-hook.js
├── combined.log
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── Main.js
│ │ ├── User.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ ├── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ ├── getUser.js
│ │ │ │ ├── messageAdded.js
│ │ │ │ └── searchQuery.js
│ │ │ └── subscriptions
│ │ │ │ └── messageAdded.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── home.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── chat
│ │ │ │ └── item.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ ├── post
│ │ │ │ ├── content.js
│ │ │ │ ├── feedlist.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ │ └── user
│ │ │ │ ├── header.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── router.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ ├── services
│ │ ├── graphql
│ │ │ ├── auth.js
│ │ │ ├── index.js
│ │ │ ├── resolvers.js
│ │ │ └── schema.js
│ │ ├── index.js
│ │ └── subscriptions
│ │ │ └── index.js
│ │ └── ssr
│ │ ├── apollo.js
│ │ ├── app.js
│ │ ├── index.js
│ │ └── template.js
├── styleguide.config.js
├── test
│ └── app.test.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
├── webpack.client.config.js
└── webpack.server.config.js
├── Chapter12
├── .babelrc
├── .circleci
│ └── config.yml
├── .dockerignore
├── .env
├── .gitignore
├── Dockerfile
├── README.md
├── assets
│ └── css
│ │ └── style.css
├── babel-hook.js
├── combined.log
├── dist
│ ├── client
│ │ ├── bundle.css
│ │ ├── bundle.js
│ │ ├── bundle.js.LICENSE.txt
│ │ └── index.html
│ └── server
│ │ └── bundle.js
├── error.log
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── client
│ │ ├── App.js
│ │ ├── Chat.js
│ │ ├── Chats.js
│ │ ├── Feed.js
│ │ ├── Main.js
│ │ ├── User.js
│ │ ├── apollo
│ │ │ ├── fragments
│ │ │ │ └── userAttributes.js
│ │ │ ├── index.js
│ │ │ ├── mutations
│ │ │ │ ├── addMessage.js
│ │ │ │ ├── addPost.js
│ │ │ │ ├── deletePost.js
│ │ │ │ ├── login.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── signup.js
│ │ │ │ └── uploadAvatar.js
│ │ │ ├── queries
│ │ │ │ ├── currentUserQuery.js
│ │ │ │ ├── getChat.js
│ │ │ │ ├── getChats.js
│ │ │ │ ├── getPosts.js
│ │ │ │ ├── getUser.js
│ │ │ │ ├── messageAdded.js
│ │ │ │ └── searchQuery.js
│ │ │ └── subscriptions
│ │ │ │ └── messageAdded.js
│ │ ├── components
│ │ │ ├── avatarModal.js
│ │ │ ├── bar
│ │ │ │ ├── home.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logout.js
│ │ │ │ ├── search.js
│ │ │ │ ├── searchList.js
│ │ │ │ └── user.js
│ │ │ ├── chat
│ │ │ │ └── item.js
│ │ │ ├── context
│ │ │ │ └── user.js
│ │ │ ├── error.js
│ │ │ ├── fontawesome.js
│ │ │ ├── helpers
│ │ │ │ └── dropdown.js
│ │ │ ├── loading.js
│ │ │ ├── loginregister.js
│ │ │ ├── post
│ │ │ │ ├── content.js
│ │ │ │ ├── feedlist.js
│ │ │ │ ├── header.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.md
│ │ │ └── user
│ │ │ │ ├── header.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── router.js
│ │ └── styleguide
│ │ │ └── Wrapper.js
│ └── server
│ │ ├── config
│ │ └── index.js
│ │ ├── database
│ │ └── index.js
│ │ ├── helpers
│ │ └── logger.js
│ │ ├── index.js
│ │ ├── migrations
│ │ ├── 20210415201849-create-post.js
│ │ ├── 20210509181257-create-user.js
│ │ ├── 20210509181635-add-userId-to-post.js
│ │ ├── 20210512105419-create-chat.js
│ │ ├── 20210512105435-create-user-chats.js
│ │ ├── 20210512110054-create-message.js
│ │ └── 20210621175316-add-email-password-to-post.js
│ │ ├── models
│ │ ├── chat.js
│ │ ├── index.js
│ │ ├── message.js
│ │ ├── post.js
│ │ └── user.js
│ │ ├── seeders
│ │ ├── 20210512085648-fake-users.js
│ │ ├── 20210512093925-fake-posts.js
│ │ ├── 20210512114115-fake-chats.js
│ │ ├── 20210512114552-fake-chats-users-relations.js
│ │ └── 20210512121856-fake-messages.js
│ │ ├── services
│ │ ├── graphql
│ │ │ ├── auth.js
│ │ │ ├── index.js
│ │ │ ├── resolvers.js
│ │ │ └── schema.js
│ │ ├── index.js
│ │ └── subscriptions
│ │ │ └── index.js
│ │ └── ssr
│ │ ├── apollo.js
│ │ ├── app.js
│ │ ├── index.js
│ │ └── template.js
├── start.sh
├── styleguide.config.js
├── test
│ └── app.test.js
├── uploads
│ ├── avatar1.png
│ └── avatar2.png
├── webpack.client.build.config.js
├── webpack.client.config.js
├── webpack.server.build.config.js
└── webpack.server.config.js
├── LICENSE
└── README.md
/Chapter01/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter01/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
--------------------------------------------------------------------------------
/Chapter01/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter01/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter01/public/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter01/public/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter01/public/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter01/public/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter01/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/Chapter01/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter01/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter01/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter01/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter02/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter02/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
--------------------------------------------------------------------------------
/Chapter02/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter02/combined.log:
--------------------------------------------------------------------------------
1 | {"level":"info","message":"Post was created"}
2 | {"level":"info","message":"Post was created"}
3 |
--------------------------------------------------------------------------------
/Chapter02/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter02/error.log
--------------------------------------------------------------------------------
/Chapter02/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter02/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/Chapter02/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter02/src/server/services/graphql/index.js:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from 'apollo-server-express';
2 | import { makeExecutableSchema } from '@graphql-tools/schema';
3 | import Resolvers from './resolvers';
4 | import Schema from './schema';
5 |
6 | const executableSchema = makeExecutableSchema({
7 | typeDefs: Schema,
8 | resolvers: Resolvers
9 | });
10 | const server = new ApolloServer({
11 | schema: executableSchema,
12 | context: ({ req }) => req
13 | });
14 |
15 | export default server;
--------------------------------------------------------------------------------
/Chapter02/src/server/services/graphql/schema.js:
--------------------------------------------------------------------------------
1 | const typeDefinitions = `
2 | type Post {
3 | id: Int
4 | text: String
5 | user: User
6 | }
7 |
8 | type User {
9 | avatar: String
10 | username: String
11 | }
12 |
13 | type RootQuery {
14 | posts: [Post]
15 | }
16 |
17 | input PostInput {
18 | text: String!
19 | }
20 |
21 | input UserInput {
22 | username: String!
23 | avatar: String!
24 | }
25 |
26 | type RootMutation {
27 | addPost (
28 | post: PostInput!
29 | user: UserInput!
30 | ): Post
31 | }
32 |
33 | schema {
34 | query: RootQuery
35 | mutation: RootMutation
36 | }
37 | `;
38 |
39 | export default [typeDefinitions];
40 |
--------------------------------------------------------------------------------
/Chapter02/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default {
4 | graphql,
5 | };
6 |
--------------------------------------------------------------------------------
/Chapter02/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter02/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter02/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter02/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter03/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter03/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
--------------------------------------------------------------------------------
/Chapter03/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter03/combined.log:
--------------------------------------------------------------------------------
1 | {"level":"info","message":"Post was created"}
2 | {"level":"info","message":"Post was created"}
3 | {"level":"info","message":"Post was created"}
4 | {"level":"info","message":"Post was created"}
5 | {"level":"info","message":"Message was created"}
6 | {"level":"info","message":"Message was created"}
7 |
--------------------------------------------------------------------------------
/Chapter03/dist/client/index.html:
--------------------------------------------------------------------------------
1 | Graphbook
--------------------------------------------------------------------------------
/Chapter03/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter03/error.log
--------------------------------------------------------------------------------
/Chapter03/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter03/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/Chapter03/src/server/config/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "development": {
3 | "username": "devuser",
4 | "password": "PASSWORD",
5 | "database": "graphbook_dev",
6 | "host": "localhost",
7 | "dialect": "mysql",
8 | "pool": {
9 | "max": 5,
10 | "min": 0,
11 | "acquire": 30000,
12 | "idle": 10000
13 | }
14 | },
15 | "production": {
16 | "host": process.env.host,
17 | "username": process.env.username,
18 | "password": process.env.password,
19 | "database": process.env.database,
20 | "logging": false,
21 | "dialect": "mysql",
22 | "pool": {
23 | "max": 5,
24 | "min": 0,
25 | "acquire": 30000,
26 | "idle": 10000
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Chapter03/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter03/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter03/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter03/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter03/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter03/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter03/src/server/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class User extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.hasMany(models.Post);
14 | this.belongsToMany(models.Chat, { through: 'users_chats' });
15 | }
16 | };
17 | User.init({
18 | avatar: DataTypes.STRING,
19 | username: DataTypes.STRING
20 | }, {
21 | sequelize,
22 | modelName: 'User',
23 | });
24 | return User;
25 | };
--------------------------------------------------------------------------------
/Chapter03/src/server/seeders/20210512085648-fake-users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Users', [{
6 | avatar: '/uploads/avatar1.png',
7 | username: 'TestUser',
8 | createdAt: new Date(),
9 | updatedAt: new Date(),
10 | },
11 | {
12 | avatar: '/uploads/avatar2.png',
13 | username: 'TestUser2',
14 | createdAt: new Date(),
15 | updatedAt: new Date(),
16 | }],
17 | {});
18 | },
19 | down: async (queryInterface, Sequelize) => {
20 | return queryInterface.bulkDelete('Users', null, {});
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter03/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter03/src/server/services/graphql/index.js:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from 'apollo-server-express';
2 | import { makeExecutableSchema } from '@graphql-tools/schema';
3 | import Resolvers from './resolvers';
4 | import Schema from './schema';
5 |
6 | export default (utils) => {
7 | const executableSchema = makeExecutableSchema({
8 | typeDefs: Schema,
9 | resolvers: Resolvers.call(utils),
10 | });
11 | const server = new ApolloServer({
12 | schema: executableSchema,
13 | context: ({ req }) => req
14 | });
15 | return server;
16 | }
--------------------------------------------------------------------------------
/Chapter03/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter03/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter03/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter03/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter03/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter04/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter04/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter04/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter04/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter04/error.log
--------------------------------------------------------------------------------
/Chapter04/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter04/src/client/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import Feed from './Feed';
4 | import Chats from './Chats';
5 | import '../../assets/css/style.css';
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 | Graphbook - Feed
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default App
--------------------------------------------------------------------------------
/Chapter04/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | , document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/Chapter04/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter04/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter04/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter04/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter04/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter04/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter04/src/server/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class User extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.hasMany(models.Post);
14 | this.belongsToMany(models.Chat, { through: 'users_chats' });
15 | }
16 | };
17 | User.init({
18 | avatar: DataTypes.STRING,
19 | username: DataTypes.STRING
20 | }, {
21 | sequelize,
22 | modelName: 'User',
23 | });
24 | return User;
25 | };
--------------------------------------------------------------------------------
/Chapter04/src/server/seeders/20210512085648-fake-users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Users', [{
6 | avatar: '/uploads/avatar1.png',
7 | username: 'TestUser',
8 | createdAt: new Date(),
9 | updatedAt: new Date(),
10 | },
11 | {
12 | avatar: '/uploads/avatar2.png',
13 | username: 'TestUser2',
14 | createdAt: new Date(),
15 | updatedAt: new Date(),
16 | }],
17 | {});
18 | },
19 | down: async (queryInterface, Sequelize) => {
20 | return queryInterface.bulkDelete('Users', null, {});
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter04/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter04/src/server/services/graphql/index.js:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from 'apollo-server-express';
2 | import { makeExecutableSchema } from '@graphql-tools/schema';
3 | import Resolvers from './resolvers';
4 | import Schema from './schema';
5 |
6 | export default (utils) => {
7 | const executableSchema = makeExecutableSchema({
8 | typeDefs: Schema,
9 | resolvers: Resolvers.call(utils),
10 | });
11 | const server = new ApolloServer({
12 | schema: executableSchema,
13 | context: ({ req }) => req
14 | });
15 | return server;
16 | }
--------------------------------------------------------------------------------
/Chapter04/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter04/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter04/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter04/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter04/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter05/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter05/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter05/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter05/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter05/error.log
--------------------------------------------------------------------------------
/Chapter05/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter05/src/client/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import Feed from './Feed';
4 | import Chats from './Chats';
5 | import Bar from './components/bar';
6 | import './components/fontawesome';
7 | import '../../assets/css/style.css';
8 |
9 | const App = () => {
10 | return (
11 |
12 |
13 | Graphbook - Feed
14 |
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default App
--------------------------------------------------------------------------------
/Chapter05/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | username
6 | avatar
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/Chapter05/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 | import { useQuery } from '@apollo/client';
4 |
5 | export const GET_CHAT = gql`
6 | query chat($chatId: Int!) {
7 | chat(chatId: $chatId) {
8 | id
9 | users {
10 | id
11 | ...userAttributes
12 | }
13 | messages {
14 | id
15 | text
16 | user {
17 | id
18 | }
19 | }
20 | }
21 | }
22 | ${USER_ATTRIBUTES}
23 | `;
24 |
25 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter05/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 | import { useQuery } from '@apollo/client';
4 |
5 | export const GET_CHATS = gql`
6 | query chats {
7 | chats {
8 | id
9 | users {
10 | id
11 | ...userAttributes
12 | }
13 | lastMessage {
14 | text
15 | }
16 | }
17 | }
18 | ${USER_ATTRIBUTES}
19 | `;
20 |
21 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter05/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 | import { useQuery } from '@apollo/client';
4 |
5 | export const GET_POSTS = gql`
6 | query postsFeed($page: Int, $limit: Int) {
7 | postsFeed(page: $page, limit: $limit) {
8 | posts {
9 | id
10 | text
11 | user {
12 | ...userAttributes
13 | }
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetPostsQuery = () => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10 } });
--------------------------------------------------------------------------------
/Chapter05/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 | import { useQuery } from '@apollo/client';
4 |
5 | export const GET_USERS = gql`
6 | query usersSearch($page: Int, $limit: Int, $text: String!) {
7 | usersSearch(page: $page, limit: $limit, text: $text) {
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | }
13 | }
14 | ${USER_ATTRIBUTES}
15 | `;
16 |
17 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
18 |
19 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
20 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 |
6 | const Bar = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default Bar
20 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const UserBar = ({ user }) => {
4 | if(!user) return null;
5 |
6 | return (
7 |
8 |

9 |
{user.username}
10 |
11 | );
12 | }
13 |
14 | export default UserBar
15 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 |
4 | export const UserConsumer = ({ children }) => {
5 | return (
6 |
7 | {client => {
8 | // Use client.readQuery to get the current logged in user.
9 | const user = {
10 | username: "Test User",
11 | avatar: "/uploads/avatar1.png"
12 | };
13 | return React.Children.map(children, function(child){
14 | return React.cloneElement(child, { user });
15 | });
16 | }}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/post/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dropdown from '../helpers/dropdown';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { useDeletePostMutation } from '../../apollo/mutations/deletePost';
5 |
6 | export default ({post}) => {
7 | const [deletePost] = useDeletePostMutation(post.id);
8 |
9 | return (
10 |
11 |

12 |
13 |
{post.user.username}
14 |
15 |
}>
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter05/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter05/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter05/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter05/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter05/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter05/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter05/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter05/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter05/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter05/src/server/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class User extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.hasMany(models.Post);
14 | this.belongsToMany(models.Chat, { through: 'users_chats' });
15 | }
16 | };
17 | User.init({
18 | avatar: DataTypes.STRING,
19 | username: DataTypes.STRING
20 | }, {
21 | sequelize,
22 | modelName: 'User',
23 | });
24 | return User;
25 | };
--------------------------------------------------------------------------------
/Chapter05/src/server/seeders/20210512085648-fake-users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Users', [{
6 | avatar: '/uploads/avatar1.png',
7 | username: 'TestUser',
8 | createdAt: new Date(),
9 | updatedAt: new Date(),
10 | },
11 | {
12 | avatar: '/uploads/avatar2.png',
13 | username: 'TestUser2',
14 | createdAt: new Date(),
15 | updatedAt: new Date(),
16 | }],
17 | {});
18 | },
19 | down: async (queryInterface, Sequelize) => {
20 | return queryInterface.bulkDelete('Users', null, {});
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter05/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter05/src/server/services/graphql/index.js:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from 'apollo-server-express';
2 | import { makeExecutableSchema } from '@graphql-tools/schema';
3 | import Resolvers from './resolvers';
4 | import Schema from './schema';
5 |
6 | export default (utils) => {
7 | const executableSchema = makeExecutableSchema({
8 | typeDefs: Schema,
9 | resolvers: Resolvers.call(utils),
10 | });
11 | const server = new ApolloServer({
12 | schema: executableSchema,
13 | context: ({ req }) => req
14 | });
15 | return server;
16 | }
--------------------------------------------------------------------------------
/Chapter05/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter05/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter05/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter05/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter05/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter05/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter06/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter06/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter06/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter06/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter06/error.log
--------------------------------------------------------------------------------
/Chapter06/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | username
6 | avatar
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int) {
6 | postsFeed(page: $page, limit: $limit) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = () => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10 } });
--------------------------------------------------------------------------------
/Chapter06/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter06/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 |
7 | const Bar = ({ changeLoginState }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default Bar
24 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 |
4 | const Logout = ({ changeLoginState, client }) => {
5 | const logout = () => {
6 | localStorage.removeItem('jwt');
7 | changeLoginState(false);
8 | client.stop();
9 | client.resetStore();
10 | }
11 |
12 | return (
13 |
14 | );
15 | }
16 |
17 | export default withApollo(Logout);
18 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const UserBar = ({ user }) => {
4 | if(!user) return null;
5 |
6 | return (
7 |
8 |

9 |
{user.username}
10 |
11 | );
12 | }
13 |
14 | export default UserBar
15 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/post/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dropdown from '../helpers/dropdown';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { useDeletePostMutation } from '../../apollo/mutations/deletePost';
5 |
6 | export default ({post}) => {
7 | const [deletePost] = useDeletePostMutation(post.id);
8 |
9 | return (
10 |
11 |

12 |
13 |
{post.user.username}
14 |
15 |
}>
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter06/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter06/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter06/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter06/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter06/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter06/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter06/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter06/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter06/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter06/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter06/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter06/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter06/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter06/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter06/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter06/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter06/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter07/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter07/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter07/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter07/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter07/error.log
--------------------------------------------------------------------------------
/Chapter07/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int) {
6 | postsFeed(page: $page, limit: $limit) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = () => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10 } });
--------------------------------------------------------------------------------
/Chapter07/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter07/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 |
7 | const Bar = ({ changeLoginState }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default Bar
24 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 |
4 | const Logout = ({ changeLoginState, client }) => {
5 | const logout = () => {
6 | localStorage.removeItem('jwt');
7 | changeLoginState(false);
8 | client.stop();
9 | client.resetStore();
10 | }
11 |
12 | return (
13 |
14 | );
15 | }
16 |
17 | export default withApollo(Logout);
18 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/post/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dropdown from '../helpers/dropdown';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { useDeletePostMutation } from '../../apollo/mutations/deletePost';
5 |
6 | export default ({post}) => {
7 | const [deletePost] = useDeletePostMutation(post.id);
8 |
9 | return (
10 |
11 |

12 |
13 |
{post.user.username}
14 |
15 |
}>
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter07/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter07/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter07/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter07/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter07/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter07/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter07/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter07/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter07/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter07/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter07/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter07/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter07/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter07/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter07/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter07/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter07/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter08/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter08/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter08/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter08/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter08/error.log
--------------------------------------------------------------------------------
/Chapter08/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter08/src/client/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Feed from './Feed';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const Main = ({ changeLoginState }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default Main;
17 |
--------------------------------------------------------------------------------
/Chapter08/src/client/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserProfile from './components/user';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const User = ({ changeLoginState, match }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default User
17 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int, $username: String) {
6 | postsFeed(page: $page, limit: $limit, username: $username) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = (variables) => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10, ...variables } });
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/getUser.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USER = gql`
5 | query user($username: String!) {
6 | user(username: $username) {
7 | ...userAttributes
8 | }
9 | }
10 | ${USER_ATTRIBUTES}
11 | `;
12 |
13 | export const useGetUserQuery = (variables) => useQuery(GET_USER, { variables: { ...variables }});
14 |
--------------------------------------------------------------------------------
/Chapter08/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter08/src/client/components/bar/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | const Home = ({ history }) => {
5 | const goHome = () => {
6 | history.push('/app');
7 | }
8 |
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default withRouter(Home);
15 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 | import Home from './home';
7 |
8 | const Bar = ({ changeLoginState }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Bar
26 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 |
4 | const Logout = ({ changeLoginState, client }) => {
5 | const logout = () => {
6 | localStorage.removeItem('jwt');
7 | changeLoginState(false);
8 | client.stop();
9 | client.resetStore();
10 | }
11 |
12 | return (
13 |
14 | );
15 | }
16 |
17 | export default withApollo(Logout);
18 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter08/src/client/components/user/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const UserProfileHeader = ({user}) => {
4 | const { avatar, username } = user;
5 |
6 | return (
7 |
8 |
9 |

10 |
11 |
12 |
{username}
13 |
You can provide further information here and build your really personal header component for your users.
14 |
15 |
16 | )
17 | }
18 |
19 | export default UserProfileHeader;
20 |
--------------------------------------------------------------------------------
/Chapter08/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter08/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter08/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter08/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter08/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter08/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter08/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter08/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter08/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter08/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter08/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter08/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter08/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter08/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter08/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter08/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter09/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter09/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter09/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter09/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter09/error.log
--------------------------------------------------------------------------------
/Chapter09/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter09/src/client/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Feed from './Feed';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const Main = ({ changeLoginState }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default Main;
17 |
--------------------------------------------------------------------------------
/Chapter09/src/client/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserProfile from './components/user';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const User = ({ changeLoginState, match }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default User
17 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/mutations/logout.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGOUT = gql`
4 | mutation logout {
5 | logout {
6 | success
7 | }
8 | }
9 | `;
10 |
11 | export const useLogoutMutation = () => useMutation(LOGOUT);
12 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int, $username: String) {
6 | postsFeed(page: $page, limit: $limit, username: $username) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = (variables) => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10, ...variables } });
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/getUser.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USER = gql`
5 | query user($username: String!) {
6 | user(username: $username) {
7 | ...userAttributes
8 | }
9 | }
10 | ${USER_ATTRIBUTES}
11 | `;
12 |
13 | export const useGetUserQuery = (variables) => useQuery(GET_USER, { variables: { ...variables }});
14 |
--------------------------------------------------------------------------------
/Chapter09/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter09/src/client/components/bar/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | const Home = ({ history }) => {
5 | const goHome = () => {
6 | history.push('/app');
7 | }
8 |
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default withRouter(Home);
15 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 | import Home from './home';
7 |
8 | const Bar = ({ changeLoginState }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Bar
26 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 | import { useLogoutMutation } from '../../apollo/mutations/logout';
4 |
5 | const Logout = ({ changeLoginState, client }) => {
6 | const [logoutMutation] = useLogoutMutation();
7 |
8 | const logout = () => {
9 | logoutMutation().then(() => {
10 | localStorage.removeItem('jwt');
11 | changeLoginState(false);
12 | client.stop();
13 | client.resetStore();
14 | });
15 | }
16 |
17 | return (
18 |
19 | );
20 | }
21 |
22 | export default withApollo(Logout);
23 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter09/src/client/components/user/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const UserProfileHeader = ({user}) => {
4 | const { avatar, username } = user;
5 |
6 | return (
7 |
8 |
9 |

10 |
11 |
12 |
{username}
13 |
You can provide further information here and build your really personal header component for your users.
14 |
15 |
16 | )
17 | }
18 |
19 | export default UserProfileHeader;
20 |
--------------------------------------------------------------------------------
/Chapter09/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.hydrate(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter09/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter09/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter09/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter09/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter09/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter09/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter09/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter09/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter09/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter09/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 |
3 | export default utils => ({
4 | graphql: graphql(utils),
5 | });
6 |
--------------------------------------------------------------------------------
/Chapter09/src/server/ssr/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from '@apollo/client';
3 | import App from './app';
4 |
5 | const ServerClient = ({ client, location, context, loggedIn }) => {
6 | return(
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default ServerClient
14 |
--------------------------------------------------------------------------------
/Chapter09/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter09/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter09/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter09/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter09/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter10/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter10/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter10/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter10/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter10/error.log
--------------------------------------------------------------------------------
/Chapter10/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter10/src/client/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Feed from './Feed';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const Main = ({ changeLoginState }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default Main;
17 |
--------------------------------------------------------------------------------
/Chapter10/src/client/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserProfile from './components/user';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const User = ({ changeLoginState, match }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default User
17 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/mutations/logout.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGOUT = gql`
4 | mutation logout {
5 | logout {
6 | success
7 | }
8 | }
9 | `;
10 |
11 | export const useLogoutMutation = () => useMutation(LOGOUT);
12 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int, $username: String) {
6 | postsFeed(page: $page, limit: $limit, username: $username) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = (variables) => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10, ...variables } });
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/getUser.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USER = gql`
5 | query user($username: String!) {
6 | user(username: $username) {
7 | ...userAttributes
8 | }
9 | }
10 | ${USER_ATTRIBUTES}
11 | `;
12 |
13 | export const useGetUserQuery = (variables) => useQuery(GET_USER, { variables: { ...variables }});
14 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter10/src/client/apollo/subscriptions/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { useSubscription, gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
20 | export const useMessageAddedSubscription = (options) => useSubscription(MESSAGES_SUBSCRIPTION, options);
21 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/bar/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | const Home = ({ history }) => {
5 | const goHome = () => {
6 | history.push('/app');
7 | }
8 |
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default withRouter(Home);
15 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 | import Home from './home';
7 |
8 | const Bar = ({ changeLoginState }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Bar
26 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 | import { useLogoutMutation } from '../../apollo/mutations/logout';
4 |
5 | const Logout = ({ changeLoginState, client }) => {
6 | const [logoutMutation] = useLogoutMutation();
7 |
8 | const logout = () => {
9 | logoutMutation().then(() => {
10 | localStorage.removeItem('jwt');
11 | changeLoginState(false);
12 | client.stop();
13 | client.resetStore();
14 | });
15 | }
16 |
17 | return (
18 |
19 | );
20 | }
21 |
22 | export default withApollo(Logout);
23 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter10/src/client/components/user/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const UserProfileHeader = ({user}) => {
4 | const { avatar, username } = user;
5 |
6 | return (
7 |
8 |
9 |

10 |
11 |
12 |
{username}
13 |
You can provide further information here and build your really personal header component for your users.
14 |
15 |
16 | )
17 | }
18 |
19 | export default UserProfileHeader;
20 |
--------------------------------------------------------------------------------
/Chapter10/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.hydrate(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter10/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter10/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter10/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter10/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter10/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter10/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter10/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter10/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter10/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter10/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 | import subscriptions from './subscriptions';
3 |
4 | export default utils => ({
5 | graphql: graphql(utils),
6 | subscriptions: subscriptions(utils),
7 | });
8 |
--------------------------------------------------------------------------------
/Chapter10/src/server/ssr/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from '@apollo/client';
3 | import App from './app';
4 |
5 | const ServerClient = ({ client, location, context, loggedIn }) => {
6 | return(
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default ServerClient
14 |
--------------------------------------------------------------------------------
/Chapter10/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter10/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter10/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter10/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter10/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter11/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"]
3 | }
--------------------------------------------------------------------------------
/Chapter11/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter11/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter11/babel-hook.js:
--------------------------------------------------------------------------------
1 | require("@babel/register")({
2 | "plugins": [
3 | "require-context-hook"
4 | ],
5 | "presets": ["@babel/env","@babel/react"]
6 | });
7 |
--------------------------------------------------------------------------------
/Chapter11/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter11/error.log
--------------------------------------------------------------------------------
/Chapter11/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter11/src/client/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Feed from './Feed';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const Main = ({ changeLoginState }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default Main;
17 |
--------------------------------------------------------------------------------
/Chapter11/src/client/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserProfile from './components/user';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const User = ({ changeLoginState, match }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default User
17 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/mutations/logout.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGOUT = gql`
4 | mutation logout {
5 | logout {
6 | success
7 | }
8 | }
9 | `;
10 |
11 | export const useLogoutMutation = () => useMutation(LOGOUT);
12 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int, $username: String) {
6 | postsFeed(page: $page, limit: $limit, username: $username) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = (variables) => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10, ...variables } });
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/getUser.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USER = gql`
5 | query user($username: String!) {
6 | user(username: $username) {
7 | ...userAttributes
8 | }
9 | }
10 | ${USER_ATTRIBUTES}
11 | `;
12 |
13 | export const useGetUserQuery = (variables) => useQuery(GET_USER, { variables: { ...variables }});
14 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter11/src/client/apollo/subscriptions/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { useSubscription, gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
20 | export const useMessageAddedSubscription = (options) => useSubscription(MESSAGES_SUBSCRIPTION, options);
21 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/bar/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | const Home = ({ history }) => {
5 | const goHome = () => {
6 | history.push('/app');
7 | }
8 |
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default withRouter(Home);
15 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 | import Home from './home';
7 |
8 | const Bar = ({ changeLoginState }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Bar
26 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 | import { useLogoutMutation } from '../../apollo/mutations/logout';
4 |
5 | const Logout = ({ changeLoginState, client }) => {
6 | const [logoutMutation] = useLogoutMutation();
7 |
8 | const logout = () => {
9 | logoutMutation().then(() => {
10 | localStorage.removeItem('jwt');
11 | changeLoginState(false);
12 | client.stop();
13 | client.resetStore();
14 | });
15 | }
16 |
17 | return (
18 |
19 | );
20 | }
21 |
22 | export default withApollo(Logout);
23 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter11/src/client/components/user/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const UserProfileHeader = ({user}) => {
4 | const { avatar, username } = user;
5 |
6 | return (
7 |
8 |
9 |

10 |
11 |
12 |
{username}
13 |
You can provide further information here and build your really personal header component for your users.
14 |
15 |
16 | )
17 | }
18 |
19 | export default UserProfileHeader;
20 |
--------------------------------------------------------------------------------
/Chapter11/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.hydrate(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter11/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter11/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter11/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter11/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter11/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter11/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter11/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter11/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter11/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter11/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 | import subscriptions from './subscriptions';
3 |
4 | export default utils => ({
5 | graphql: graphql(utils),
6 | subscriptions: subscriptions(utils),
7 | });
8 |
--------------------------------------------------------------------------------
/Chapter11/src/server/ssr/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from '@apollo/client';
3 | import App from './app';
4 |
5 | const ServerClient = ({ client, location, context, loggedIn }) => {
6 | return(
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default ServerClient
14 |
--------------------------------------------------------------------------------
/Chapter11/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter11/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter11/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter11/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter11/uploads/avatar2.png
--------------------------------------------------------------------------------
/Chapter12/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env","@babel/react"],
3 | "sourceType": "unambiguous"
4 | }
--------------------------------------------------------------------------------
/Chapter12/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/Chapter12/.env:
--------------------------------------------------------------------------------
1 | JWT_SECRET=YOUR_JWT_SECRET
2 | AWS_ACCESS_KEY_ID=YOUR_AWS_KEY_ID
3 | AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
4 | host=YOUR_HOST
5 | username=YOUR_USERNAME
6 | database=YOUR_DATABASE
7 | password=YOUR_PASSWORD
--------------------------------------------------------------------------------
/Chapter12/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | loading.gif
--------------------------------------------------------------------------------
/Chapter12/README.md:
--------------------------------------------------------------------------------
1 | Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React-2nd-Edition
2 |
--------------------------------------------------------------------------------
/Chapter12/babel-hook.js:
--------------------------------------------------------------------------------
1 | require("@babel/register")({
2 | "plugins": [
3 | "require-context-hook"
4 | ],
5 | "presets": ["@babel/env","@babel/react"]
6 | });
7 |
--------------------------------------------------------------------------------
/Chapter12/dist/client/index.html:
--------------------------------------------------------------------------------
1 | Graphbook
--------------------------------------------------------------------------------
/Chapter12/error.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter12/error.log
--------------------------------------------------------------------------------
/Chapter12/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graphbook
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Chapter12/src/client/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Feed from './Feed';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const Main = ({ changeLoginState }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default Main;
17 |
--------------------------------------------------------------------------------
/Chapter12/src/client/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import UserProfile from './components/user';
3 | import Chats from './Chats';
4 | import Bar from './components/bar';
5 |
6 | export const User = ({ changeLoginState, match }) => {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export default User
17 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/fragments/userAttributes.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const USER_ATTRIBUTES = gql`
4 | fragment userAttributes on User {
5 | id
6 | username
7 | avatar
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/mutations/login.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGIN = gql`
4 | mutation login($email : String!, $password : String!) {
5 | login(email : $email, password : $password) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useLoginMutation = () => useMutation(LOGIN);
12 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/mutations/logout.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const LOGOUT = gql`
4 | mutation logout {
5 | logout {
6 | success
7 | }
8 | }
9 | `;
10 |
11 | export const useLogoutMutation = () => useMutation(LOGOUT);
12 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/mutations/signup.js:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from '@apollo/client';
2 |
3 | export const SIGNUP = gql`
4 | mutation signup($email : String!, $password : String!, $username : String!) {
5 | signup(email : $email, password : $password, username : $username) {
6 | token
7 | }
8 | }
9 | `;
10 |
11 | export const useSignupMutation = () => useMutation(SIGNUP);
12 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/currentUserQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 |
3 | export const GET_CURRENT_USER = gql`
4 | query currentUser {
5 | currentUser {
6 | id
7 | username
8 | avatar
9 | }
10 | }
11 | `;
12 |
13 | export const useCurrentUserQuery = (options) => useQuery(GET_CURRENT_USER, options);
14 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/getChat.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHAT = gql`
5 | query chat($chatId: Int!) {
6 | chat(chatId: $chatId) {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | messages {
13 | id
14 | text
15 | user {
16 | id
17 | }
18 | }
19 | }
20 | }
21 | ${USER_ATTRIBUTES}
22 | `;
23 |
24 | export const useGetChatQuery = (chatId) => useQuery(GET_CHAT, { variables: { chatId }});
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/getChats.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_CHATS = gql`
5 | query chats {
6 | chats {
7 | id
8 | users {
9 | id
10 | ...userAttributes
11 | }
12 | lastMessage {
13 | text
14 | }
15 | }
16 | }
17 | ${USER_ATTRIBUTES}
18 | `;
19 |
20 | export const useGetChatsQuery = () => useQuery(GET_CHATS);
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/getPosts.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_POSTS = gql`
5 | query postsFeed($page: Int, $limit: Int, $username: String) {
6 | postsFeed(page: $page, limit: $limit, username: $username) {
7 | posts {
8 | id
9 | text
10 | user {
11 | ...userAttributes
12 | }
13 | }
14 | }
15 | }
16 | ${USER_ATTRIBUTES}
17 | `;
18 |
19 | export const useGetPostsQuery = (variables) => useQuery(GET_POSTS, { pollInterval: 5000, variables: { page: 0, limit: 10, ...variables } });
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/getUser.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USER = gql`
5 | query user($username: String!) {
6 | user(username: $username) {
7 | ...userAttributes
8 | }
9 | }
10 | ${USER_ATTRIBUTES}
11 | `;
12 |
13 | export const useGetUserQuery = (variables) => useQuery(GET_USER, { variables: { ...variables }});
14 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/queries/searchQuery.js:
--------------------------------------------------------------------------------
1 | import { gql, useQuery } from '@apollo/client';
2 | import { USER_ATTRIBUTES } from '../fragments/userAttributes';
3 |
4 | export const GET_USERS = gql`
5 | query usersSearch($page: Int, $limit: Int, $text: String!) {
6 | usersSearch(page: $page, limit: $limit, text: $text) {
7 | users {
8 | id
9 | ...userAttributes
10 | }
11 | }
12 | }
13 | ${USER_ATTRIBUTES}
14 | `;
15 |
16 | export const getUserSearchConfig = (text) => ({ variables: { page: 0, limit: 5, text }, skip: text.length < 3})
17 |
18 | export const useUserSearchQuery = (text) => useQuery(GET_USERS, getUserSearchConfig(text))
--------------------------------------------------------------------------------
/Chapter12/src/client/apollo/subscriptions/messageAdded.js:
--------------------------------------------------------------------------------
1 | import { useSubscription, gql } from '@apollo/client';
2 |
3 | export const MESSAGES_SUBSCRIPTION = gql`
4 | subscription onMessageAdded {
5 | messageAdded {
6 | id
7 | text
8 | chat {
9 | id
10 | }
11 | user {
12 | id
13 | __typename
14 | }
15 | __typename
16 | }
17 | }
18 | `;
19 |
20 | export const useMessageAddedSubscription = (options) => useSubscription(MESSAGES_SUBSCRIPTION, options);
21 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/bar/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router';
3 |
4 | const Home = ({ history }) => {
5 | const goHome = () => {
6 | history.push('/app');
7 | }
8 |
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default withRouter(Home);
15 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/bar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SearchBar from './search';
3 | import UserBar from './user';
4 | import { UserConsumer } from '../context/user';
5 | import Logout from './logout';
6 | import Home from './home';
7 |
8 | const Bar = ({ changeLoginState }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Bar
26 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/bar/logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withApollo } from '@apollo/client/react/hoc';
3 | import { useLogoutMutation } from '../../apollo/mutations/logout';
4 |
5 | const Logout = ({ changeLoginState, client }) => {
6 | const [logoutMutation] = useLogoutMutation();
7 |
8 | const logout = () => {
9 | logoutMutation().then(() => {
10 | localStorage.removeItem('jwt');
11 | changeLoginState(false);
12 | client.stop();
13 | client.resetStore();
14 | });
15 | }
16 |
17 | return (
18 |
19 | );
20 | }
21 |
22 | export default withApollo(Logout);
23 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/bar/search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useUserSearchQuery } from '../../apollo/queries/searchQuery';
3 | import SearchList from './searchList';
4 |
5 | const SearchBar = () => {
6 | const [text, setText] = useState('');
7 | const { loading, error, data } = useUserSearchQuery(text);
8 |
9 | const changeText = (event) => {
10 | setText(event.target.value);
11 | }
12 |
13 | return (
14 |
15 |
17 | {!loading && !error && data && (
18 |
19 | )}
20 |
21 | );
22 | }
23 |
24 | export default SearchBar
25 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/bar/user.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AvatarModal from '../avatarModal';
3 |
4 | const UserBar = ({ user }) => {
5 | const [isOpen, setIsOpen] = useState(false);
6 |
7 | const showModal = () => {
8 | setIsOpen(!isOpen);
9 | }
10 |
11 | if(!user) return null;
12 |
13 | return (
14 |
15 |

showModal()} />
16 |
17 | {user.username}
18 |
19 | );
20 | }
21 |
22 | export default UserBar
23 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/context/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloConsumer } from '@apollo/client';
3 | import { GET_CURRENT_USER } from '../../apollo/queries/currentUserQuery';
4 |
5 | export const UserConsumer = ({ children }) => {
6 | return (
7 |
8 | {client => {
9 | // Use client.readQuery to get the current logged in user.
10 | const result = client.readQuery({ query: GET_CURRENT_USER });
11 | return React.Children.map(children, function(child){
12 | return React.cloneElement(child, { user: result?.currentUser ? result.currentUser : null });
13 | });
14 | }}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/fontawesome.js:
--------------------------------------------------------------------------------
1 | import { library } from '@fortawesome/fontawesome-svg-core';
2 | import { faAngleDown } from '@fortawesome/free-solid-svg-icons';
3 |
4 | library.add(faAngleDown);
5 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({color, size}) => {
4 | var style = {
5 | backgroundColor: '#6ca6fd',
6 | width: 40,
7 | height: 40,
8 | };
9 |
10 | if(typeof color !== typeof undefined) {
11 | style.color = color;
12 | }
13 | if(typeof size !== typeof undefined) {
14 | style.width = size;
15 | style.height = size;
16 | }
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/post/content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({post}) =>
4 |
5 | {post.text}
6 |
7 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/post/index.md:
--------------------------------------------------------------------------------
1 | Post example:
2 |
3 | ```js
4 | const post = {
5 | id: 3,
6 | text: "This is a test post!",
7 | user: {
8 | avatar: "/uploads/avatar1.png",
9 | username: "Test User"
10 | }
11 | };
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Chapter12/src/client/components/user/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const UserProfileHeader = ({user}) => {
4 | const { avatar, username } = user;
5 |
6 | return (
7 |
8 |
9 |

10 |
11 |
12 |
{username}
13 |
You can provide further information here and build your really personal header component for your users.
14 |
15 |
16 | )
17 | }
18 |
19 | export default UserProfileHeader;
20 |
--------------------------------------------------------------------------------
/Chapter12/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { ApolloProvider } from '@apollo/client/react';
4 | import App from './App';
5 | import client from './apollo';
6 |
7 | ReactDOM.hydrate(
8 |
9 |
10 | , document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/Chapter12/src/client/styleguide/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import client from '../apollo';
3 | import { ApolloProvider } from '@apollo/client/react';
4 |
5 | const Wrapper = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default Wrapper
14 |
--------------------------------------------------------------------------------
/Chapter12/src/server/database/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | import configFile from '../config';
3 | import models from '../models';
4 |
5 | const env = process.env.NODE_ENV || 'development';
6 | const config = configFile[env];
7 |
8 | const sequelize = new Sequelize(config.database, config.username,
9 | config.password, config);
10 |
11 | const db = {
12 | models: models(sequelize),
13 | sequelize,
14 | };
15 |
16 | export default db;
17 |
--------------------------------------------------------------------------------
/Chapter12/src/server/helpers/logger.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 |
3 | let transports = [
4 | new winston.transports.File({
5 | filename: 'error.log',
6 | level: 'error',
7 | }),
8 | new winston.transports.File({
9 | filename: 'combined.log',
10 | level: 'verbose',
11 | }),
12 | ];
13 |
14 | if (process.env.NODE_ENV !== 'production') {
15 | transports.push(new winston.transports.Console());
16 | }
17 |
18 | const logger = winston.createLogger({
19 | level: 'info',
20 | format: winston.format.json(),
21 | transports,
22 | });
23 |
24 | export default logger;
25 |
--------------------------------------------------------------------------------
/Chapter12/src/server/migrations/20210415201849-create-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: async (queryInterface, Sequelize) => {
4 | await queryInterface.createTable('Posts', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | text: {
12 | type: Sequelize.TEXT
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE
17 | },
18 | updatedAt: {
19 | allowNull: false,
20 | type: Sequelize.DATE
21 | }
22 | });
23 | },
24 | down: async (queryInterface, Sequelize) => {
25 | await queryInterface.dropTable('Posts');
26 | }
27 | };
--------------------------------------------------------------------------------
/Chapter12/src/server/migrations/20210621175316-add-email-password-to-post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: (queryInterface, Sequelize) => {
5 | return Promise.all([
6 | queryInterface.addColumn('Users',
7 | 'email',
8 | {
9 | type: Sequelize.STRING,
10 | unique : true,
11 | }
12 | ),
13 | queryInterface.addColumn('Users',
14 | 'password',
15 | {
16 | type: Sequelize.STRING,
17 | }
18 | ),
19 | ]);
20 | },
21 |
22 | down: (queryInterface, Sequelize) => {
23 | return Promise.all([
24 | queryInterface.removeColumn('Users', 'email'),
25 | queryInterface.removeColumn('Users', 'password'),
26 | ]);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/Chapter12/src/server/models/index.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize';
2 | if (process.env.NODE_ENV === 'development') {
3 | require('babel-plugin-require-context-hook/register')()
4 | }
5 |
6 | export default (sequelize) => {
7 | let db = {};
8 |
9 | const context = require.context('.', true, /^\.\/(?!index\.js).*\.js$/, 'sync')
10 | context.keys().map(context).forEach(module => {
11 | const model = module(sequelize, Sequelize);
12 | db[model.name] = model;
13 | });
14 |
15 | Object.keys(db).forEach((modelName) => {
16 | if (db[modelName].associate) {
17 | db[modelName].associate(db);
18 | }
19 | });
20 |
21 | return db;
22 | };
23 |
--------------------------------------------------------------------------------
/Chapter12/src/server/models/message.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Message extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | this.belongsTo(models.Chat);
15 | }
16 | };
17 | Message.init({
18 | text: DataTypes.STRING,
19 | userId: DataTypes.INTEGER,
20 | chatId: DataTypes.INTEGER
21 | }, {
22 | sequelize,
23 | modelName: 'Message',
24 | });
25 | return Message;
26 | };
--------------------------------------------------------------------------------
/Chapter12/src/server/models/post.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {
3 | Model
4 | } = require('sequelize');
5 | module.exports = (sequelize, DataTypes) => {
6 | class Post extends Model {
7 | /**
8 | * Helper method for defining associations.
9 | * This method is not a part of Sequelize lifecycle.
10 | * The `models/index` file will call this method automatically.
11 | */
12 | static associate(models) {
13 | this.belongsTo(models.User);
14 | }
15 | };
16 | Post.init({
17 | text: DataTypes.TEXT,
18 | userId: DataTypes.INTEGER,
19 | }, {
20 | sequelize,
21 | modelName: 'Post',
22 | });
23 | return Post;
24 | };
--------------------------------------------------------------------------------
/Chapter12/src/server/seeders/20210512114115-fake-chats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | up: async (queryInterface, Sequelize) => {
5 | return queryInterface.bulkInsert('Chats', [{
6 | createdAt: new Date(),
7 | updatedAt: new Date(),
8 | }],
9 | {});
10 | },
11 | down: async (queryInterface, Sequelize) => {
12 | return queryInterface.bulkDelete('Chats', null, {});
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/Chapter12/src/server/services/index.js:
--------------------------------------------------------------------------------
1 | import graphql from './graphql';
2 | import subscriptions from './subscriptions';
3 |
4 | export default utils => ({
5 | graphql: graphql(utils),
6 | subscriptions: subscriptions(utils),
7 | });
8 |
--------------------------------------------------------------------------------
/Chapter12/src/server/ssr/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from '@apollo/client';
3 | import App from './app';
4 |
5 | const ServerClient = ({ client, location, context, loggedIn }) => {
6 | return(
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default ServerClient
14 |
--------------------------------------------------------------------------------
/Chapter12/start.sh:
--------------------------------------------------------------------------------
1 | sequelize db:migrate --migrations-path src/server/migrations --config src/server/config/index.js --env production
2 | npm run server:production
3 |
--------------------------------------------------------------------------------
/Chapter12/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | styleguideComponents: {
5 | Wrapper: path.join(__dirname, 'src/client/styleguide/Wrapper')
6 | },
7 | components: 'src/client/components/**/*.js',
8 | require: [
9 | path.join(__dirname, 'assets/css/style.css')
10 | ],
11 | webpackConfig: require('./webpack.client.config')
12 | }
--------------------------------------------------------------------------------
/Chapter12/uploads/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter12/uploads/avatar1.png
--------------------------------------------------------------------------------
/Chapter12/uploads/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Full-Stack-Web-Development-with-GraphQL-and-React-Second-Edition/128c2fcfcee9673f8571b80d454cead337865bb7/Chapter12/uploads/avatar2.png
--------------------------------------------------------------------------------