├── .DS_Store
├── README.md
├── final
├── .DS_Store
├── client
│ ├── .env
│ ├── .gitignore
│ ├── Procfile
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── server.js
│ └── src
│ │ ├── App.js
│ │ ├── components
│ │ ├── FileUpload.js
│ │ ├── Image.js
│ │ ├── LoadingToRedirect.js
│ │ ├── Nav.js
│ │ ├── PostCard.js
│ │ ├── PostPagination.js
│ │ ├── PrivateRoute.js
│ │ ├── PublicRoute.js
│ │ ├── Search.js
│ │ ├── SearchResult.js
│ │ ├── UserCard.js
│ │ └── forms
│ │ │ ├── AuthForm.js
│ │ │ └── UserProfile.js
│ │ ├── context
│ │ └── authContext.js
│ │ ├── firebase.js
│ │ ├── graphql
│ │ ├── fragments.js
│ │ ├── mutations.js
│ │ ├── queries.js
│ │ └── subscriptions.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── pages
│ │ ├── Home.js
│ │ ├── SingleUser.js
│ │ ├── Users.js
│ │ ├── auth
│ │ ├── CompleteRegistration.js
│ │ ├── Login.js
│ │ ├── PasswordForgot.js
│ │ ├── PasswordUpdate.js
│ │ ├── Profile.js
│ │ └── Register.js
│ │ └── post
│ │ ├── Post.js
│ │ ├── PostUpdate.js
│ │ └── SinglePost.js
└── server
│ ├── .DS_Store
│ ├── .gitignore
│ ├── Procfile
│ ├── config
│ └── fbServiceAccountKey.json
│ ├── helpers
│ └── auth.js
│ ├── models
│ ├── post.js
│ └── user.js
│ ├── package-lock.json
│ ├── package.json
│ ├── resolvers
│ ├── auth.js
│ └── post.js
│ ├── server.js
│ ├── temp.js
│ └── typeDefs
│ ├── auth.js
│ └── post.js
└── source_code_lecture_5-131.zip
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## GraphQL React Node MongoDB Firebase(Auth Only) Realtime MERN Stack Web App
2 |
3 | ## Source code for Udemy course
4 |
5 | [GraphQL from Scratch with React Node MongoDB Firebase MERN ](https://www.udemy.com/course/graphql-react-node/?couponCode=GRAPHQL)
6 |
--------------------------------------------------------------------------------
/final/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/final/.DS_Store
--------------------------------------------------------------------------------
/final/client/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_GRAPHQL_ENDPOINT=http://localhost:8000/graphql
2 | REACT_APP_GRAPHQL_WS_ENDPOINT=ws://localhost:8000/graphql
3 | REACT_APP_REST_ENDPOINT=http://localhost:8000
4 | REACT_APP_CONFIRMATION_EMAIL_REDIRECT=http://localhost:3000/complete-registration
5 | REACT_APP_PASSWORD_FORGOT_REDIRECT=http://localhost:3000/login
--------------------------------------------------------------------------------
/final/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | .env
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/final/client/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
--------------------------------------------------------------------------------
/final/client/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `yarn build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/final/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.2",
4 | "private": true,
5 | "dependencies": {
6 | "@apollo/client": "^3.0.0-beta.43",
7 | "@apollo/react-hooks": "^3.1.4",
8 | "@testing-library/jest-dom": "^4.2.4",
9 | "@testing-library/react": "^9.3.2",
10 | "@testing-library/user-event": "^7.1.2",
11 | "apollo-boost": "^0.4.7",
12 | "apollo-link": "^1.2.14",
13 | "apollo-link-context": "^1.0.20",
14 | "apollo-link-ws": "^1.0.20",
15 | "apollo-utilities": "^1.3.3",
16 | "axios": "^0.19.2",
17 | "firebase": "^7.14.0",
18 | "graphql": "^15.0.0",
19 | "omit-deep": "^0.3.0",
20 | "react": "^16.13.1",
21 | "react-dom": "^16.13.1",
22 | "react-image-file-resizer": "^0.2.3",
23 | "react-router-dom": "^5.1.2",
24 | "react-scripts": "3.4.1",
25 | "react-snap": "^1.23.0",
26 | "react-toastify": "^5.5.0",
27 | "subscriptions-transport-ws": "^0.9.16"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject",
34 | "postbuild": "react-snap"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/final/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/final/client/public/favicon.ico
--------------------------------------------------------------------------------
/final/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 | React App
18 |
19 |
20 |
21 |
22 |
27 |
32 |
37 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/final/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/final/client/public/logo192.png
--------------------------------------------------------------------------------
/final/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/final/client/public/logo512.png
--------------------------------------------------------------------------------
/final/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/final/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/final/client/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const compression = require('compression');
3 | const path = require('path');
4 | const app = express();
5 |
6 | app.use(compression());
7 | app.use(express.static(path.join(__dirname, 'build')));
8 |
9 | app.get('*', function(req, res) {
10 | res.sendFile(path.join(__dirname, 'build', 'index.html'));
11 | });
12 |
13 | const PORT = process.env.PORT || 3000;
14 |
15 | app.listen(PORT, () => {
16 | console.log(`App is running on port ${PORT}`);
17 | });
18 |
--------------------------------------------------------------------------------
/final/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | // from apollo boost
4 | // import ApolloClient, { InMemoryCache, HttpLink } from 'apollo-boost';
5 | import ApolloClient from 'apollo-client';
6 | import { InMemoryCache } from 'apollo-cache-inmemory';
7 | import { HttpLink } from 'apollo-link-http';
8 | // gql
9 | import { gql } from 'apollo-boost';
10 | import { split } from 'apollo-link';
11 | import { setContext } from 'apollo-link-context';
12 | import { WebSocketLink } from 'apollo-link-ws';
13 | import { getMainDefinition } from 'apollo-utilities';
14 |
15 | import { ApolloProvider } from '@apollo/react-hooks';
16 | import { ToastContainer } from 'react-toastify';
17 | // import components
18 | import Nav from './components/Nav';
19 | import Home from './pages/Home';
20 | import Users from './pages/Users';
21 | import Register from './pages/auth/Register';
22 | import PasswordUpdate from './pages/auth/PasswordUpdate';
23 | import PasswordForgot from './pages/auth/PasswordForgot';
24 | import Profile from './pages/auth/Profile';
25 | import Login from './pages/auth/Login';
26 | import CompleteRegistration from './pages/auth/CompleteRegistration';
27 | import { AuthContext } from './context/authContext';
28 | import PrivateRoute from './components/PrivateRoute';
29 | import PublicRoute from './components/PublicRoute';
30 | import Post from './pages/post/Post';
31 | import PostUpdate from './pages/post/PostUpdate';
32 | import SinglePost from './pages/post/SinglePost';
33 | import SingleUser from './pages/SingleUser';
34 | import SearchResult from './components/SearchResult';
35 |
36 | const App = () => {
37 | const { state } = useContext(AuthContext);
38 | const { user } = state;
39 |
40 | // 1. create websocket link
41 | const wsLink = new WebSocketLink({
42 | uri: process.env.REACT_APP_GRAPHQL_WS_ENDPOINT,
43 | options: {
44 | reconnect: true
45 | }
46 | });
47 |
48 | // 2. create http link
49 | const httpLink = new HttpLink({
50 | uri: process.env.REACT_APP_GRAPHQL_ENDPOINT
51 | });
52 |
53 | // 3. setContext for authtoken
54 | const authLink = setContext(() => {
55 | return {
56 | headers: {
57 | authtoken: user ? user.token : ''
58 | }
59 | };
60 | });
61 |
62 | // 4. concat http and authtoken link
63 | const httpAuthLink = authLink.concat(httpLink);
64 |
65 | // 5. use split to split http link or websocket link
66 | const link = split(
67 | ({ query }) => {
68 | // split link based on operation type
69 | const definition = getMainDefinition(query);
70 | return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
71 | },
72 | wsLink,
73 | httpAuthLink
74 | );
75 |
76 | const client = new ApolloClient({
77 | cache: new InMemoryCache(),
78 | link
79 | });
80 |
81 | // const client = new ApolloClient({
82 | // uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
83 | // request: (operation) => {
84 | // operation.setContext({
85 | // headers: {
86 | // authtoken: user ? user.token : ''
87 | // }
88 | // });
89 | // }
90 | // });
91 |
92 | return (
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | export default App;
116 |
--------------------------------------------------------------------------------
/final/client/src/components/FileUpload.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, Fragment } from 'react';
2 | import Resizer from 'react-image-file-resizer';
3 | import axios from 'axios';
4 | import { AuthContext } from '../context/authContext';
5 | import Image from './Image';
6 |
7 | const FileUpload = ({ setValues, setLoading, values, loading, singleUpload = false }) => {
8 | const { state } = useContext(AuthContext);
9 |
10 | const fileResizeAndUpload = (event) => {
11 | setLoading(true);
12 | let fileInput = false;
13 | if (event.target.files[0]) {
14 | fileInput = true;
15 | }
16 | if (fileInput) {
17 | Resizer.imageFileResizer(
18 | event.target.files[0],
19 | 300,
20 | 300,
21 | 'JPEG',
22 | 100,
23 | 0,
24 | (uri) => {
25 | // console.log(uri);
26 | axios
27 | .post(
28 | `${process.env.REACT_APP_REST_ENDPOINT}/uploadimages`,
29 | { image: uri },
30 | {
31 | headers: {
32 | authtoken: state.user.token
33 | }
34 | }
35 | )
36 | .then((response) => {
37 | setLoading(false);
38 | console.log('CLOUDINARY UPLOAD', response);
39 | // setValues to parent component based on either it is
40 | // used for single/multiple upload
41 | if (singleUpload) {
42 | // single upload
43 | const { image } = values;
44 | setValues({ ...values, image: response.data });
45 | } else {
46 | const { images } = values;
47 | setValues({ ...values, images: [...images, response.data] });
48 | }
49 | })
50 | .catch((error) => {
51 | setLoading(false);
52 | console.log('CLOUDINARY UPLOAD FAILED', error);
53 | });
54 | },
55 | 'base64'
56 | );
57 | }
58 | };
59 |
60 | const handleImageRemove = (id) => {
61 | setLoading(true);
62 | axios
63 | .post(
64 | `${process.env.REACT_APP_REST_ENDPOINT}/removeimage`,
65 | {
66 | public_id: id
67 | },
68 | {
69 | headers: {
70 | authtoken: state.user.token
71 | }
72 | }
73 | )
74 | .then((response) => {
75 | setLoading(false);
76 | // setValues to parent component based on either it is
77 | // used for single/multiple upload
78 | if (singleUpload) {
79 | const { image } = values;
80 | setValues({
81 | ...values,
82 | image: {
83 | url: '',
84 | public_id: ''
85 | }
86 | });
87 | } else {
88 | const { images } = values;
89 | let filteredImages = images.filter((item) => {
90 | return item.public_id !== id;
91 | });
92 | setValues({ ...values, images: filteredImages });
93 | }
94 | })
95 | .catch((error) => {
96 | setLoading(false);
97 | console.log(error);
98 | });
99 | };
100 |
101 | return (
102 |
103 |
104 |
105 |
116 |
117 |
118 |
119 | {/*for single image*/}
120 | {values.image && (
121 |
122 | )}
123 |
124 | {/*for multiple image*/}
125 | {values.images &&
126 | values.images.map((image) => (
127 |
128 | ))}
129 |
130 |
131 | );
132 | };
133 |
134 | export default FileUpload;
135 |
--------------------------------------------------------------------------------
/final/client/src/components/Image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Image = ({ image, handleImageRemove = (f) => f }) => (
4 |
handleImageRemove(image.public_id)}
11 | />
12 | );
13 |
14 | export default Image;
15 |
--------------------------------------------------------------------------------
/final/client/src/components/LoadingToRedirect.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 |
4 | const LoadingToRedirect = ({ path }) => {
5 | const [count, setCount] = useState(5);
6 | let history = useHistory();
7 |
8 | useEffect(() => {
9 | const interval = setInterval(() => {
10 | setCount((currentCount) => --currentCount);
11 | }, 1000);
12 | // redirect once count is equal to 0
13 | count === 0 && history.push(path);
14 | // cleanup
15 | return () => clearInterval(interval);
16 | }, [count]);
17 |
18 | return (
19 |
20 |
Redirecting you in {count} seconds
21 |
22 | );
23 | };
24 |
25 | export default LoadingToRedirect;
26 |
--------------------------------------------------------------------------------
/final/client/src/components/Nav.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, Fragment } from 'react';
2 | import { Link, useHistory } from 'react-router-dom';
3 | import { auth } from 'firebase';
4 | import { AuthContext } from '../context/authContext';
5 | import Search from './Search';
6 |
7 | const Nav = () => {
8 | const { state, dispatch } = useContext(AuthContext);
9 | let history = useHistory();
10 |
11 | const { user } = state;
12 |
13 | const logout = () => {
14 | auth().signOut();
15 | dispatch({
16 | type: 'LOGGED_IN_USER',
17 | payload: null
18 | });
19 | history.push('/login');
20 | };
21 |
22 | return (
23 |
82 | );
83 | };
84 |
85 | export default Nav;
86 |
--------------------------------------------------------------------------------
/final/client/src/components/PostCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from './Image';
3 | import { Link, useHistory } from 'react-router-dom';
4 |
5 | const PostCard = ({ post, handleDelete = (f) => f, showUpdateButton = false, showDeleteButton = false }) => {
6 | const history = useHistory();
7 | const { image, content, postedBy } = post;
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
@{post.postedBy.username}
15 |
16 | {content}
17 |
18 |
19 | {showDeleteButton && (
20 |
23 | )}
24 | {showUpdateButton && (
25 |
28 | )}
29 |
30 |
31 | );
32 | };
33 |
34 | export default PostCard;
35 |
--------------------------------------------------------------------------------
/final/client/src/components/PostPagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PostPagination = ({ page, setPage, postCount }) => {
4 | let totalPages;
5 | const pagination = () => {
6 | totalPages = Math.ceil(postCount && postCount.totalPosts / 3);
7 | if (totalPages > 10) totalPages = 10;
8 | // console.log(totalPages);
9 | let pages = [];
10 | for (let i = 1; i <= totalPages; i++) {
11 | pages.push(
12 |
13 | setPage(i)}>
14 | {i}
15 |
16 |
17 | );
18 | }
19 | return pages;
20 | };
21 |
22 | return (
23 |
38 | );
39 | };
40 |
41 | export default PostPagination;
42 |
--------------------------------------------------------------------------------
/final/client/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { Route, Link } from 'react-router-dom';
3 | import { AuthContext } from '../context/authContext';
4 | import LoadingToRedirect from './LoadingToRedirect';
5 |
6 | const PrivateRoute = ({ ...rest }) => {
7 | const { state } = useContext(AuthContext);
8 | const [user, setUser] = useState(false);
9 |
10 | useEffect(() => {
11 | if (state.user) {
12 | setUser(true);
13 | }
14 | }, [state.user]);
15 |
16 | const navLinks = () => (
17 |
36 | );
37 |
38 | const renderContent = () => (
39 |
40 |
41 |
{navLinks()}
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
49 | return user ? renderContent() : ;
50 | };
51 |
52 | export default PrivateRoute;
53 |
--------------------------------------------------------------------------------
/final/client/src/components/PublicRoute.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from 'react';
2 | import { Route, useHistory } from 'react-router-dom';
3 | import { AuthContext } from '../context/authContext';
4 |
5 | const PublicRoute = ({ ...rest }) => {
6 | const { state } = useContext(AuthContext);
7 | let history = useHistory();
8 |
9 | useEffect(() => {
10 | if (state.user) {
11 | history.push('profile');
12 | }
13 | }, [state.user]);
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default PublicRoute;
23 |
--------------------------------------------------------------------------------
/final/client/src/components/Search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 |
4 | const Search = () => {
5 | const [query, setQuery] = useState('');
6 | const history = useHistory();
7 |
8 | const handleSubmit = (e) => {
9 | e.preventDefault();
10 | history.push(`/search/${query}`);
11 | };
12 |
13 | return (
14 |
27 | );
28 | };
29 |
30 | export default Search;
31 |
--------------------------------------------------------------------------------
/final/client/src/components/SearchResult.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import { useQuery } from '@apollo/react-hooks';
4 | import { SEARCH } from '../graphql/queries';
5 | import PostCard from '../components/PostCard';
6 |
7 | const SearchResult = () => {
8 | // route query
9 | const { query } = useParams();
10 | // gql query
11 | const { data, loading } = useQuery(SEARCH, {
12 | variables: { query }
13 | });
14 |
15 | if (loading)
16 | return (
17 |
20 | );
21 |
22 | if (!data.search.length)
23 | return (
24 |
27 | );
28 |
29 | return (
30 |
31 |
32 | {data.search.map((post) => (
33 |
36 | ))}
37 |
38 |
39 | );
40 | };
41 |
42 | export default SearchResult;
43 |
--------------------------------------------------------------------------------
/final/client/src/components/UserCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from './Image';
3 | import { Link } from 'react-router-dom';
4 |
5 | const UserCard = ({ user }) => {
6 | const { username, images, about } = user;
7 | return (
8 |
9 |
10 |
11 |
12 |
@{username}
13 |
14 |
15 | {about}
16 |
17 |
18 | );
19 | };
20 |
21 | export default UserCard;
22 |
--------------------------------------------------------------------------------
/final/client/src/components/forms/AuthForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const AuthForm = ({
4 | email = '',
5 | password = '',
6 | loading,
7 | setEmail = (f) => f,
8 | setPassword,
9 | handleSubmit,
10 | showPasswordInput = false,
11 | hideEmailInput = false
12 | }) => (
13 |
46 | );
47 |
48 | export default AuthForm;
49 |
--------------------------------------------------------------------------------
/final/client/src/components/forms/UserProfile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const UserProfile = ({ handleSubmit, handleChange, username, name, email, about, loading }) => (
4 |
60 | );
61 |
62 | export default UserProfile;
63 |
--------------------------------------------------------------------------------
/final/client/src/context/authContext.js:
--------------------------------------------------------------------------------
1 | import React, { useReducer, createContext, useEffect } from 'react';
2 | import { auth } from '../firebase';
3 |
4 | // reducer
5 | const firebaseReducer = (state, action) => {
6 | switch (action.type) {
7 | case 'LOGGED_IN_USER':
8 | return { ...state, user: action.payload };
9 | default:
10 | return state;
11 | }
12 | };
13 |
14 | // state
15 | const initialState = {
16 | user: null
17 | };
18 |
19 | // create context
20 | const AuthContext = createContext();
21 |
22 | // context provider
23 | const AuthProvider = ({ children }) => {
24 | const [state, dispatch] = useReducer(firebaseReducer, initialState);
25 |
26 | useEffect(() => {
27 | const unsubscribe = auth.onAuthStateChanged(async (user) => {
28 | if (user) {
29 | const idTokenResult = await user.getIdTokenResult();
30 |
31 | dispatch({
32 | type: 'LOGGED_IN_USER',
33 | payload: { email: user.email, token: idTokenResult.token }
34 | });
35 | } else {
36 | dispatch({
37 | type: 'LOGGED_IN_USER',
38 | payload: null
39 | });
40 | }
41 | });
42 | // cleanup
43 | return () => unsubscribe();
44 | }, []);
45 |
46 | const value = { state, dispatch };
47 | return {children};
48 | };
49 |
50 | // export
51 | export { AuthContext, AuthProvider };
52 |
--------------------------------------------------------------------------------
/final/client/src/firebase.js:
--------------------------------------------------------------------------------
1 | import * as firebase from 'firebase';
2 | // Your web app's Firebase configuration
3 | var firebaseConfig = {
4 | apiKey: 'AIzaSyA5EkxSPBgCm_K3hhHnZF1GyAFIXaSmYH4',
5 | authDomain: 'gqlreactnode99.firebaseapp.com',
6 | // databaseURL: 'https://gqlreactnode99.firebaseio.com',
7 | projectId: 'gqlreactnode99',
8 | storageBucket: 'gqlreactnode99.appspot.com',
9 | // messagingSenderId: '985266742688',
10 | appId: '1:985266742688:web:f6f59bf21efd2add3c70c5',
11 | measurementId: 'G-MTHN9B9VT9'
12 | };
13 | // Initialize Firebase
14 | firebase.initializeApp(firebaseConfig);
15 |
16 | export const auth = firebase.auth();
17 | export const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
18 |
--------------------------------------------------------------------------------
/final/client/src/graphql/fragments.js:
--------------------------------------------------------------------------------
1 | import { gql } from 'apollo-boost';
2 |
3 | export const USER_INFO = gql`
4 | fragment userInfo on User {
5 | _id
6 | name
7 | username
8 | email
9 | images {
10 | url
11 | public_id
12 | }
13 | about
14 | createdAt
15 | updatedAt
16 | }
17 | `;
18 |
19 | export const POST_DATA = gql`
20 | fragment postData on Post {
21 | _id
22 | content
23 | image {
24 | url
25 | public_id
26 | }
27 | postedBy {
28 | _id
29 | username
30 | }
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/final/client/src/graphql/mutations.js:
--------------------------------------------------------------------------------
1 | import { gql } from 'apollo-boost';
2 | import { USER_INFO, POST_DATA } from './fragments';
3 |
4 | export const USER_UPDATE = gql`
5 | mutation userUpdate($input: UserUpdateInput!) {
6 | userUpdate(input: $input) {
7 | ...userInfo
8 | }
9 | }
10 | ${USER_INFO}
11 | `;
12 |
13 | export const USER_CREATE = gql`
14 | mutation userCreate {
15 | userCreate {
16 | username
17 | email
18 | }
19 | }
20 | `;
21 |
22 | export const POST_CREATE = gql`
23 | mutation postCreate($input: PostCreateInput!) {
24 | postCreate(input: $input) {
25 | ...postData
26 | }
27 | }
28 | ${POST_DATA}
29 | `;
30 |
31 | export const POST_DELETE = gql`
32 | mutation postDelete($postId: String!) {
33 | postDelete(postId: $postId) {
34 | _id
35 | }
36 | }
37 | `;
38 |
39 | export const POST_UPDATE = gql`
40 | mutation postUpdate($input: PostUpdateInput!) {
41 | postUpdate(input: $input) {
42 | ...postData
43 | }
44 | }
45 | ${POST_DATA}
46 | `;
47 |
--------------------------------------------------------------------------------
/final/client/src/graphql/queries.js:
--------------------------------------------------------------------------------
1 | import { gql } from 'apollo-boost';
2 | import { USER_INFO, POST_DATA } from './fragments';
3 |
4 | export const PROFILE = gql`
5 | query {
6 | profile {
7 | ...userInfo
8 | }
9 | }
10 | ${USER_INFO}
11 | `;
12 |
13 | export const GET_ALL_POSTS = gql`
14 | query allPosts($page: Int!) {
15 | allPosts(page: $page) {
16 | ...postData
17 | }
18 | }
19 | ${POST_DATA}
20 | `;
21 |
22 | export const ALL_USERS = gql`
23 | query {
24 | allUsers {
25 | ...userInfo
26 | }
27 | }
28 | ${USER_INFO}
29 | `;
30 |
31 | export const POSTS_BY_USER = gql`
32 | query {
33 | postsByUser {
34 | ...postData
35 | }
36 | }
37 | ${POST_DATA}
38 | `;
39 |
40 | export const SINGLE_POST = gql`
41 | query singlePost($postId: String!) {
42 | singlePost(postId: $postId) {
43 | ...postData
44 | }
45 | }
46 | ${POST_DATA}
47 | `;
48 |
49 | export const TOTAL_POSTS = gql`
50 | query {
51 | totalPosts
52 | }
53 | `;
54 |
55 | export const SEARCH = gql`
56 | query search($query: String!) {
57 | search(query: $query) {
58 | ...postData
59 | }
60 | }
61 | ${POST_DATA}
62 | `;
63 |
--------------------------------------------------------------------------------
/final/client/src/graphql/subscriptions.js:
--------------------------------------------------------------------------------
1 | import { gql } from 'apollo-boost';
2 | import { POST_DATA } from './fragments';
3 |
4 | export const POST_ADDED = gql`
5 | subscription {
6 | postAdded {
7 | ...postData
8 | }
9 | }
10 | ${POST_DATA}
11 | `;
12 |
13 | export const POST_UPDATED = gql`
14 | subscription {
15 | postUpdated {
16 | ...postData
17 | }
18 | }
19 | ${POST_DATA}
20 | `;
21 |
22 | export const POST_DELETED = gql`
23 | subscription {
24 | postDeleted {
25 | ...postData
26 | }
27 | }
28 | ${POST_DATA}
29 | `;
30 |
--------------------------------------------------------------------------------
/final/client/src/index.css:
--------------------------------------------------------------------------------
1 | .activePagination {
2 | background-color: green;
3 | }
4 |
--------------------------------------------------------------------------------
/final/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { hydrate, render } from "react-dom";
3 | import { AuthProvider } from "./context/authContext";
4 | import { BrowserRouter } from "react-router-dom";
5 | import "./index.css";
6 | import "react-toastify/dist/ReactToastify.css";
7 | import App from "./App";
8 |
9 | const rootElement = document.getElementById("root");
10 | if (rootElement.hasChildNodes()) {
11 | hydrate(, rootElement);
12 | } else {
13 | render(, rootElement);
14 | }
15 |
16 | // render(
17 | //
18 | //
19 | //
20 | //
21 | // ,
22 | // document.getElementById('root')
23 | // );
24 |
--------------------------------------------------------------------------------
/final/client/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from 'react';
2 | import ApolloClient from 'apollo-boost';
3 | import { gql } from 'apollo-boost';
4 | import { useQuery, useLazyQuery, useSubscription } from '@apollo/react-hooks';
5 | import { AuthContext } from '../context/authContext';
6 | import { useHistory } from 'react-router-dom';
7 | import { GET_ALL_POSTS, TOTAL_POSTS } from '../graphql/queries';
8 | import { POST_ADDED, POST_UPDATED, POST_DELETED } from '../graphql/subscriptions';
9 | import PostCard from '../components/PostCard';
10 | import PostPagination from '../components/PostPagination';
11 | import { toast } from 'react-toastify';
12 |
13 | const Home = () => {
14 | const [page, setPage] = useState(1);
15 | const { data, loading, error } = useQuery(GET_ALL_POSTS, {
16 | variables: { page }
17 | });
18 | const { data: postCount } = useQuery(TOTAL_POSTS);
19 | // subscription > post added
20 | const { data: newPost } = useSubscription(POST_ADDED, {
21 | onSubscriptionData: async ({ client: { cache }, subscriptionData: { data } }) => {
22 | // console.log(data)
23 | // readQuery from cache
24 | const { allPosts } = cache.readQuery({
25 | query: GET_ALL_POSTS,
26 | variables: { page }
27 | });
28 | // console.log(allPosts)
29 |
30 | // write back to cache
31 | cache.writeQuery({
32 | query: GET_ALL_POSTS,
33 | variables: { page },
34 | data: {
35 | allPosts: [data.postAdded, ...allPosts]
36 | }
37 | });
38 | // refetch all posts to update ui
39 | fetchPosts({
40 | variables: { page },
41 | refetchQueries: [{ query: GET_ALL_POSTS, variables: { page } }]
42 | });
43 | // show toast notification
44 | toast.success('New post!');
45 | }
46 | });
47 | // post updated
48 | const { data: updatedPost } = useSubscription(POST_UPDATED, {
49 | onSubscriptionData: () => {
50 | toast.success('Post updated!');
51 | }
52 | });
53 | // post deleted
54 | const { data: deletedPost } = useSubscription(POST_DELETED, {
55 | onSubscriptionData: async ({ client: { cache }, subscriptionData: { data } }) => {
56 | // console.log(data)
57 | // readQuery from cache
58 | const { allPosts } = cache.readQuery({
59 | query: GET_ALL_POSTS,
60 | variables: { page }
61 | });
62 | // console.log(allPosts)
63 |
64 | let filteredPosts = allPosts.filter((p) => p._id !== data.postDeleted._id);
65 |
66 | // write back to cache
67 | cache.writeQuery({
68 | query: GET_ALL_POSTS,
69 | variables: { page },
70 | data: {
71 | allPosts: filteredPosts
72 | }
73 | });
74 | // refetch all posts to update ui
75 | fetchPosts({
76 | variables: { page },
77 | refetchQueries: [{ query: GET_ALL_POSTS, variables: { page } }]
78 | });
79 | // show toast notification
80 | toast.error('Post deleted!');
81 | }
82 | });
83 |
84 | const [fetchPosts, { data: posts }] = useLazyQuery(GET_ALL_POSTS);
85 | // access context
86 | const { state, dispatch } = useContext(AuthContext);
87 | // react router
88 | let history = useHistory();
89 |
90 | const updateUserName = () => {
91 | dispatch({
92 | type: 'LOGGED_IN_USER',
93 | payload: 'Ryan Dhungel'
94 | });
95 | };
96 |
97 | if (loading) return Loading...
;
98 |
99 | return (
100 |
101 |
102 | {data &&
103 | data.allPosts.map((post) => (
104 |
107 | ))}
108 |
109 |
110 |
111 | {/*
112 | {JSON.stringify(newPost)}
113 |
114 | {JSON.stringify(state.user)}
115 |
116 | {JSON.stringify(history)}*/}
117 |
118 | );
119 | };
120 |
121 | export default Home;
122 |
--------------------------------------------------------------------------------
/final/client/src/pages/SingleUser.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useQuery } from '@apollo/react-hooks';
3 | import { gql } from 'apollo-boost';
4 | import { useParams } from 'react-router-dom';
5 | import UserCard from '../components/UserCard';
6 |
7 | const PUBLIC_PROFILE = gql`
8 | query publicProfile($username: String!) {
9 | publicProfile(username: $username) {
10 | _id
11 | username
12 | name
13 | email
14 | images {
15 | url
16 | public_id
17 | }
18 | about
19 | }
20 | }
21 | `;
22 | const SingleUser = () => {
23 | let params = useParams();
24 | const { loading, data } = useQuery(PUBLIC_PROFILE, {
25 | variables: { username: params.username }
26 | });
27 |
28 | if (loading) return Loading...
;
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default SingleUser;
40 |
--------------------------------------------------------------------------------
/final/client/src/pages/Users.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from 'react';
2 | import ApolloClient from 'apollo-boost';
3 | import { gql } from 'apollo-boost';
4 | import { useQuery, useLazyQuery } from '@apollo/react-hooks';
5 | import { AuthContext } from '../context/authContext';
6 | import { useHistory } from 'react-router-dom';
7 | import { ALL_USERS } from '../graphql/queries';
8 | import UserCard from '../components/UserCard';
9 |
10 | const Users = () => {
11 | const { data, loading, error } = useQuery(ALL_USERS);
12 |
13 | if (loading) return Loading...
;
14 |
15 | return (
16 |
17 |
18 | {data &&
19 | data.allUsers.map((user) => (
20 |
21 |
22 |
23 | ))}
24 |
25 |
26 | );
27 | };
28 |
29 | export default Users;
30 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/CompleteRegistration.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from 'react';
2 | import { auth } from '../../firebase';
3 | import { toast } from 'react-toastify';
4 | import { useHistory } from 'react-router-dom';
5 | import { AuthContext } from '../../context/authContext';
6 | import { useMutation } from '@apollo/react-hooks';
7 | import { gql } from 'apollo-boost';
8 | import AuthForm from '../../components/forms/AuthForm';
9 | import { USER_CREATE } from '../../graphql/mutations';
10 |
11 | const CompleteRegistration = () => {
12 | const { dispatch } = useContext(AuthContext);
13 | const [email, setEmail] = useState('');
14 | const [password, setPassword] = useState('');
15 | const [loading, setLoading] = useState(false);
16 |
17 | let history = useHistory();
18 |
19 | useEffect(() => {
20 | setEmail(window.localStorage.getItem('emailForRegistration'));
21 | }, [history]);
22 |
23 | const [userCreate] = useMutation(USER_CREATE);
24 |
25 | const handleSubmit = async (e) => {
26 | e.preventDefault();
27 | setLoading(true);
28 | // validation
29 | if (!email || !password) {
30 | toast.error('Email and password is required');
31 | return;
32 | }
33 | try {
34 | const result = await auth.signInWithEmailLink(email, window.location.href);
35 | // console.log(result);
36 | if (result.user.emailVerified) {
37 | // remove email from local storage
38 | window.localStorage.removeItem('emailForRegistration');
39 | let user = auth.currentUser;
40 | await user.updatePassword(password);
41 |
42 | // dispatch user with token and email
43 | // then redirect
44 | const idTokenResult = await user.getIdTokenResult();
45 | dispatch({
46 | type: 'LOGGED_IN_USER',
47 | payload: { email: user.email, token: idTokenResult.token }
48 | });
49 | // make api request to save/update user in mongodb
50 | userCreate();
51 | history.push('/password/update');
52 | }
53 | } catch (error) {
54 | console.log('register complete error', error.message);
55 | setLoading(false);
56 | toast.error(error.message);
57 | }
58 | };
59 |
60 | return (
61 |
62 | {loading ?
Loading...
:
Complete Your Registration
}
63 |
72 |
73 | );
74 | };
75 |
76 | export default CompleteRegistration;
77 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from 'react';
2 | import { AuthContext } from '../../context/authContext';
3 | import { Link, useHistory } from 'react-router-dom';
4 | import { toast } from 'react-toastify';
5 | import { auth, googleAuthProvider } from '../../firebase';
6 | import { useMutation } from '@apollo/react-hooks';
7 | import { gql } from 'apollo-boost';
8 | import AuthForm from '../../components/forms/AuthForm';
9 | import { USER_CREATE } from '../../graphql/mutations';
10 |
11 | const Login = () => {
12 | const { dispatch } = useContext(AuthContext);
13 | const [email, setEmail] = useState('gqlreactnode@gmail.com');
14 | const [password, setPassword] = useState('gggggg');
15 | const [loading, setLoading] = useState(false);
16 |
17 | let history = useHistory();
18 |
19 | const [userCreate] = useMutation(USER_CREATE);
20 |
21 | const handleSubmit = async (e) => {
22 | e.preventDefault();
23 | setLoading(true);
24 | try {
25 | await auth.signInWithEmailAndPassword(email, password).then(async (result) => {
26 | const { user } = result;
27 | const idTokenResult = await user.getIdTokenResult();
28 |
29 | dispatch({
30 | type: 'LOGGED_IN_USER',
31 | payload: { email: user.email, token: idTokenResult.token }
32 | });
33 |
34 | // send user info to our server mongodb to either update/create
35 | userCreate();
36 | history.push('/profile');
37 | });
38 | } catch (error) {
39 | console.log('login error', error);
40 | toast.error(error.message);
41 | setLoading(false);
42 | }
43 | };
44 |
45 | const googleLogin = () => {
46 | auth.signInWithPopup(googleAuthProvider).then(async (result) => {
47 | const { user } = result;
48 | const idTokenResult = await user.getIdTokenResult();
49 |
50 | dispatch({
51 | type: 'LOGGED_IN_USER',
52 | payload: { email: user.email, token: idTokenResult.token }
53 | });
54 |
55 | // send user info to our server mongodb to either update/create
56 | userCreate();
57 | history.push('/profile');
58 | });
59 | };
60 |
61 | return (
62 |
63 | {loading ?
Loading...
:
Login
}
64 |
67 |
76 |
77 | Forgot Password
78 |
79 |
80 | );
81 | };
82 |
83 | export default Login;
84 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/PasswordForgot.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { auth } from '../../firebase';
3 | import { toast } from 'react-toastify';
4 | import AuthForm from '../../components/forms/AuthForm';
5 |
6 | const PasswordForgot = () => {
7 | const [email, setEmail] = useState('');
8 | const [loading, setLoading] = useState(false);
9 |
10 | const handleSubmit = async (e) => {
11 | e.preventDefault();
12 | setLoading(true);
13 |
14 | const config = {
15 | url: process.env.REACT_APP_PASSWORD_FORGOT_REDIRECT,
16 | handleCodeInApp: true
17 | };
18 |
19 | await auth
20 | .sendPasswordResetEmail(email, config)
21 | .then(() => {
22 | setEmail('');
23 | setLoading(false);
24 | toast.success(`Email is sent to ${email}. Click on the link to reset your password`);
25 | })
26 | .catch((error) => {
27 | setLoading(false);
28 | console.log('error on password forgot email', error);
29 | });
30 | };
31 |
32 | return (
33 |
34 | {loading ?
Loading...
:
Forgot Password
}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default PasswordForgot;
42 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/PasswordUpdate.js:
--------------------------------------------------------------------------------
1 | import React, { useState, Fragment } from 'react';
2 | import { auth } from '../../firebase';
3 | import { toast } from 'react-toastify';
4 | import AuthForm from '../../components/forms/AuthForm';
5 |
6 | const PasswordUpdate = () => {
7 | const [password, setPassword] = useState('');
8 | const [loading, setLoading] = useState(false);
9 |
10 | const handleSubmit = async (e) => {
11 | e.preventDefault();
12 | setLoading(true);
13 |
14 | auth.currentUser
15 | .updatePassword(password)
16 | .then(() => {
17 | setLoading(false);
18 | toast.success('Passowrd updated');
19 | })
20 | .catch((error) => {
21 | setLoading(false);
22 | toast.error(error.message);
23 | });
24 | };
25 |
26 | return (
27 |
28 | {loading ?
Loading...
:
Password update
}
29 |
30 |
38 |
39 | );
40 | };
41 |
42 | export default PasswordUpdate;
43 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, Fragment, useContext } from 'react';
2 | import { toast } from 'react-toastify';
3 | import { useQuery, useMutation } from '@apollo/react-hooks';
4 | import { gql } from 'apollo-boost';
5 | import omitDeep from 'omit-deep';
6 | import { PROFILE } from '../../graphql/queries';
7 | import { USER_UPDATE } from '../../graphql/mutations';
8 | import Resizer from 'react-image-file-resizer';
9 | import axios from 'axios';
10 | import { AuthContext } from '../../context/authContext';
11 | import UserProfile from '../../components/forms/UserProfile';
12 | import FileUpload from '../../components/FileUpload';
13 |
14 | const Profile = () => {
15 | const { state } = useContext(AuthContext);
16 | const [values, setValues] = useState({
17 | username: '',
18 | name: '',
19 | email: '',
20 | about: '',
21 | images: []
22 | });
23 | const [loading, setLoading] = useState(false);
24 |
25 | const { data } = useQuery(PROFILE);
26 |
27 | useMemo(() => {
28 | if (data) {
29 | console.log(data.profile);
30 | setValues({
31 | ...values,
32 | username: data.profile.username,
33 | name: data.profile.name,
34 | email: data.profile.email,
35 | about: data.profile.about,
36 | images: omitDeep(data.profile.images, ['__typename'])
37 | });
38 | }
39 | }, [data]);
40 |
41 | // mutation
42 | const [userUpdate] = useMutation(USER_UPDATE, {
43 | update: ({ data }) => {
44 | console.log('USER UPDATE MUTATION IN PROFILE', data);
45 | toast.success('Profile updated');
46 | }
47 | });
48 |
49 | // destructure
50 | const { username, name, email, about, images } = values;
51 |
52 | const handleSubmit = (e) => {
53 | e.preventDefault();
54 | // console.log(values);
55 | setLoading(true);
56 | userUpdate({ variables: { input: values } });
57 | setLoading(false);
58 | };
59 |
60 | const handleChange = (e) => {
61 | setValues({ ...values, [e.target.name]: e.target.value });
62 | };
63 |
64 | return (
65 |
66 |
67 |
68 | {loading ?
Loading...
: Profile
}
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default Profile;
79 |
--------------------------------------------------------------------------------
/final/client/src/pages/auth/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { auth } from '../../firebase';
3 | import { toast } from 'react-toastify';
4 | import AuthForm from '../../components/forms/AuthForm';
5 |
6 | const Register = () => {
7 | const [email, setEmail] = useState('gqlreactnode@gmail.com');
8 | const [loading, setLoading] = useState(false);
9 |
10 | const handleSubmit = async (e) => {
11 | e.preventDefault();
12 | setLoading(true);
13 | const config = {
14 | url: process.env.REACT_APP_CONFIRMATION_EMAIL_REDIRECT,
15 | handleCodeInApp: true
16 | };
17 | const result = await auth.sendSignInLinkToEmail(email, config);
18 | console.log('result', result);
19 | // show toast notification to user about email sent
20 | toast.success(`Email is sent to ${email}. click the link to complete your registration.`);
21 | // save user email to local storage
22 | window.localStorage.setItem('emailForRegistration', email);
23 | // clear state
24 | setEmail('');
25 | setLoading('');
26 | };
27 |
28 | return (
29 |
30 | {loading ?
Loading...
:
Register
}
31 |
32 |
33 | );
34 | };
35 |
36 | export default Register;
37 |
--------------------------------------------------------------------------------
/final/client/src/pages/post/Post.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, useEffect, fragment } from 'react';
2 | import { toast } from 'react-toastify';
3 | import { AuthContext } from '../../context/authContext';
4 | import { useQuery, useMutation } from '@apollo/react-hooks';
5 | import FileUpload from '../../components/FileUpload';
6 | import { POST_CREATE, POST_DELETE } from '../../graphql/mutations';
7 | import { POSTS_BY_USER } from '../../graphql/queries';
8 | import PostCard from '../../components/PostCard';
9 |
10 | const initialState = {
11 | content: '',
12 | image: {
13 | url: 'https://via.placeholder.com/200x200.png?text=Post',
14 | public_id: '123'
15 | }
16 | };
17 |
18 | const Post = () => {
19 | const [values, setValues] = useState(initialState);
20 | const [loading, setLoading] = useState(false);
21 | // query
22 | const { data: posts } = useQuery(POSTS_BY_USER);
23 |
24 | // destructure
25 | const { content, image } = values;
26 |
27 | // mutation
28 | const [postCreate] = useMutation(POST_CREATE, {
29 | // read query from cache / write query to cache
30 | update: (cache, { data: { postCreate } }) => {
31 | // read Query from cache
32 | const { postsByUser } = cache.readQuery({
33 | query: POSTS_BY_USER
34 | });
35 | // write Query to cache
36 | cache.writeQuery({
37 | query: POSTS_BY_USER,
38 | data: {
39 | postsByUser: [postCreate, ...postsByUser]
40 | }
41 | });
42 | },
43 | onError: (err) => console.log(err.graphqQLError[0].message)
44 | });
45 |
46 | const [postDelete] = useMutation(POST_DELETE, {
47 | update: ({ data }) => {
48 | console.log('POST DELETE MUTATION', data);
49 | toast.error('Post deleted');
50 | },
51 | onError: (err) => {
52 | console.log(err);
53 | toast.error('Post delete failed');
54 | }
55 | });
56 |
57 | const handleDelete = async (postId) => {
58 | let answer = window.confirm('Delete?');
59 | if (answer) {
60 | setLoading(true);
61 | postDelete({
62 | variables: { postId },
63 | refetchQueries: [{ query: POSTS_BY_USER }]
64 | });
65 | setLoading(false);
66 | }
67 | };
68 |
69 | const handleSubmit = async (e) => {
70 | e.preventDefault();
71 | setLoading(true);
72 | postCreate({ variables: { input: values } });
73 | setValues(initialState);
74 | setLoading(false);
75 | toast.success('Post created');
76 | };
77 |
78 | const handleChange = (e) => {
79 | e.preventDefault();
80 | setValues({ ...values, [e.target.name]: e.target.value });
81 | };
82 |
83 | const createForm = () => (
84 |
102 | );
103 |
104 | return (
105 |
106 | {loading ?
Loading...
:
Create
}
107 |
108 |
115 |
116 |
117 |
{createForm()}
118 |
119 |
120 |
121 | {posts &&
122 | posts.postsByUser.map((post) => (
123 |
131 | ))}
132 |
133 |
134 | );
135 | };
136 |
137 | export default Post;
138 |
--------------------------------------------------------------------------------
/final/client/src/pages/post/PostUpdate.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useEffect } from 'react';
2 | import { toast } from 'react-toastify';
3 | import { useLazyQuery, useMutation } from '@apollo/react-hooks';
4 | import { SINGLE_POST } from '../../graphql/queries';
5 | import { POST_UPDATE } from '../../graphql/mutations';
6 | import omitDeep from 'omit-deep';
7 | import { useParams } from 'react-router-dom';
8 | import FileUpload from '../../components/FileUpload';
9 |
10 | const PostUpdate = () => {
11 | const [values, setValues] = useState({
12 | content: '',
13 | image: {
14 | url: '',
15 | public_id: ''
16 | }
17 | });
18 | const [getSinglePost, { data: singlePost }] = useLazyQuery(SINGLE_POST);
19 | const [postUpdate] = useMutation(POST_UPDATE);
20 |
21 | const [loading, setLoading] = useState(false);
22 | // router
23 | const { postid } = useParams();
24 | // destructure
25 | const { content, image } = values;
26 |
27 | useMemo(() => {
28 | if (singlePost) {
29 | setValues({
30 | ...values,
31 | _id: singlePost.singlePost._id,
32 | content: singlePost.singlePost.content,
33 | image: omitDeep(singlePost.singlePost.image, ['__typename'])
34 | });
35 | }
36 | }, [singlePost]);
37 |
38 | useEffect(() => {
39 | console.log(postid);
40 | getSinglePost({ variables: { postId: postid } });
41 | }, []);
42 |
43 | const handleChange = (e) => {
44 | setValues({ ...values, [e.target.name]: e.target.value });
45 | };
46 |
47 | const handleSubmit = (e) => {
48 | e.preventDefault();
49 | setLoading(true);
50 | postUpdate({ variables: { input: values } });
51 | setLoading(false);
52 | toast.success('Post Updated');
53 | };
54 |
55 | const updateForm = () => (
56 |
74 | );
75 |
76 | return (
77 |
78 | {loading ?
Loading...
: Update
}
79 |
80 |
87 |
88 | {updateForm()}
89 |
90 | );
91 | };
92 |
93 | export default PostUpdate;
94 |
--------------------------------------------------------------------------------
/final/client/src/pages/post/SinglePost.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useEffect } from 'react';
2 | import { toast } from 'react-toastify';
3 | import { useLazyQuery, useMutation } from '@apollo/react-hooks';
4 | import { SINGLE_POST } from '../../graphql/queries';
5 | import { useParams } from 'react-router-dom';
6 | import FileUpload from '../../components/FileUpload';
7 | import PostCard from '../../components/PostCard';
8 |
9 | const SinglePost = () => {
10 | const [values, setValues] = useState({
11 | content: '',
12 | image: {
13 | url: '',
14 | public_id: ''
15 | },
16 | postedBy: {}
17 | });
18 | const [getSinglePost, { data: singlePost }] = useLazyQuery(SINGLE_POST);
19 | // router
20 | const { postid } = useParams();
21 | // destructure
22 | const { content, image } = values;
23 |
24 | useMemo(() => {
25 | if (singlePost) {
26 | setValues({
27 | ...values,
28 | _id: singlePost.singlePost._id,
29 | content: singlePost.singlePost.content,
30 | image: singlePost.singlePost.image,
31 | postedBy: singlePost.singlePost.postedBy
32 | });
33 | }
34 | }, [singlePost]);
35 |
36 | useEffect(() => {
37 | console.log(postid);
38 | getSinglePost({ variables: { postId: postid } });
39 | }, []);
40 |
41 | return (
42 |
45 | );
46 | };
47 |
48 | export default SinglePost;
49 |
--------------------------------------------------------------------------------
/final/server/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/final/server/.DS_Store
--------------------------------------------------------------------------------
/final/server/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules
3 |
--------------------------------------------------------------------------------
/final/server/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
--------------------------------------------------------------------------------
/final/server/config/fbServiceAccountKey.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "service_account",
3 | "project_id": "gqlreactnode99",
4 | "private_key_id": "b2be34e46169cfdd87c44b8e6e74551edb25fe57",
5 | "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClaV6IBnlyYZtX\n4Mr9Je7T4eWpBwjZjRRIxTl5VYvtM5sfNFxd9aDxqVXjZYZxuvhk/vRkz8uRg0yy\nhiNn+XLwfROw9aJRuaOYGaoPaKWbYsziAnKp9cL4b4W2iKKm3UhcH4kfq8X4LMPu\nK0g/5CRXN3losr2UWQNzQn7yjzPGYgo7AzHav9GUIMsfLfk4czDCKm2WrzGKfqSr\n3GD0Yyd40wDMNh1kHM+gRjMb8HFK257iJ5ZiWe8CX8sbk07AYnGexiy2rWPqUH+s\n5d87iONpMjPx9QtQIl32jZxdmp2sbM9gY/VWPN3UWkL71QiN8gn0U2yWZPmfdmes\ncdsuiyt9AgMBAAECggEAHsMFzY5Gr7rvRNyNY57WV5k/OLwSLUNLbm0fTy1VpHtg\nceEu/Fpd4BN54ZxT8HqxDzdLBCSQ2zLr5JeFpiSB96+LJfHIiiJYBkVflxYDFm69\nY5rI8ErAQ6uZ1mR6u0WT224R9k3ft1O77GMoQJmRGJc8gPgcwaX8Okc3jKyNGGt+\nhDf1fKBpMxpnIp84amGgnZPmhOK6CXo/Il/xmWvAcsbANa8SlSkqOmgRI8AOs+V3\nEkXYTB7hhHhnWGwXIP1obgeUM5Vdqy0RRZyPRRpf3PiGMYxI6P78c1MxpUvI9peS\nnxrvVHwklZzhh/bFSoVNah9pxo2sfy+w1iu6HrgQAQKBgQDa3a8UTnROAmFh+qy3\n3zI4eQKjR87CNkIFHinJ6KFz9k6yjEhmp2MTttp5+OZi9bwL+zpxFU0uHWwDxudF\nfCSiI0AlPO8GktSHqHhOHTu4XiXiZwUvHR7aZjy/MpL0h9P+0GviMu5iph+CpYeq\nAAxGTIBa3rfX9C/8ecnrfiU2fQKBgQDBee0ooEBNIvACc8R9OBuv9rlOSEwVV6zw\n8/c7hc4YsoHj9YEofc1Spb0lL8jkKCuxqGX+BhKZrAhIXRXoOaTeng3sBQLP7fxA\n40lrx6UynlJoBAmaDQpJ9qf4IJTuEi+ZjxifSmK08ovuBk/HjGotMW1ToSvZZXP0\nnFZvAe7ZAQKBgQDL1aJ1GwweiwD/K2Moa+ptxeW7A+l/3uBlu83XiZy0TzTD/PqB\nAGu5tJaM+k424/2aewPWxav3wtcSPXCuugu0JXhcNf+285CUN0a0GW3BK43E6YVe\nd5SAeq8gso1CyC2cJ6gSJrT1kxnNpzROE3VbE2yHNN2rdnQqg5KWjAlEUQKBgAdQ\nwuTcWlIQVh7hnbZWXsWigJMzLJ1J+WIK93gqsQZCCaNC7yVGzHKpjaNQUTo/Qpev\nFFwsgpuI469IxcFIHLHGLCSWc+rExtr1PRt0Kwzk65y/OlW21ILDbsp1AOyXh3E4\n9edf+qd43E2ds3qKFqGq0sVsvKm1qlK/bo03934BAoGATRn4Ysv5JzjtNzuOI8NN\n5sqSWRfvVmDqsyuet/10sXih4B8rzL3YYZscNoUxUpn9sZorgl03/ivDYUms/qqW\nK26NwIn+LLZmeVy0+onTWRGYAzNm0OzIX1NQmStZf0OVYO/vBsYRFKYDTqwe/tEk\nXkXvoJ0UiQvJtmPln0mYfPM=\n-----END PRIVATE KEY-----\n",
6 | "client_email": "firebase-adminsdk-5yha0@gqlreactnode99.iam.gserviceaccount.com",
7 | "client_id": "106432225930891843237",
8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9 | "token_uri": "https://oauth2.googleapis.com/token",
10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-5yha0%40gqlreactnode99.iam.gserviceaccount.com"
12 | }
13 |
--------------------------------------------------------------------------------
/final/server/helpers/auth.js:
--------------------------------------------------------------------------------
1 | var admin = require('firebase-admin');
2 |
3 | var serviceAccount = require('../config/fbServiceAccountKey.json');
4 |
5 | admin.initializeApp({
6 | credential: admin.credential.cert(serviceAccount)
7 | // databaseURL: 'https://gqlreactnode99.firebaseio.com'
8 | });
9 |
10 | exports.authCheck = async (req) => {
11 | try {
12 | const currentUser = await admin.auth().verifyIdToken(req.headers.authtoken);
13 | console.log('CURRENT USER', currentUser);
14 | return currentUser;
15 | } catch (error) {
16 | console.log('AUTH CHECK ERROR', error);
17 | throw new Error('Invalid or expired token');
18 | }
19 | };
20 |
21 | exports.authCheckMiddleware = (req, res, next) => {
22 | if (req.headers.authtoken) {
23 | admin
24 | .auth()
25 | .verifyIdToken(req.headers.authtoken)
26 | .then((result) => {
27 | next();
28 | })
29 | .catch((error) => console.log(error));
30 | } else {
31 | res.json({ error: 'Unauthorized' });
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/final/server/models/post.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { ObjectId } = mongoose.Schema;
3 |
4 | const postSchema = new mongoose.Schema(
5 | {
6 | content: {
7 | type: String,
8 | required: 'Content is required',
9 | text: true
10 | },
11 | image: {
12 | url: {
13 | type: String,
14 | default: 'https://via.placeholder.com/200x200.png?text=Post'
15 | },
16 | public_id: {
17 | type: String,
18 | default: Date.now
19 | }
20 | },
21 | postedBy: {
22 | type: ObjectId,
23 | ref: 'User'
24 | }
25 | },
26 | { timestamps: true }
27 | );
28 |
29 | module.exports = mongoose.model('Post', postSchema);
30 |
--------------------------------------------------------------------------------
/final/server/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { ObjectId } = mongoose.Schema;
3 |
4 | const userSchema = new mongoose.Schema(
5 | {
6 | username: {
7 | type: String,
8 | required: true,
9 | index: true,
10 | unique: true
11 | },
12 | name: {
13 | type: String
14 | },
15 | email: {
16 | type: String,
17 | required: true,
18 | index: true,
19 | unique: true
20 | },
21 | images: {
22 | type: Array,
23 | default: [
24 | {
25 | url: 'https://via.placeholder.com/200x200.png?text=Profile',
26 | public_id: Date.now
27 | }
28 | ]
29 | },
30 | about: {
31 | type: String
32 | }
33 | },
34 | { timestamps: true }
35 | );
36 |
37 | module.exports = mongoose.model('User', userSchema);
38 |
--------------------------------------------------------------------------------
/final/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon server.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "apollo-server": "^2.11.0",
14 | "apollo-server-express": "^2.11.0",
15 | "body-parser": "^1.19.0",
16 | "cloudinary": "^1.21.0",
17 | "cors": "^2.8.5",
18 | "dotenv": "^8.2.0",
19 | "express": "^4.17.1",
20 | "firebase-admin": "^8.10.0",
21 | "graphql": "^15.0.0",
22 | "graphql-scalars": "^1.1.0",
23 | "merge-graphql-schemas": "^1.7.7",
24 | "mongoose": "^5.9.7",
25 | "nodemon": "^2.0.2",
26 | "shortid": "^2.2.15"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/final/server/resolvers/auth.js:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server-express');
2 | const shortid = require('shortid');
3 | const { authCheck } = require('../helpers/auth');
4 | const User = require('../models/user');
5 | const { DateTimeResolver } = require('graphql-scalars');
6 |
7 | const profile = async (parent, args, { req }) => {
8 | const currentUser = await authCheck(req);
9 | return await User.findOne({ email: currentUser.email }).exec();
10 | };
11 |
12 | const publicProfile = async (parent, args, { req }) => {
13 | return await User.findOne({ username: args.username }).exec();
14 | };
15 |
16 | const allUsers = async (parent, args) => await User.find({}).exec();
17 |
18 | const userCreate = async (parent, args, { req }) => {
19 | const currentUser = await authCheck(req);
20 | const user = await User.findOne({ email: currentUser.email });
21 | return user
22 | ? user
23 | : new User({
24 | email: currentUser.email,
25 | username: shortid.generate()
26 | }).save();
27 | };
28 |
29 | const userUpdate = async (parent, args, { req }) => {
30 | const currentUser = await authCheck(req);
31 | console.log(args);
32 | const updatedUser = await User.findOneAndUpdate(
33 | { email: currentUser.email },
34 | { ...args.input },
35 | { new: true }
36 | ).exec();
37 | return updatedUser;
38 | };
39 |
40 | module.exports = {
41 | Query: {
42 | profile,
43 | publicProfile,
44 | allUsers
45 | },
46 | Mutation: {
47 | userCreate,
48 | userUpdate
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/final/server/resolvers/post.js:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server-express');
2 | const { posts } = require('../temp');
3 | const { authCheck } = require('../helpers/auth');
4 | const { DateTimeResolver } = require('graphql-scalars');
5 | const Post = require('../models/post');
6 | const User = require('../models/user');
7 |
8 | // subscriptions
9 | const POST_ADDED = 'POST_ADDED';
10 | const POST_UPDATED = 'POST_UPDATED';
11 | const POST_DELETED = 'POST_DELETED';
12 |
13 | // mutation
14 | const postCreate = async (parent, args, { req, pubsub }) => {
15 | const currentUser = await authCheck(req);
16 | // validation
17 | if (args.input.content.trim() === '') throw new Error('Content is required');
18 |
19 | const currentUserFromDb = await User.findOne({
20 | email: currentUser.email
21 | });
22 | let newPost = await new Post({
23 | ...args.input,
24 | postedBy: currentUserFromDb._id
25 | })
26 | .save()
27 | .then((post) => post.populate('postedBy', '_id username').execPopulate());
28 |
29 | pubsub.publish(POST_ADDED, { postAdded: newPost });
30 |
31 | return newPost;
32 | };
33 |
34 | const allPosts = async (parent, args) => {
35 | const currentPage = args.page || 1;
36 | const perPage = 3;
37 |
38 | return await Post.find({})
39 | .skip((currentPage - 1) * perPage)
40 | .populate('postedBy', 'username _id')
41 | .limit(perPage)
42 | .sort({ createdAt: -1 })
43 | .exec();
44 | };
45 |
46 | const postsByUser = async (parent, args, { req }) => {
47 | const currentUser = await authCheck(req);
48 | const currentUserFromDb = await User.findOne({
49 | email: currentUser.email
50 | }).exec();
51 |
52 | return await Post.find({ postedBy: currentUserFromDb })
53 | .populate('postedBy', '_id username')
54 | .sort({ createdAt: -1 });
55 | };
56 |
57 | const singlePost = async (parent, args) => {
58 | return await Post.findById({ _id: args.postId })
59 | .populate('postedBy', '_id username')
60 | .exec();
61 | };
62 |
63 | const postUpdate = async (parent, args, { req, pubsub }) => {
64 | const currentUser = await authCheck(req);
65 | // validation
66 | if (args.input.content.trim() === '') throw new Error('Content is requried');
67 | // get current user mongodb _id based in email
68 | const currentUserFromDb = await User.findOne({ email: currentUser.email }).exec();
69 | // _id of post to update
70 | const postToUpdate = await Post.findById({ _id: args.input._id }).exec();
71 | // if currentuser id and id of the post's postedBy user id is same, allow update
72 | if (currentUserFromDb._id.toString() !== postToUpdate.postedBy._id.toString())
73 | throw new Error('Unauthorized action');
74 | let updatedPost = await Post.findByIdAndUpdate(args.input._id, { ...args.input }, { new: true })
75 | .exec()
76 | .then((post) => post.populate('postedBy', '_id username').execPopulate());
77 |
78 | pubsub.publish(POST_UPDATED, {
79 | postUpdated: updatedPost
80 | });
81 |
82 | return updatedPost;
83 | };
84 |
85 | const postDelete = async (parent, args, { req, pubsub }) => {
86 | const currentUser = await authCheck(req);
87 | const currentUserFromDb = await User.findOne({ email: currentUser.email }).exec();
88 | const postToDelete = await Post.findById({ _id: args.postId }).exec();
89 | if (currentUserFromDb._id.toString() !== postToDelete.postedBy._id.toString())
90 | throw new Error('Unauthorized action');
91 | let deletedPost = await Post.findByIdAndDelete({ _id: args.postId })
92 | .exec()
93 | .then((post) => post.populate('postedBy', '_id username').execPopulate());
94 |
95 | pubsub.publish(POST_DELETED, {
96 | postDeleted: deletedPost
97 | });
98 |
99 | return deletedPost;
100 | };
101 |
102 | const totalPosts = async (parent, args) =>
103 | await Post.find({})
104 | .estimatedDocumentCount()
105 | .exec();
106 |
107 | const search = async (parent, { query }) => {
108 | return await Post.find({ $text: { $search: query } })
109 | .populate('postedBy', 'username')
110 | .exec();
111 | };
112 |
113 | module.exports = {
114 | Query: {
115 | allPosts,
116 | postsByUser,
117 | singlePost,
118 | totalPosts,
119 | search
120 | },
121 | Mutation: {
122 | postCreate,
123 | postUpdate,
124 | postDelete
125 | },
126 | Subscription: {
127 | postAdded: {
128 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator([POST_ADDED])
129 | },
130 | postUpdated: {
131 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator([POST_UPDATED])
132 | },
133 | postDeleted: {
134 | subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator([POST_DELETED])
135 | }
136 | }
137 | };
138 |
--------------------------------------------------------------------------------
/final/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { ApolloServer, PubSub } = require('apollo-server-express');
3 | const http = require('http');
4 | const path = require('path');
5 | const mongoose = require('mongoose');
6 | const { fileLoader, mergeTypes, mergeResolvers } = require('merge-graphql-schemas');
7 | require('dotenv').config();
8 | const { authCheckMiddleware } = require('./helpers/auth');
9 | const cors = require('cors');
10 | const bodyParser = require('body-parser');
11 | const cloudinary = require('cloudinary');
12 |
13 | const pubsub = new PubSub();
14 |
15 | // express server
16 | const app = express();
17 |
18 | // db
19 | const db = async () => {
20 | try {
21 | const success = await mongoose.connect(process.env.DATABASE, {
22 | useNewUrlParser: true,
23 | useUnifiedTopology: true,
24 | useCreateIndex: true,
25 | useFindAndModify: false
26 | });
27 | console.log('DB Connected');
28 | } catch (error) {
29 | console.log('DB Connection Error', error);
30 | }
31 | };
32 | // execute database connection
33 | db();
34 |
35 | // middlewares
36 | app.use(cors());
37 | app.use(bodyParser.json({ limit: '5mb' }));
38 |
39 | // typeDefs
40 | const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './typeDefs')));
41 | // resolvers
42 | const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));
43 |
44 | // graphql server
45 | const apolloServer = new ApolloServer({
46 | typeDefs,
47 | resolvers,
48 | context: ({ req }) => ({ req, pubsub })
49 | });
50 |
51 | // applyMiddleware method connects ApolloServer to a specific HTTP framework ie: express
52 | apolloServer.applyMiddleware({ app });
53 |
54 | // server
55 | const httpserver = http.createServer(app);
56 | apolloServer.installSubscriptionHandlers(httpserver);
57 |
58 | // rest endpoint
59 | app.get('/rest', authCheckMiddleware, function(req, res) {
60 | res.json({
61 | data: 'you hit rest endpoint great!'
62 | });
63 | });
64 |
65 | // cloudinary config
66 | cloudinary.config({
67 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
68 | api_key: process.env.CLOUDINARY_API_KEY,
69 | api_secret: process.env.CLOUDINARY_API_SECRET
70 | });
71 |
72 | // upload
73 | app.post('/uploadimages', authCheckMiddleware, (req, res) => {
74 | cloudinary.uploader.upload(
75 | req.body.image,
76 | (result) => {
77 | console.log(result);
78 | res.send({
79 | // url: result.url,
80 | url: result.secure_url,
81 | public_id: result.public_id
82 | });
83 | },
84 | {
85 | public_id: `${Date.now()}`, // public name
86 | resource_type: 'auto' // JPEG, PNG
87 | }
88 | );
89 | });
90 |
91 | // remove image
92 | app.post('/removeimage', authCheckMiddleware, (req, res) => {
93 | let image_id = req.body.public_id;
94 |
95 | cloudinary.uploader.destroy(image_id, (error, result) => {
96 | if (error) return res.json({ success: false, error });
97 | res.send('ok');
98 | });
99 | });
100 |
101 | // port
102 | httpserver.listen(process.env.PORT, function() {
103 | console.log(`server is ready at http://localhost:${process.env.PORT}`);
104 | console.log(`graphql server is ready at http://localhost:${process.env.PORT}${apolloServer.graphqlPath}`);
105 | console.log(`subscription is ready at http://localhost:${process.env.PORT}${apolloServer.subscriptionsPath}`);
106 | });
107 |
--------------------------------------------------------------------------------
/final/server/temp.js:
--------------------------------------------------------------------------------
1 | const posts = [
2 | {
3 | id: 1,
4 | title: 'First post',
5 | description: 'First post description',
6 | category: 'PUBLISHED'
7 | },
8 | {
9 | id: 2,
10 | title: 'Second post',
11 | description: 'Second post description',
12 | category: 'PUBLISHED'
13 | }
14 | ];
15 |
16 | module.exports = {
17 | posts
18 | };
19 |
--------------------------------------------------------------------------------
/final/server/typeDefs/auth.js:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server-express');
2 |
3 | module.exports = gql`
4 | # scalar type
5 | scalar DataTime
6 | type Query {
7 | me: String!
8 | }
9 | type Image {
10 | url: String
11 | public_id: String
12 | }
13 | type User {
14 | _id: ID!
15 | username: String
16 | name: String
17 | email: String
18 | images: [Image]
19 | about: String
20 | createdAt: DataTime
21 | updatedAt: DataTime
22 | }
23 | # custom type
24 | type UserCreateResponse {
25 | username: String!
26 | email: String!
27 | }
28 | # input type
29 | input ImageInput {
30 | url: String
31 | public_id: String
32 | }
33 | # input type
34 | input UserUpdateInput {
35 | username: String
36 | name: String
37 | email: String!
38 | images: [ImageInput]
39 | about: String
40 | }
41 | type Query {
42 | profile: User!
43 | publicProfile(username: String!): User!
44 | allUsers: [User!]
45 | }
46 | type Mutation {
47 | userCreate: UserCreateResponse!
48 | userUpdate(input: UserUpdateInput): User!
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/final/server/typeDefs/post.js:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server-express');
2 |
3 | module.exports = gql`
4 | type Post {
5 | _id: ID!
6 | content: String
7 | image: Image
8 | postedBy: User
9 | }
10 | # input type
11 | input PostCreateInput {
12 | content: String!
13 | image: ImageInput
14 | }
15 | # input type
16 | input PostUpdateInput {
17 | _id: String!
18 | content: String!
19 | image: ImageInput
20 | }
21 | # queries
22 | type Query {
23 | totalPosts: Int!
24 | allPosts(page: Int): [Post!]!
25 | postsByUser: [Post!]!
26 | singlePost(postId: String!): Post!
27 | search(query: String): [Post]
28 | }
29 | # mutations
30 | type Mutation {
31 | postCreate(input: PostCreateInput!): Post!
32 | postUpdate(input: PostUpdateInput!): Post!
33 | postDelete(postId: String!): Post!
34 | }
35 | # subscriptions
36 | type Subscription {
37 | postAdded: Post
38 | postUpdated: Post
39 | postDeleted: Post
40 | }
41 | `;
42 |
--------------------------------------------------------------------------------
/source_code_lecture_5-131.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/graphql-react-node/0b194d2b11b594c430e1a83ec96cf0130228310e/source_code_lecture_5-131.zip
--------------------------------------------------------------------------------