├── 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 --------------------------------------------------------------------------------