Skip to content

Latest commit

 

History

History
809 lines (714 loc) · 23.1 KB

File metadata and controls

809 lines (714 loc) · 23.1 KB

GraphQl 2

이미지 업로드 기능 넣기 -by multer

GraphQL도 REST API와 마찬가지로 JSON 데이터로 Client 서버와 통신합니다. 따라서 multer를 이용해서 로직을 구현해봅시다.

  1. app.js

    • post-image put 요청 (auth 같이 설정하기) GraphQL에는 별도의 라우터가 존재하지 않습니다 .따라서 app.js에 기존 REST API 방식처럼 라우터를 추가해줍니다.

      app.use(auth);
      
      app.put('/post-image', (req, res, next) => {
        if (!req.isAuth) {
          throw new Error('Not authentication!');
        }
      
        // 1. 파일이 보내졌는지 확인하기 (업데이트 할 때 새 파일을 안넣어도 되기 때문에 200 code을 보낸다.)
        if (!req.file) {
          return res.status(200).json({ message: 'No file provided!' });
        }
        if (req.body.oldPath) {
          clearImage(req.body.oldPath);
        }
        return res
          .status(201)
          .json({ message: 'File stored.', filePath: req.file.path });
      });
      
    • clearImage 함수 추가하기

      const clearImage = filePath => {
        filePath = path.join(__dirname, '..', filePath);
        fs.unlink(filePath, err => console.log(err));
      };
  2. Front: .../Feed.js

    • finishEditHandler 수정하기

      finishEditHandler = (postData) => {
          this.setState({
            editLoading: true,
          });
          // 1. fromData 객체 생성
          const formData = new FormData();
          formData.append('image', postData.image);
          if (this.state.editPost) {
            formData.append('oldPath', this.state.editPost.imagePath);
          }
          // 2. PUT 요청으로 BackEnd로 formData에 사진 넣어서 보내기
          fetch('http://localhost:8080/post-image', {
            method: 'PUT',
            headers: {
              Authorization: 'Bearer ' + this.props.token,
            },
            body: formData,
          })
            .then((res) => res.json())
          // 3. BackEnd 서버로 새로 생성한(업데이트한) post 쿼리 보내기
            .then((fileResData) => {
              console.log(fileResData);
              const imageUrl = fileResData.filePath;
              const graphqlQuery = {
                query: `
                mutation {
                  createPost(postInput: {title: "${postData.title}", content: "${postData.contetn}", imageUrl: "${imageUrl}"}) {
                    _id
                    title
                    content
                    imageUrl
                    creator {
                      name
                    }
                    createdAt
                  }
                }
                `,
              };
      
              return fetch('http://localhost:8080/graphql', {
                method: 'POST',
                body: JSON.stringify(graphqlQuery),
                headers: {
                  Authorization: 'Bearer ' + this.props.token,
                  'Content-Type': 'application/json',
                },
              });
            })
            .then((res) => {
              return res.json();
            })
            .then((resData) => {
              if (resData.errors && resData.errors[0].status === 422) {
                throw new Error(
                  "Validation failed. Make sure the email address isn't used yet!"
                );
              }
              if (resData.errors) {
                throw new Error('Create user failed!');
              }
      
              console.log(resData);
              const post = {
                _id: resData.data.createPost._id,
                title: resData.data.createPost.title,
                content: resData.data.createPost.content,
                creator: resData.data.createPost.creator,
                createdAt: resData.data.createPost.createdAt,
                imagePath: resData.data.createPost.imageUrl,
              };
              this.setState((prevState) => {
                let updatedPosts = [...prevState.posts];
                if (prevState.editPost) {
                  const postIndex = prevState.posts.findIndex(
                    (p) => p._id === prevState.editPosts._id
                  );
                  updatedPosts[postIndex] = post;
                } else {
                  updatedPosts.pop();
                  updatedPosts.unshift(post);
                }
                return {
                  posts: updatedPosts,
                  isEditing: false,
                  editPost: null,
                  editLoading: false,
                };
              });
            })
            .catch((err) => {
              console.log(err);
              this.setState({
                isEditing: false,
                editPost: null,
                editLoading: false,
                error: err,
              });
            });
        };

Single Post 가져오기

  1. graphql/schema.js

    • 쿼리 만들기

      `    
      type RootQuery {
              login(email: String!, password: String!): AuthData!
              posts(page:Int): PostData!
              getPost(id: ID!): Post!
          }
      `
  2. graphql/resolvers.js

    • getPost resolver 만들기

      getPost: async function ({ id }, req) {
          if (!req.isAuth) {
            const error = new Error('권한이 없습니다.');
            error.code = 401;
            throw error;
          }
          console.log('여기 통과');
          const post = await Post.findById(id).populate('creator');
          if (!post) {
            const error = new Error('No post found!');
            error.code = 404;
            throw error;
          }
          return {
            ...post._doc,
            _id: post._id.toString(),
            createdAt: post.createdAt.toISOString(),
            updatedAt: post.updatedAt.toISOString(),
          };
        },
  3. Front : .../SinglePost.js

    • componentDidMount() 주의: fetch 함수에 POST 요청(method:'POST' ) 안보내줘서 1시간 동안 고생함.

        componentDidMount() {
          const postId = this.props.match.params.postId;
          console.log(postId);
          const graphqlQuery = {
            query: `
            {
              getPost(id: "${postId}") {
                title
                content
                imageUrl
                creator {
                  name
                }
                createdAt
              }
            }
            `,
          };
          fetch('http://localhost:8080/graphql', {
            method: 'POST',
            headers: {
              Authorization: 'Bearer ' + this.props.token,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(graphqlQuery),
          })
            .then((res) => {
              if (res.status !== 200) {
                throw new Error('Failed to fetch status');
              }
              return res.json();
            })
            .then((resData) => {
              console.log(resData);
              if (resData.errors) {
                throw new Error('Fetching failed!');
              }
              this.setState({
                title: resData.data.getPost.title,
                author: resData.data.getPost.creator.name,
                image: 'http://localhost:8080/' + resData.data.getPost.imageUrl,
                date: new Date(resData.data.getPost.createdAt).toLocaleDateString(
                  'en-US'
                ),
                content: resData.data.getPost.content,
              });
            })
            .catch((err) => {
              console.log(err);
            });
        }

post 업데이트 기능 추가

  1. graphql/schema.js

    • RootMutationupdatePost 추가

      `
          type RootMutation {
              createUser(userInput: UserInputData): User!
              createPost(postInput: PostInputData): Post!
              updatePost(id: ID!, postInput: PostInputData): Post!
          }
      `
  2. graphql/resolvers.js

    • updatePost 작성하기

        updatePost: async function ({ id, postInput }, req) {
          // 1. 권한 체크
          if (!req.isAuth) {
            const error = new Error('권한이 없습니다.');
            error.code = 401;
            throw error;
          }
          // 2. mongoDB에서 수정할 post 객체 가져오기
          const post = await Post.findById(id).populate('creator');
          if (!post) {
            const error = new Error('No post found!');
            error.code = 404;
            throw error;
          }
          // 3. 자기가 작성한 post만 수정가능하게 권한 설정
          if (post.creator._id.toString() !== req.userId.toString()) {
            const error = new Error('다른 사람의 post를 수정할 권한이 없습니다.');
            error.code = 403;
            throw error;
          }
          // 4. 사용자 Input 체크
          const errors = [];
          if (
            validator.isEmpty(postInput.title) ||
            !validator.isLength(postInput.title, { min: 3 })
          ) {
            errors.push({ message: 'Title is invalid.' });
          }
          if (
            validator.isEmpty(postInput.content) ||
            !validator.isLength(postInput.content, { min: 3 })
          ) {
            errors.push({ message: 'content is invalid.' });
          }
          if (errors.length > 0) {
            const error = new Error('Invalid Input');
            error.data = errors;
            error.code = 422;
            throw error;
          }
      
          // 5. post 객체 update하기
          post.title = postInput.title;
          post.content = postInput.content;
          // 사용자가 새로운 사진을 업데이트했다면
          if (postInput.imageUrl !== 'undefined') {
            post.imageUrl = postInput.imageUrl;
          }
          // 6. mongoDB에 저장
          const updatedPost = await post.save();
      
          return {
            ...updatedPost._doc,
            _id: updatedPost._id.totring(),
            createdAt: updatedPost.createdAt.toISOString(),
            updatedAt: updatedPost.updatedAt.toISOString(),
          };
        },
  3. Front: .../Feed.js

    • finshEditHandler

      finishEditHandler = (postData) => {
          this.setState({
            editLoading: true,
          });
          const formData = new FormData();
          formData.append('image', postData.image);
          if (this.state.editPost) {
            formData.append('oldPath', this.state.editPost.imagePath);
          }
          fetch('http://localhost:8080/post-image', {
            method: 'PUT',
            headers: {
              Authorization: 'Bearer ' + this.props.token,
            },
            body: formData,
          })
            .then((res) => res.json())
            .then((fileResData) => {
              console.log(fileResData);
              let imageUrl;
              if (fileResData.filePath) {
                imageUrl = fileResData.filePath.replace('\\', '/');
              }
              // const imageUrl = fileResData.filePath;
              let graphqlQuery = {
                query: `
                mutation {
                  createPost(postInput: {title: "${postData.title}", content: "${postData.content}", imageUrl: "${imageUrl}"}) {
                    _id
                    title
                    content
                    imageUrl
                    creator {
                      name
                    }
                    createdAt
                  }
                }
                `,
              };
      
              if (this.state.editPost) {
                graphqlQuery = {
                  query: `
                  mutation {
                    updatePost(id:"${this.state.editPost._id}" ,postInput: {title: "${postData.title}", content: "${postData.content}", imageUrl: "${imageUrl}"}) {
                      _id
                      title
                      content
                      imageUrl
                      creator {
                        name
                      }
                      createdAt
                    }
                  }
                  `,
                };
              }
      
              return fetch('http://localhost:8080/graphql', {
                method: 'POST',
                body: JSON.stringify(graphqlQuery),
                headers: {
                  Authorization: 'Bearer ' + this.props.token,
                  'Content-Type': 'application/json',
                },
              });
            })
            .then((res) => {
              return res.json();
            })
            .then((resData) => {
              if (resData.errors && resData.errors[0].status === 422) {
                throw new Error(
                  "Validation failed. Make sure the email address isn't used yet!"
                );
              }
              if (resData.errors) {
                throw new Error('Create user failed!');
              }
              let resDataFiled = 'createPost';
              if (this.state.editPost) {
                resDataFiled = 'updatePost';
              }
              const post = {
                _id: resData.data[resDataFiled]._id,
                title: resData.data[resDataFiled].title,
                content: resData.data[resDataFiled].content,
                creator: resData.data[resDataFiled].creator,
                createdAt: resData.data[resDataFiled].createdAt,
              };
              this.setState((prevState) => {
                let updatedPosts = [...prevState.posts];
                if (prevState.editPost) {
                  const postIndex = prevState.posts.findIndex(
                    (p) => p._id === prevState.editPost._id
                  );
                  updatedPosts[postIndex] = post;
                } else {
                  updatedPosts.pop();
                  updatedPosts.unshift(post);
                }
                return {
                  posts: updatedPosts,
                  isEditing: false,
                  editPost: null,
                  editLoading: false,
                };
              });
            })
            .catch((err) => {
              console.log(err);
              this.setState({
                isEditing: false,
                editPost: null,
                editLoading: false,
                error: err,
              });
            });
        };

삭제 기능 넣기

  1. graphql/schema.js

    • deletePost 생성하기

      `
          type RootMutation {
              createUser(userInput: UserInputData): User!
              createPost(postInput: PostInputData): Post!
              updatePost(id: ID!, postInput: PostInputData): Post!
              deletePost(id: ID!): Boolean
          }
      `
  2. grpahql/resolvers.js

    • deletePost 생성하기

        deletePost: async function ({ id }, req) {
          // 1. 권한 확인
          if (!req.auth) {
            const error = new Error('권한이 없습니다.');
            error.code = 401;
            throw error;
          }
          // 2. 삭제하고자 하는 post 객체 가져오기
          const post = await Post.findById(id);
      
          // 없으면 오류!
          console.log(post);
          if (!post) {
            const error = new Error('포스트가 없습니다.');
            error.state = 404;
            throw error;
          }
          // 3. post의 userId와 요청하는 유저의 id가 일치하는지 확인하기
          if (req.userId.toString() !== post.creator.toString()) {
            const error = new Error('다른 사람의 post를 삭제할 권한이 없습니다.');
            error.code = 403;
            throw error;
          }
      
          // 4. post DB와 이미지 삭제하기
          clearImage(post.imageUrl);
          await Post.findByIdAndRemove(id);
      
          // 5. user객체 가져와서 posts에 해당 post지우기
          const user = await User.findById(req.userId);
          user.posts.pull(id);
          await user.save();
          return true;
        },
      };
    • clearImage 함수 가져오기

      const path = require('path');
      const fs = require('fs');
      
      const clearImage = (filePath) => {
        filePath = path.join(__dirname, '..', filePath);
        fs.unlink(filePath, (err) => console.log(err));
      };
  3. Front : .../Feed.js

    • deletePostHandler

       deletePostHandler = (postId) => {
          this.setState({ postsLoading: true });
          const graphqlQuery = {
            query: `
            mutation{
              deletePost(id: "${postId}")
            }
            `,
          };
          fetch('http://localhost:8080/graphql', {
            method: 'POST',
            headers: {
              Authorization: 'Bearer ' + this.props.token,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(graphqlQuery),
          })
            .then((res) => {
              if (res.status !== 200 && res.status !== 201) {
                throw new Error('Deleting a post failed!');
              }
              return res.json();
            })
            .then((resData) => {
              console.log(resData);
              this.loadPosts();
            })
            .catch((err) => {
              console.log(err);
              this.setState({ postsLoading: false });
            });
        };

    User Status 가져오기

    1. graphql/schema.js

      • query 만들기

        `
            type RootQuery {
                login(email: String!, password: String!): AuthData!
                posts(page:Int): PostData!
                getPost(id: ID!): Post!
                status: User!
            }
        `
    2. graphql/resolvers.js

      • status

          status: async function (args, req) {
            if (!req.isAuth) {
              const error = new Error('권한이 없습니다.');
              error.code = 401;
              throw error;
            }
            // console.log(req.);
            // 2. status db에서 찾기
            const user = await User.findById(req.userId);
            if (!user) {
              const error = new Error();
              error.status = 404;
              throw error;
            }
            return { ...user._doc, _id: user._id.toString() };
          },
    3. Front : .../Feed.js

      • componentDidMount()

        componentDidMount() {
            const graphqlQuery = {
              query: `
              {
                status {
                  status
                }
              }      
              `,
            };
            fetch('http://localhost:8080/graphql', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer ' + this.props.token,
                'Content-Type': 'application/json',
              },
              body: JSON.stringify(graphqlQuery),
            })
              .then((res) => {
                return res.json();
              })
              .then((resData) => {
                if(resData.errors){
                  throw new Error('Fetching status failed!')
                }
                this.setState({ status: resData.data.status.status });
              })
              .catch(this.catchError);
        
            this.loadPosts();
          }

      User Status 업데이트

      1. graphql/shcema.js

        • mutation 정의하기

          `  
          type RootMutation {
                  createUser(userInput: UserInputData): User!
                  createPost(postInput: PostInputData): Post!
                  updatePost(id: ID!, postInput: PostInputData): Post!
                  deletePost(id: ID!): Boolean,
                  updateStatus(status: String!): User!
              }
          `
      2. graphql/resolvers.js

        • updateStatus 작성하기

            updateStatus: async function ({ status }, req) {
              if (!req.isAuth) {
                const error = new Error('권한 없디?');
                error.code = 401;
                throw error;
              }
          
              const user = await User.findById(req.userId);
              if (!user) {
                const error = new Error();
                error.status = 404;
                throw error;
              }
              user.status = status;
              await user.save();
          
              return { ...user._doc };
            },
      3. Front : .../Feed.js

        • statusUpdateHandler()

            statusUpdateHandler = (event) => {
              const graphqlQuery = {
                query: `
                mutation{
                  updateStatus(status: "${this.state.status}") {
                    status
                  }
                }
                `,
              };
              event.preventDefault();
              fetch('http://localhost:8080/graphql', {
                method: 'POST',
                headers: {
                  Authorization: 'Bearer ' + this.props.token,
                  'Content-Type': 'application/json',
                },
                body: JSON.stringify(graphqlQuery),
              })
                .then((res) => {
                  return res.json();
                })
                .then((resData) => {
                  if (resData.errors) {
                    throw new Error('Fetching status failed!');
                  }
                  console.log(resData);
                })
                .catch(this.catchError);
            };

GraphQL에서 동적으로 인자를 다루는 방법

  • 기존에 동적으로 인자를 바꾸기 위해 아래와 같이 백틱 `` $`사인을 사용하여 동적으로 인자를 넣어줬습니다.

    const graphqlQuery = {
          query: `
          {
          posts(page: ${page}){
            posts{
              _id
              title
              imageUrl
              content
              creator{
                name
              }
              createdAt
            }
            totalPosts
         }
        }
          `,
        };
  • 물론 이 방법도 가능하지만 GraphQL에서 권장하는 방법이 아닙니다.

  • 동적으로 인자를 넣는 방법

    const graphqlQuery = {
          query: `
    query FetchPosts($page: Int) {
          posts(page: $page) {
            posts{
              _id
              title
              imageUrl
              content
              creator{
                name
              }
              createdAt
            }
            totalPosts
         }
    }
          `,
        variables: {
            page: page
        }
        };
  • 다른 query도 인자 바꾸기

    const graphqlQuery = {
          query: `
          mutation UpdateUserStatus($userStatus: String){
            updateStatus(status: $userStatus) {
              status
            }
          }
          `,
          variables: {
            userStatus: this.state.status,
          },
        };
  • 참고로 BackEnd에서 타입 뒤에 ! 마크를 붙였다면 FrontEnd에서도 !마크를 붙어야한다.