├── .gitignore ├── Chapter01 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── package.json ├── public │ └── index.html ├── src │ └── client │ │ ├── App.js │ │ └── index.js ├── stats.json ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter02 ├── .babelrc ├── .eslintrc ├── 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 ├── stats.json ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter03 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── 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 │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ └── 20181220211552-create-message.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter04 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── apollo │ │ │ └── index.js │ │ └── index.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ └── 20181220211552-create-message.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter05 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── bar │ │ │ │ ├── index.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ └── updatePost.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ └── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── postsFeed.js │ │ │ │ └── searchQuery.js │ │ └── index.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ └── 20181220211552-create-message.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter06 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── bar │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── signup.js │ │ │ │ └── updatePost.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ └── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ └── searchQuery.js │ │ └── index.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── auth.js │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter07 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ └── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ └── searchQuery.js │ │ └── index.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── auth.js │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter08 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ └── services │ │ ├── graphql │ │ ├── auth.js │ │ ├── index.js │ │ ├── resolvers.js │ │ └── schema.js │ │ └── index.js ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js └── webpack.client.config.js ├── Chapter09 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── logout.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-fake-messages.js │ │ ├── services │ │ ├── graphql │ │ │ ├── auth.js │ │ │ ├── index.js │ │ │ ├── resolvers.js │ │ │ └── schema.js │ │ └── index.js │ │ └── ssr │ │ ├── apollo.js │ │ ├── app.js │ │ ├── index.js │ │ └── template.js ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js ├── webpack.client.config.js └── webpack.server.config.js ├── Chapter10 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ ├── notification.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── logout.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ ├── subscriptions │ │ │ │ └── messageAdded.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-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 ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js ├── webpack.client.config.js └── webpack.server.config.js ├── Chapter11 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── babel-hook.js ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ ├── notification.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── logout.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ ├── subscriptions │ │ │ │ └── messageAdded.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-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 ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── test │ └── app.test.js ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js ├── webpack.client.config.js └── webpack.server.config.js ├── Chapter12 ├── .babelrc ├── .eslintrc ├── assets │ └── css │ │ └── style.css ├── babel-hook.js ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ ├── notification.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── logout.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ ├── subscriptions │ │ │ │ └── messageAdded.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-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 ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── test │ └── app.test.js ├── uploads │ ├── avatar1.png │ └── avatar2.png ├── webpack.client.build.config.js ├── webpack.client.config.js └── webpack.server.config.js ├── Chapter13 ├── .babelrc ├── .circleci │ └── config.yml ├── .dockerignore ├── .env ├── .eslintrc ├── Dockerfile ├── assets │ └── css │ │ └── style.css ├── babel-hook.js ├── combined.log ├── error.log ├── package-lock.json ├── package.json ├── public │ └── index.html ├── src │ ├── client │ │ ├── App.js │ │ ├── Chats.js │ │ ├── Feed.js │ │ ├── Main.js │ │ ├── User.js │ │ ├── apollo │ │ │ └── index.js │ │ ├── components │ │ │ ├── avatarModal.js │ │ │ ├── bar │ │ │ │ ├── home.js │ │ │ │ ├── index.js │ │ │ │ ├── logout.js │ │ │ │ ├── search.js │ │ │ │ ├── searchList.js │ │ │ │ └── user.js │ │ │ ├── chat │ │ │ │ ├── input.js │ │ │ │ ├── list.js │ │ │ │ ├── notification.js │ │ │ │ └── window.js │ │ │ ├── context │ │ │ │ └── user.js │ │ │ ├── error.js │ │ │ ├── fontawesome.js │ │ │ ├── helpers │ │ │ │ └── dropdown.js │ │ │ ├── loading.js │ │ │ ├── loginregister.js │ │ │ ├── mutations │ │ │ │ ├── addMessage.js │ │ │ │ ├── addPost.js │ │ │ │ ├── deletePost.js │ │ │ │ ├── login.js │ │ │ │ ├── logout.js │ │ │ │ ├── signup.js │ │ │ │ ├── updatePost.js │ │ │ │ └── uploadAvatar.js │ │ │ ├── post │ │ │ │ ├── content.js │ │ │ │ ├── feedlist.js │ │ │ │ ├── form.js │ │ │ │ ├── header.js │ │ │ │ ├── index.js │ │ │ │ └── index.md │ │ │ ├── queries │ │ │ │ ├── chatQuery.js │ │ │ │ ├── chatsQuery.js │ │ │ │ ├── currentUser.js │ │ │ │ ├── postsFeed.js │ │ │ │ ├── searchQuery.js │ │ │ │ └── userQuery.js │ │ │ ├── subscriptions │ │ │ │ └── messageAdded.js │ │ │ └── user │ │ │ │ ├── header.js │ │ │ │ └── index.js │ │ ├── index.js │ │ └── router.js │ └── server │ │ ├── config │ │ └── index.js │ │ ├── database │ │ └── index.js │ │ ├── helpers │ │ └── logger.js │ │ ├── index.js │ │ ├── migrations │ │ ├── 20181220200051-create-post.js │ │ ├── 20181220205457-create-user.js │ │ ├── 20181220205518-add-userId-to-post.js │ │ ├── 20181220210921-create-chat.js │ │ ├── 20181220211017-create-user-chats.js │ │ ├── 20181220211552-create-message.js │ │ └── 20190109223910-add-email-password-to-post.js │ │ ├── models │ │ ├── chat.js │ │ ├── index.js │ │ ├── message.js │ │ ├── post.js │ │ └── user.js │ │ ├── seeders │ │ ├── 20181220205914-fake-users.js │ │ ├── 20181220206627-fake-posts.js │ │ ├── 20181220212733-fake-chats.js │ │ ├── 20181220213102-fake-chats-users-relations.js │ │ └── 20181220213133-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 ├── stats.json ├── styleguide.config.js ├── styleguide │ ├── build │ │ ├── 0.dd0ee2ca.js │ │ └── bundle.cf4144eb.js │ └── index.html ├── 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 /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *package-lock.json -------------------------------------------------------------------------------- /Chapter01/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter01/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter01/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /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/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter01/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter01/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter01/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter02/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter02/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter02/combined.log: -------------------------------------------------------------------------------- 1 | {"level":"info","message":"Post was created"} 2 | -------------------------------------------------------------------------------- /Chapter02/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/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; -------------------------------------------------------------------------------- /Chapter02/src/server/services/graphql/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import {makeExecutableSchema} from 'graphql-tools'; 3 | import Resolvers from './resolvers'; 4 | import Schema from './schema'; 5 | 6 | const executableSchema = makeExecutableSchema({ 7 | typeDefs: Schema, 8 | resolvers: Resolvers, 9 | }); 10 | 11 | const server = new ApolloServer({ 12 | schema: executableSchema, 13 | context: ({ req }) => req, 14 | }); 15 | 16 | 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 | input PostInput { 14 | text: String! 15 | } 16 | 17 | input UserInput { 18 | username: String! 19 | avatar: String! 20 | } 21 | 22 | type RootMutation { 23 | addPost ( 24 | post: PostInput! 25 | user: UserInput! 26 | ): Post 27 | } 28 | 29 | type RootQuery { 30 | posts: [Post] 31 | } 32 | 33 | schema { 34 | query: RootQuery 35 | mutation: RootMutation 36 | } 37 | `; 38 | 39 | export default [typeDefinitions]; -------------------------------------------------------------------------------- /Chapter02/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default { 4 | graphql 5 | }; -------------------------------------------------------------------------------- /Chapter02/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter02/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter02/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter02/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter03/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter03/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter03/combined.log: -------------------------------------------------------------------------------- 1 | {"level":"info","message":"Post was created"} 2 | {"level":"info","message":"Post was created"} 3 | {"level":"info","message":"Message was created"} 4 | {"level":"info","message":"Message was created"} 5 | {"level":"info","message":"Message was created"} 6 | {"level":"info","message":"Message was created"} 7 | {"level":"info","message":"Message was created"} 8 | -------------------------------------------------------------------------------- /Chapter03/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/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": "Test1234%", 5 | "database": "graphbook_dev", 6 | "host": "192.168.2.107", 7 | "dialect": "mysql", 8 | "operatorsAliases": false, 9 | "pool": { 10 | "max": 5, 11 | "min": 0, 12 | "acquire": 30000, 13 | "idle": 10000 14 | } 15 | }, 16 | "production": { 17 | "host": process.env.host, 18 | "username": process.env.username, 19 | "password": process.env.password, 20 | "database": process.env.database, 21 | "logging": false, 22 | "dialect": "mysql", 23 | "operatorsAliases": false, 24 | "pool": { 25 | "max": 5, 26 | "min": 0, 27 | "acquire": 30000, 28 | "idle": 10000 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter03/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/migrations/20181220205457-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | avatar: { 12 | type: Sequelize.STRING 13 | }, 14 | username: { 15 | type: Sequelize.STRING 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: (queryInterface, Sequelize) => { 28 | return queryInterface.dropTable('Users'); 29 | } 30 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING 6 | }, {}); 7 | User.associate = function(models) { 8 | User.hasMany(models.Post); 9 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 10 | }; 11 | return User; 12 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/seeders/20181220205914-fake-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (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: (queryInterface, Sequelize) => { 20 | return queryInterface.bulkDelete('Users', null, {}); 21 | } 22 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/services/graphql/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 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 | 12 | const server = new ApolloServer({ 13 | schema: executableSchema, 14 | context: ({ req }) => req, 15 | }); 16 | 17 | return server; 18 | }; -------------------------------------------------------------------------------- /Chapter03/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter03/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter03/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter03/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter03/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter04/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter04/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter04/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/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, { Component } 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 | export default class App extends Component { 8 | render() { 9 | return ( 10 |
11 | 12 | Graphbook - Feed 13 | 14 | 15 | 16 | 17 |
18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /Chapter04/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /Chapter04/src/server/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "development": { 3 | "username": "devuser", 4 | "password": "Test1234%", 5 | "database": "graphbook_dev", 6 | "host": "192.168.2.107", 7 | "dialect": "mysql", 8 | "operatorsAliases": false, 9 | "pool": { 10 | "max": 5, 11 | "min": 0, 12 | "acquire": 30000, 13 | "idle": 10000 14 | } 15 | }, 16 | "production": { 17 | "host": process.env.host, 18 | "username": process.env.username, 19 | "password": process.env.password, 20 | "database": process.env.database, 21 | "logging": false, 22 | "dialect": "mysql", 23 | "operatorsAliases": false, 24 | "pool": { 25 | "max": 5, 26 | "min": 0, 27 | "acquire": 30000, 28 | "idle": 10000 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter04/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/migrations/20181220205457-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | avatar: { 12 | type: Sequelize.STRING 13 | }, 14 | username: { 15 | type: Sequelize.STRING 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: (queryInterface, Sequelize) => { 28 | return queryInterface.dropTable('Users'); 29 | } 30 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING 6 | }, {}); 7 | User.associate = function(models) { 8 | User.hasMany(models.Post); 9 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 10 | }; 11 | return User; 12 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/seeders/20181220205914-fake-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (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: (queryInterface, Sequelize) => { 20 | return queryInterface.bulkDelete('Users', null, {}); 21 | } 22 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/services/graphql/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 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 | 12 | const server = new ApolloServer({ 13 | schema: executableSchema, 14 | context: ({ req }) => req, 15 | }); 16 | 17 | return server; 18 | }; -------------------------------------------------------------------------------- /Chapter04/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter04/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter04/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter04/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter04/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter05/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter05/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter05/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/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, { Component } 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 | import './components/fontawesome'; 7 | import Bar from './components/bar'; 8 | 9 | export default class App extends Component { 10 | render() { 11 | return ( 12 |
13 | 14 | Graphbook - Feed 15 | 16 | 17 | 18 | 19 | 20 |
21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter05/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter05/src/client/components/bar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import SearchBar from './search'; 3 | import UserBar from './user'; 4 | import { UserConsumer } from '../context/user'; 5 | 6 | export default class Bar extends Component { 7 | render() { 8 | return ( 9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter05/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter05/src/client/components/bar/user.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class UserBar extends Component { 4 | render() { 5 | const { user } = this.props; 6 | if(!user) return null; 7 | return ( 8 |
9 | 10 | {user.username} 11 |
12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter05/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter05/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter05/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter05/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING 6 | }, {}); 7 | User.associate = function(models) { 8 | User.hasMany(models.Post); 9 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 10 | }; 11 | return User; 12 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/seeders/20181220205914-fake-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (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: (queryInterface, Sequelize) => { 20 | return queryInterface.bulkDelete('Users', null, {}); 21 | } 22 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/services/graphql/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 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 | 12 | const server = new ApolloServer({ 13 | schema: executableSchema, 14 | context: ({ req }) => req, 15 | }); 16 | 17 | return server; 18 | }; -------------------------------------------------------------------------------- /Chapter05/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter05/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter05/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter05/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter05/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter05/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter05/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter06/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter06/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter06/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter06/error.log -------------------------------------------------------------------------------- /Chapter06/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter06/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter06/src/client/components/bar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } 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 | export default class Bar extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter06/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | localStorage.removeItem('jwt'); 7 | this.props.client.resetStore(); 8 | } 9 | render() { 10 | return ( 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter06/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter06/src/client/components/bar/user.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class UserBar extends Component { 4 | render() { 5 | const { user } = this.props; 6 | if(!user) return null; 7 | return ( 8 |
9 | 10 | {user.username} 11 |
12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter06/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter06/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter06/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter06/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter06/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /Chapter06/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter06/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter06/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter06/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter06/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter06/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter06/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter07/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter07/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter07/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter07/error.log -------------------------------------------------------------------------------- /Chapter07/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter07/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter07/src/client/components/bar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } 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 | export default class Bar extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter07/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | localStorage.removeItem('jwt'); 7 | this.props.client.resetStore(); 8 | } 9 | render() { 10 | return ( 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter07/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter07/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter07/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter07/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter07/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter07/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /Chapter07/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter07/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter07/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter07/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter07/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter07/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter07/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter08/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter08/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter08/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter08/error.log -------------------------------------------------------------------------------- /Chapter08/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter08/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter08/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter08/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter08/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter08/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | localStorage.removeItem('jwt'); 7 | this.props.client.resetStore(); 8 | } 9 | render() { 10 | return ( 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter08/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter08/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter08/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter08/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter08/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter08/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /Chapter08/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter08/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter08/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter08/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter08/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter08/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter08/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter09/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter09/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter09/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter09/error.log -------------------------------------------------------------------------------- /Chapter09/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter09/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter09/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter09/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter09/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter09/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | this.props.logout().then(() => { 7 | localStorage.removeItem('jwt'); 8 | this.props.client.resetStore(); 9 | }); 10 | } 11 | render() { 12 | return ( 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter09/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter09/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter09/src/client/components/mutations/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from "react-apollo"; 3 | import gql from "graphql-tag"; 4 | 5 | const LOGOUT = gql` 6 | mutation logout { 7 | logout { 8 | success 9 | } 10 | } 11 | `; 12 | 13 | export default class LogoutMutation extends Component { 14 | render() { 15 | const { children } = this.props; 16 | return ( 17 | 19 | {(logout, { loading, error}) => 20 | React.Children.map(children, function(child){ 21 | return React.cloneElement(child, { logout, loading, error }); 22 | }) 23 | } 24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /Chapter09/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter09/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.hydrate( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter09/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter09/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /Chapter09/src/server/services/index.js: -------------------------------------------------------------------------------- 1 | import graphql from './graphql'; 2 | 3 | export default utils => ({ 4 | graphql: graphql(utils), 5 | }); -------------------------------------------------------------------------------- /Chapter09/src/server/ssr/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloProvider } from 'react-apollo'; 3 | import App from './app'; 4 | 5 | export default class ServerClient extends React.Component { 6 | render() { 7 | const { client, location, context, loggedIn } = this.props; 8 | return( 9 | 10 | 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter09/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter09/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter09/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter09/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter09/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter09/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter10/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter10/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter10/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter10/error.log -------------------------------------------------------------------------------- /Chapter10/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter10/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter10/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter10/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter10/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter10/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | this.props.logout().then(() => { 7 | localStorage.removeItem('jwt'); 8 | this.props.client.resetStore(); 9 | }); 10 | } 11 | render() { 12 | return ( 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter10/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter10/src/client/components/chat/notification.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { toast } from 'react-toastify'; 3 | 4 | export default class ChatNotification extends Component { 5 | componentWillReceiveProps(props) { 6 | if(typeof props.data !== typeof undefined && typeof props.data.messageAdded !== typeof undefined) 7 | toast(props.data.messageAdded.text, { position: toast.POSITION.TOP_LEFT }); 8 | } 9 | render() { 10 | return (null); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter10/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter10/src/client/components/mutations/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from "react-apollo"; 3 | import gql from "graphql-tag"; 4 | 5 | const LOGOUT = gql` 6 | mutation logout { 7 | logout { 8 | success 9 | } 10 | } 11 | `; 12 | 13 | export default class LogoutMutation extends Component { 14 | render() { 15 | const { children } = this.props; 16 | return ( 17 | 19 | {(logout, { loading, error}) => 20 | React.Children.map(children, function(child){ 21 | return React.cloneElement(child, { logout, loading, error }); 22 | }) 23 | } 24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /Chapter10/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter10/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.hydrate( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter10/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter10/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /Chapter10/src/server/ssr/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloProvider } from 'react-apollo'; 3 | import App from './app'; 4 | 5 | export default class ServerClient extends React.Component { 6 | render() { 7 | const { client, location, context, loggedIn } = this.props; 8 | return( 9 | 10 | 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter10/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter10/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter10/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter10/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter10/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter10/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter11/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter11/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter11/babel-hook.js: -------------------------------------------------------------------------------- 1 | require("@babel/register")({ 2 | "plugins": [ 3 | "require-context-hook" 4 | ], 5 | "presets": ["@babel/env","@babel/react"] 6 | }); -------------------------------------------------------------------------------- /Chapter11/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter11/error.log -------------------------------------------------------------------------------- /Chapter11/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter11/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter11/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter11/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter11/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter11/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | this.props.logout().then(() => { 7 | localStorage.removeItem('jwt'); 8 | this.props.client.resetStore(); 9 | }); 10 | } 11 | render() { 12 | return ( 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter11/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter11/src/client/components/chat/notification.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { toast } from 'react-toastify'; 3 | 4 | export default class ChatNotification extends Component { 5 | componentWillReceiveProps(props) { 6 | if(typeof props.data !== typeof undefined && typeof props.data.messageAdded !== typeof undefined && props.data && props.data.messageAdded) 7 | toast(props.data.messageAdded.text, { position: toast.POSITION.TOP_LEFT }); 8 | } 9 | render() { 10 | return (null); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter11/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter11/src/client/components/mutations/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from "react-apollo"; 3 | import gql from "graphql-tag"; 4 | 5 | const LOGOUT = gql` 6 | mutation logout { 7 | logout { 8 | success 9 | } 10 | } 11 | `; 12 | 13 | export default class LogoutMutation extends Component { 14 | render() { 15 | const { children } = this.props; 16 | return ( 17 | 19 | {(logout, { loading, error}) => 20 | React.Children.map(children, function(child){ 21 | return React.cloneElement(child, { logout, loading, error }); 22 | }) 23 | } 24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /Chapter11/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter11/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.hydrate( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter11/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter11/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /Chapter11/src/server/ssr/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloProvider } from 'react-apollo'; 3 | import App from './app'; 4 | 5 | export default class ServerClient extends React.Component { 6 | render() { 7 | const { client, location, context, loggedIn } = this.props; 8 | return( 9 | 10 | 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter11/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter11/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter11/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter11/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter11/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter11/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter12/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }] 9 | ], 10 | "presets": ["@babel/env","@babel/react"] 11 | } -------------------------------------------------------------------------------- /Chapter12/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter12/babel-hook.js: -------------------------------------------------------------------------------- 1 | require("@babel/register")({ 2 | "plugins": [ 3 | "require-context-hook" 4 | ], 5 | "presets": ["@babel/env","@babel/react"] 6 | }); -------------------------------------------------------------------------------- /Chapter12/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter12/error.log -------------------------------------------------------------------------------- /Chapter12/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter12/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter12/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter12/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter12/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter12/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | this.props.logout().then(() => { 7 | localStorage.removeItem('jwt'); 8 | this.props.client.resetStore(); 9 | }); 10 | } 11 | render() { 12 | return ( 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter12/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter12/src/client/components/chat/notification.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { toast } from 'react-toastify'; 3 | 4 | export default class ChatNotification extends Component { 5 | componentWillReceiveProps(props) { 6 | if(typeof props.data !== typeof undefined && typeof props.data.messageAdded !== typeof undefined && props.data && props.data.messageAdded) 7 | toast(props.data.messageAdded.text, { position: toast.POSITION.TOP_LEFT }); 8 | } 9 | render() { 10 | return (null); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter12/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Chapter12/src/client/components/mutations/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from "react-apollo"; 3 | import gql from "graphql-tag"; 4 | 5 | const LOGOUT = gql` 6 | mutation logout { 7 | logout { 8 | success 9 | } 10 | } 11 | `; 12 | 13 | export default class LogoutMutation extends Component { 14 | render() { 15 | const { children } = this.props; 16 | return ( 17 | 19 | {(logout, { loading, error}) => 20 | React.Children.map(children, function(child){ 21 | return React.cloneElement(child, { logout, loading, error }); 22 | }) 23 | } 24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /Chapter12/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /Chapter12/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.hydrate( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /Chapter12/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter12/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /Chapter12/src/server/ssr/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloProvider } from 'react-apollo'; 3 | import App from './app'; 4 | 5 | export default class ServerClient extends React.Component { 6 | render() { 7 | const { client, location, context, loggedIn } = this.props; 8 | return( 9 | 10 | 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter12/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter12/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter12/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter12/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter12/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter12/uploads/avatar2.png -------------------------------------------------------------------------------- /Chapter13/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 4 | "@babel/plugin-proposal-function-sent", 5 | "@babel/plugin-proposal-export-namespace-from", 6 | "@babel/plugin-proposal-numeric-separator", 7 | "@babel/plugin-proposal-throw-expressions", 8 | ["@babel/plugin-proposal-class-properties", { "loose": false }], 9 | "@babel/plugin-syntax-dynamic-import", 10 | "react-loadable/babel" 11 | ], 12 | "presets": ["@babel/env","@babel/react"] 13 | } -------------------------------------------------------------------------------- /Chapter13/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /Chapter13/.env: -------------------------------------------------------------------------------- 1 | ENGINE_KEY=YOUR_APLLO_ENGINE_API_KEY 2 | NODE_ENV=development 3 | JWT_SECRET=YOUR_JWT_SECRET 4 | AWS_ACCESS_KEY_ID=YOUR_AWS_KEY_ID 5 | AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY 6 | username=devuser 7 | password=Test1234% 8 | database=graphbook_dev 9 | host=192.168.2.101 -------------------------------------------------------------------------------- /Chapter13/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb"], 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "react/jsx-filename-extension": "off" 9 | } 10 | } -------------------------------------------------------------------------------- /Chapter13/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 AS build 2 | WORKDIR /usr/src/app 3 | COPY .babelrc ./ 4 | COPY package*.json ./ 5 | COPY webpack.server.build.config.js ./ 6 | COPY webpack.client.build.config.js ./ 7 | COPY src src 8 | COPY assets assets 9 | RUN npm install 10 | RUN npm run build 11 | FROM node:10 12 | WORKDIR /usr/src/app 13 | COPY --from=build /usr/src/app/package.json package.json 14 | COPY --from=build /usr/src/app/dist dist 15 | RUN npm install --only=production 16 | EXPOSE 8000 17 | CMD [ "npm", "run", "server:production" ] -------------------------------------------------------------------------------- /Chapter13/babel-hook.js: -------------------------------------------------------------------------------- 1 | require("@babel/register")({ 2 | "plugins": [ 3 | "require-context-hook", "react-loadable/babel", "dynamic-import-node" 4 | ], 5 | "presets": ["@babel/env","@babel/react"] 6 | }); -------------------------------------------------------------------------------- /Chapter13/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter13/error.log -------------------------------------------------------------------------------- /Chapter13/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Graphbook 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /Chapter13/src/client/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PostsQuery from './components/queries/postsFeed'; 3 | import AddPostMutation from './components/mutations/addPost'; 4 | import FeedList from './components/post/feedlist'; 5 | import PostForm from './components/post/form'; 6 | 7 | export default class Feed extends Component { 8 | render() { 9 | const query_variables = { page: 0, limit: 10}; 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /Chapter13/src/client/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Feed from './Feed'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter13/src/client/User.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UserProfile from './components/user'; 3 | import Chats from './Chats'; 4 | import Bar from './components/bar'; 5 | import CurrentUserQuery from './components/queries/currentUser'; 6 | import { UserConsumer } from './components/context/user'; 7 | 8 | export default class User extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /Chapter13/src/client/components/bar/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class Home extends Component { 5 | goHome = () => { 6 | this.props.history.push('/app'); 7 | } 8 | render() { 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default withRouter(Home); -------------------------------------------------------------------------------- /Chapter13/src/client/components/bar/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from "react-apollo"; 3 | 4 | class Logout extends Component { 5 | logout = () => { 6 | this.props.logout().then(() => { 7 | localStorage.removeItem('jwt'); 8 | this.props.client.resetStore(); 9 | }); 10 | } 11 | render() { 12 | return ( 13 | 14 | ); 15 | } 16 | } 17 | 18 | export default withApollo(Logout); -------------------------------------------------------------------------------- /Chapter13/src/client/components/bar/search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import UsersSearchQuery from '../queries/searchQuery'; 3 | import SearchList from './searchList'; 4 | 5 | export default class SearchBar extends Component { 6 | state = { 7 | text: '' 8 | } 9 | changeText = (event) => { 10 | this.setState({text: event.target.value}); 11 | } 12 | render() { 13 | const { text } = this.state; 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /Chapter13/src/client/components/chat/notification.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { toast } from 'react-toastify'; 3 | 4 | export default class ChatNotification extends Component { 5 | componentWillReceiveProps(props) { 6 | if(typeof props.data !== typeof undefined && typeof props.data.messageAdded !== typeof undefined && props.data && props.data.messageAdded) 7 | toast(props.data.messageAdded.text, { position: toast.POSITION.TOP_LEFT }); 8 | } 9 | render() { 10 | return (null); 11 | } 12 | } -------------------------------------------------------------------------------- /Chapter13/src/client/components/error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Error extends Component { 4 | render() { 5 | const { children } = this.props; 6 | 7 | return ( 8 |
9 | {children} 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter13/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); -------------------------------------------------------------------------------- /Chapter13/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 | } -------------------------------------------------------------------------------- /Chapter13/src/client/components/mutations/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Mutation } from "react-apollo"; 3 | import gql from "graphql-tag"; 4 | 5 | const LOGOUT = gql` 6 | mutation logout { 7 | logout { 8 | success 9 | } 10 | } 11 | `; 12 | 13 | export default class LogoutMutation extends Component { 14 | render() { 15 | const { children } = this.props; 16 | return ( 17 | 19 | {(logout, { loading, error}) => 20 | React.Children.map(children, function(child){ 21 | return React.cloneElement(child, { logout, loading, error }); 22 | }) 23 | } 24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /Chapter13/src/client/components/post/content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({post}) => 4 |

5 | {post.text} 6 |

-------------------------------------------------------------------------------- /Chapter13/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 | ``` -------------------------------------------------------------------------------- /Chapter13/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { ApolloProvider } from 'react-apollo'; 5 | import client from './apollo'; 6 | 7 | ReactDOM.hydrate( 8 | 9 | 10 | 11 | , document.getElementById('root')); -------------------------------------------------------------------------------- /Chapter13/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, config.password, config); 9 | 10 | const db = { 11 | models: models(sequelize), 12 | sequelize, 13 | }; 14 | 15 | export default db; -------------------------------------------------------------------------------- /Chapter13/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; -------------------------------------------------------------------------------- /Chapter13/src/server/migrations/20181220200051-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return 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: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Posts'); 26 | } 27 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/migrations/20181220205518-add-userId-to-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return Promise.all([ 6 | queryInterface.addColumn('Posts', 7 | 'userId', 8 | { 9 | type: Sequelize.INTEGER, 10 | }), 11 | queryInterface.addConstraint('Posts', ['userId'], { 12 | type: 'foreign key', 13 | name: 'fk_user_id', 14 | references: { 15 | table: 'Users', 16 | field: 'id', 17 | }, 18 | onDelete: 'cascade', 19 | onUpdate: 'cascade', 20 | }), 21 | ]); 22 | }, 23 | 24 | down: (queryInterface, Sequelize) => { 25 | return Promise.all([ 26 | queryInterface.removeColumn('Posts', 'userId'), 27 | ]); 28 | } 29 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/migrations/20181220210921-create-chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Chats', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | createdAt: { 12 | allowNull: false, 13 | type: Sequelize.DATE 14 | }, 15 | updatedAt: { 16 | allowNull: false, 17 | type: Sequelize.DATE 18 | } 19 | }); 20 | }, 21 | down: (queryInterface, Sequelize) => { 22 | return queryInterface.dropTable('Chats'); 23 | } 24 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/migrations/20190109223910-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 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/models/chat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Chat = sequelize.define('Chat', {}, {}); 4 | Chat.associate = function(models) { 5 | Chat.belongsToMany(models.User, { through: 'users_chats' }); 6 | Chat.hasMany(models.Message); 7 | }; 8 | return Chat; 9 | }; -------------------------------------------------------------------------------- /Chapter13/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 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Message = sequelize.define('Message', { 4 | text: DataTypes.STRING, 5 | userId: DataTypes.INTEGER, 6 | chatId: DataTypes.INTEGER 7 | }, {}); 8 | Message.associate = function(models) { 9 | Message.belongsTo(models.User); 10 | Message.belongsTo(models.Chat); 11 | }; 12 | return Message; 13 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var Post = sequelize.define('Post', { 4 | text: DataTypes.TEXT, 5 | userId: DataTypes.INTEGER, 6 | }, {}); 7 | Post.associate = function(models) { 8 | Post.belongsTo(models.User); 9 | }; 10 | return Post; 11 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | avatar: DataTypes.STRING, 5 | username: DataTypes.STRING, 6 | email: DataTypes.STRING, 7 | password: DataTypes.STRING, 8 | }, {}); 9 | User.associate = function(models) { 10 | User.hasMany(models.Post); 11 | User.belongsToMany(models.Chat, { through: 'users_chats' }); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/seeders/20181220212733-fake-chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('Chats', [{ 6 | createdAt: new Date(), 7 | updatedAt: new Date(), 8 | }], 9 | {}); 10 | }, 11 | down: (queryInterface, Sequelize) => { 12 | return queryInterface.bulkDelete('Chats', null, {}); 13 | } 14 | }; -------------------------------------------------------------------------------- /Chapter13/src/server/services/graphql/auth.js: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor, AuthenticationError } from 'apollo-server-express'; 2 | 3 | class AuthDirective extends SchemaDirectiveVisitor { 4 | visitFieldDefinition(field) { 5 | const { resolve = defaultFieldResolver } = field; 6 | field.resolve = async function(...args) { 7 | const ctx = args[2]; 8 | if (ctx.user) { 9 | return await resolve.apply(this, args); 10 | } else { 11 | throw new AuthenticationError("You need to be logged in."); 12 | } 13 | }; 14 | } 15 | } 16 | 17 | export default AuthDirective; -------------------------------------------------------------------------------- /Chapter13/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 | }); -------------------------------------------------------------------------------- /Chapter13/src/server/ssr/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApolloProvider } from 'react-apollo'; 3 | import App from './app'; 4 | 5 | export default class ServerClient extends React.Component { 6 | render() { 7 | const { client, location, context, loggedIn } = this.props; 8 | return( 9 | 10 | 11 | 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter13/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | components: 'src/client/components/**/*.js', 5 | require: [ 6 | path.join(__dirname, 'assets/css/style.css') 7 | ], 8 | webpackConfig: require('./webpack.client.config') 9 | } -------------------------------------------------------------------------------- /Chapter13/styleguide/index.html: -------------------------------------------------------------------------------- 1 | Graphbook Style Guide
-------------------------------------------------------------------------------- /Chapter13/uploads/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter13/uploads/avatar1.png -------------------------------------------------------------------------------- /Chapter13/uploads/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-on-Full-Stack-Web-Development-with-GraphQL-and-React/e1fd2e83229c44f0f2c132bb822c930809b84489/Chapter13/uploads/avatar2.png --------------------------------------------------------------------------------