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