백엔드 노드 서버 구축하기
버전이 다르기 때문에 소스가 강좌와 다를 수 있다.
버전
next: 13.0.4
antd: 5.0.1
소스 : https://github.dev/braverokmc79/node-bird-sns
제로초 소스 : https://github.com/ZeroCho/react-nodebird
55. 게시글 불러오기
강의 :
오류 시 주의 try ~ catch 에서 console.log(err); 확인 처리 인트값 확인 , user 정보 값 확인
백엔드 : routes/posts.js
비밀번호 제외 user 정보 불러오기
const express = require('express'); const { Post, Image, User, Comment } = require('../models'); const router = express.Router(); //GET /posts router.get('/:limit', async (req, res, next) => { try { console.log("req.params : ", req.params.limit); const posts = await Post.findAll({ // where: { id: lastId }, limit: 10, order: [ ['createdAt', 'DESC'], [Comment, 'createdAt', 'DESC'] ], offsset: parseInt(req.params.limit), //21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1 include: [{ model: User, attributes: ['id', 'nickname'] }, { model: Image }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'] }] } ] }); res.status(200).json(posts); } catch (error) { console.error("posts error : ", error); next(error); } }); module.exports = router;
프론트 엔드
saga/post.js
import { all, fork, put, throttle, delay, takeLatest, call } from 'redux-saga/effects'; import axios from 'axios'; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, generateDummyPost } from '../reducers/post' import { ADD_POST_TO_ME, REMOVE_POST_OF_ME } from '../reducers/user'; let scrollCount = 0 //무한 스크롤 function loadPostsAPI(data) { console.log("DB 데이터 가져오기 :"); return axios.get(`/posts/${data}`); } function* loadPosts(action) { try { yield delay(1000); scrollCount += 10; const result = yield call(loadPostsAPI, scrollCount); console.log(" 2무한 스크롤 result: ", scrollCount, result.data); // console.log(" 3무한 스크롤 generateDummyPost: ", generateDummyPost(10)); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data }); // yield put({ // type: LOAD_POSTS_SUCCESS, // data: generateDummyPost(10) // }); // if (result !== null) { // yield put({ // type: LOAD_POSTS_SUCCESS, // data: result.data // }); // } else { // yield put({ // type: LOAD_POSTS_SUCCESS, // data: generateDummyPost(10) // }); // } } catch (err) { console.log(err); yield put({ type: LOAD_POSTS_FAILURE, error: err.response.data }); } } function* watchLoadPosts() { // yield takeLatest(LOAD_POSTS_REQUEST, loadPosts); yield throttle(3000, LOAD_POSTS_REQUEST, loadPosts); } ~
56. 게시글 좋아요
강의 :
프론트 엔드
reducers/post.js
import shortid from 'shortid'; import produce from 'immer'; import { faker } from '@faker-js/faker'; faker.seed(123); export const initialState = { mainPosts: [], imagePaths: [], hasMorePosts: true, unlikePostLoading: false, unlikePostDone: false, unlikePostError: null, likePostLoading: false, likePostDone: false, likePostError: null, ~ export const LIKE_POST_REQUEST = 'LIKE_POST_REQUEST'; export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS'; export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE'; export const UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST'; export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS'; export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE'; ~ //LIKE case LIKE_POST_REQUEST: draft.likePostLoading = true; draft.likePostDone = false; draft.likePostError = null; break; case LIKE_POST_SUCCESS: { const post = draft.mainPosts.find((v) => v.id === parseInt(action.data.PostId)); post.Likers.push({ id: action.data.UserId }); draft.likePostLoading = false; draft.likePostDone = true; break; } case LIKE_POST_FAILURE: draft.likePostLoading = false; draft.likePostError = action.error; break; //UN LIKE case UNLIKE_POST_REQUEST: draft.unlikePostLoading = true; draft.unlikePostDone = false; draft.unlikePostError = null; break; case UNLIKE_POST_SUCCESS: { const post = draft.mainPosts.find((v) => v.id === parseInt(action.data.PostId)); post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId); draft.unlikePostLoading = false; draft.unlikePostDone = true; break; } case UNLIKE_POST_FAILURE: draft.unlikePostLoading = false; draft.unlikePostError = action.error; break; ~
saga/post.js
import { all, fork, put, throttle, delay, takeLatest, call } from 'redux-saga/effects'; import axios from 'axios'; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, generateDummyPost, LIKE_POST_REQUEST, LIKE_POST_SUCCESS, LIKE_POST_FAILURE, UNLIKE_POST_REQUEST, UNLIKE_POST_SUCCESS, UNLIKE_POST_FAILURE, } from '../reducers/post' import { ADD_POST_TO_ME, REMOVE_POST_OF_ME } from '../reducers/user'; //Like function likePostAPI(data) { return axios.patch(`/post/${data}/like`); } function* likePost(action) { try { const result = yield call(likePostAPI, action.data); yield put({ type: LIKE_POST_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: LIKE_POST_FAILURE, error: err.response.data }); } } function* watchLikePost() { yield takeLatest(LIKE_POST_REQUEST, likePost); } //UN Like function unlikePostAPI(data) { return axios.delete(`/post/${data}/like`); } function* unlikePost(action) { try { const result = yield call(unlikePostAPI, action.data); yield put({ type: UNLIKE_POST_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: UNLIKE_POST_FAILURE, error: err.response.data }); } } function* watchUnlikePost() { yield takeLatest(UNLIKE_POST_REQUEST, unlikePost); } ~ ~ export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchAddComment), fork(watchRemovePost), fork(watchLoadPosts), fork(watchLikePost), fork(watchUnlikePost) ]); }
components/PostCard.js
import React, { useState, useCallback } from 'react'; import { Card, Button, Avatar, Image, Popover, List, Space } from 'antd'; import { Comment } from '@ant-design/compatible'; import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone } from '@ant-design/icons'; import PropTypes from 'prop-types' import { useSelector, useDispatch } from 'react-redux'; import PostImages from './PostImages'; import CommentForm from './CommentForm'; import { createGlobalStyle } from 'styled-components'; import PostCardContent from './PostCardContent'; import { REMOVE_POST_REQUEST } from '../reducers/post'; import FollowButton from './FollowButton'; import { LIKE_POST_REQUEST, UNLIKE_POST_REQUEST } from '../reducers/post'; const Global = createGlobalStyle` .ant-card-actions{ background: #eeeeee !important; } `; const PostCard = ({ post }) => { const dispatch = useDispatch(); const { removePostLoading } = useSelector((state) => state.post); const [commentFormOpened, setCommentFormOpened] = useState(false); const id = useSelector((state) => state.user.me?.id); const liked = post.Likers.find((v) => v.id === id); const onLike = useCallback(() => { dispatch({ type: LIKE_POST_REQUEST, data: post.id }) }, []); const onUnlike = useCallback(() => { dispatch({ type: UNLIKE_POST_REQUEST, data: post.id }) }, []); ~
백엔드
시퀄라이즈는 모델을 만들면 다음과 같은 함수를 제공해 준다.
post.addLikers , post.removeLikers
Post.associate = (db) => { db.Post.belongsTo(db.User); //post.addUser post.getUser , post.setUser db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); //post.addHashtags db.Post.hasMany(db.Comment); //post.addComments, post.getComments db.Post.hasMany(db.Image); //post.addImages . post.getImages db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' }); //post.addLikers , post.removeLikers db.Post.belongsTo(db.Post, { as: "Retweet" }); //post.addReteet }
댓글 등록 및 불러올시에 좋아요 누른 사람 컬럼값 가져오기 Likers
}, { model: User, //좋아요 누른 사람 as: 'Likers', attributes: ['id'] } ]
routes/post.js
const express = require('express'); const { Post, User, Image, Comment } = require('../models'); const { isLoggedIn, isNotLoggedIn } = require('./middlewares'); const router = express.Router(); //** passport 특성상 로그인 하면, 라우터 접근시 항상 deserializeUser 실행해서 req.user 를 만든다. req.user.id로 접근해서 정보를 가져올 수 있다. //POST /post router.post('/', isLoggedIn, async (req, res, next) => { try { const post = await Post.create({ content: req.body.content, UserId: req.user.id }); const fullPost = await Post.findOne({ where: { id: post.id }, include: [{ model: Image, }, { model: Comment, include: [{ model: User, //댓글 작성자 attributes: ['id', 'nickname'] }] }, { model: User, //게시글 작성자 attributes: ['id', 'nickname'] }, { model: User, //좋아요 누른 사람 as: 'Likers', attributes: ['id'] } ] }) res.status(201).json(fullPost); } catch (error) { console.error(" Post 에러 : ", error); next(error); } }); //POST 댓글 /post router.post('/:postId/comment', isLoggedIn, async (req, res, next) => { //POST /post/1/comment try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("존재하지 않는 게시글입니다."); } const comment = await Comment.create({ content: req.body.content, PostId: req.params.postId, UserId: req.user.id }); const fullComment = await Comment.findOne({ where: { id: comment.id }, include: [{ model: User, attributes: ['id', 'nickname'] }] }) //console.log(" 댓글 작성 완료 : ", comment); res.status(201).json(fullComment); } catch (error) { console.error(" comment 에러 : ", error); next(error); } }); router.patch('/:postId/like', async (req, res, next) => { //PATcH /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.addLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }) } catch (error) { console.error(error); next(error); } }); router.delete('/:postId/like', async (req, res, next) => { //DELETE /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.removeLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }) } catch (error) { console.error(error); next(error); } }) //DELETE /post router.delete('/', (req, res) => { res.json({ id: 1 }) }); module.exports = router;
57. 게시글 제거 / 닉네임 변경
강의 :
백엔드
routes/post.js
~ //게시글 삭제 DELETE /post/10 router.delete('/:postId', isLoggedIn, async (req, res, next) => { console.log(" 게시글 삭제 : ", req.params.postId); console.log(" 패스포트 정보: ", req.user.id); try { await Post.destroy({ where: { id: req.params.postId, UserId: req.user.id }, }); res.status(200).json({ PostId: parseInt(req.params.postId, 10) }) } catch (error) { console.error(error); next(error); } }); ~
routes/user.js
~ router.patch('/nickname', isLoggedIn, async (req, res, next) => { try { await User.update({ nickname: req.body.nickname, }, { where: { id: req.user.id } }); res.status(200).json({ nickname: req.body.nickname }) } catch (error) { console.error(error); next(error); } }) module.exports = router;
프론트엔드
components/NicknameEditForm.js
import React, { useMemo, useCallback } from 'react'; import { Form, Input } from 'antd'; import { useDispatch, useSelector } from 'react-redux'; import { CHANGE_NICKNAME_REQUEST } from './../reducers/user'; import useInput from '../hooks/useInput'; const NicknameEditForm = () => { const { me } = useSelector((state) => state.user); const [nickname, onChangeNickname] = useInput(me?.nickname || ''); const dispatch = useDispatch(); const onSubmit = useCallback(() => { dispatch({ type: CHANGE_NICKNAME_REQUEST, data: nickname }) }, [nickname]); const style = useMemo(() => ({ marginBottom: '20px', border: "1px solid #d9d9d9", padding: "20px" }, [])); return ( <Form style={style} > <Input.Search value={nickname} onChange={onChangeNickname} addonBefore="닉네임" enterButton="수정" onSearch={onSubmit} /> </Form> ); }; export default NicknameEditForm;
reducer/user.js
~ //닉네임변경 case CHANGE_NICKNAME_REQUEST: draft.changeNicknameLoading = true; draft.changeNicknameDone = false; draft.changeNicknameError = null; break; case CHANGE_NICKNAME_SUCCESS: draft.me.nickname = action.data.nickname; draft.changeNicknameLoading = false; draft.changeNicknameDone = true; break; case CHANGE_NICKNAME_FAILURE: draft.changeNicknameLoading = false; draft.changeNicknameError = action.error; break; ~
saga/user.js
import { all, fork, put, takeLatest, delay, call, throttle } from 'redux-saga/effects'; import axios from 'axios'; import { LOG_IN_REQUEST, LOG_IN_SUCCESS, LOG_IN_FAILURE, LOG_OUT_REQUEST, LOG_OUT_SUCCESS, LOG_OUT_FAILURE, SIGN_UP_REQUEST, SIGN_UP_SUCCESS, SIGN_UP_FAILURE, FOLLOW_REQUEST, FOLLOW_SUCCESS, FOLLOW_FAILURE, UNFOLLOW_REQUEST, UNFOLLOW_SUCCESS, UNFOLLOW_FAILURE, LOAD_MY_INFO_REQUEST, LOAD_MY_INFO_SUCCESS, LOAD_MY_INFO_FAILURE, CHANGE_NICKNAME_REQUEST, CHANGE_NICKNAME_SUCCESS, CHANGE_NICKNAME_FAILURE } from '../reducers/user'; //닉네임 변경 function changeNicknameAPI(data) { return axios.patch('/user/nickname', {nickname:data}); } function* changeNickname(action) { try { const result = yield call(changeNicknameAPI, action.data) yield put({ type: CHANGE_NICKNAME_SUCCESS, data: result.data }); } catch (err) { yield put({ type: CHANGE_NICKNAME_FAILURE, error: err.response.data }); } } function* watchChangeNickname() { yield takeLatest(CHANGE_NICKNAME_REQUEST, changeNickname); } ~
saga/post.js
~ //게시글 삭제 function removePostAPI(data) { return axios.delete(`/post/${data}`, data); } function* removePost(action) { try { console.log("게시글 삭제 :", action.data); const result = yield call(removePostAPI, action.data); yield put({ type: REMOVE_POST_SUCCESS, data: result.data }); yield put({ type: REMOVE_POST_OF_ME, data: action.data }) } catch (err) { console.log(err); yield put({ type: REMOVE_POST_FAILURE, error: err.response.data }); } } function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost); } ~
58. 팔로우/언팔로우
강의 :
백엔드 :
routes/users.js
~ //팔로우 추가 router.patch('/:userId/follow', isLoggedIn, async (req, res, next) => { //PATCH /user/1/follow try { const user = await User.findOne({ where: { id: req.params.userId } }); if (!user) { res.status(403).send('없은 사람을 팔로우하려고 하시네요?'); } await user.addFollowers(req.user.id) res.status(200).json({ UserId: parseInt(req.params.userId, 10) }) } catch (error) { console.error(error); next(error); } }); //팔로우 취소 ==> 팔로잉 제거 router.delete('/:userId/follow', isLoggedIn, async (req, res, next) => { //DELETE /user/1/follow try { const user = await User.findOne({ where: { id: req.params.userId } }); console.log(" 팔로잉 제거 : ", req.params.userId); if (!user) { res.status(403).send('없은 사람을 제거하려고 하시네요?'); } //const me = await User.findOne({ where: { id: req.user.id } }); //제거 하려는 아이디 req.params.userId 이다. //현재 접속 한 나의 아이디는 패스포트로 req.user.id 이다. //따라서, and 조건으로 findOne me 가져오고 으로 제거하려는 req.params.userId 넣으면 된다. //DELETE FROM `Follow` WHERE `FollowerId` = 2 AND `FollowingId` IN (1) //await me.removeFollowings(req.params.userId); //=> 반대로 팔로워 제거로 해도 된다. await user.removeFollowers(req.user.id); res.status(200).json({ UserId: parseInt(req.params.userId, 10) }) } catch (error) { console.error(error); next(error); } }); //팔로우 차단 => 팔로워 제거 router.delete('/follower/:userId', isLoggedIn, async (req, res, next) => { //DELETE /user/1/follow try { console.log(" 팔로워 제거 : ", req.params.userId); const user = await User.findOne({ where: { id: req.params.userId } }); if (!user) { res.status(403).send('없은 사람을 팔로우차단 하려고 하시네요?'); } // const me = await User.findOne({ where: { id: req.user.id } }); // await me.removeFollowers(req.params.userId); //=> 반대로 팔로잉 제거로 한번만 실행 처리 await user.removeFollowings(req.user.id); res.status(200).json({ UserId: parseInt(req.params.userId, 10) }) } catch (error) { console.error(error); next(error); } }); //팔로워 불러워기 router.get('/followers', isLoggedIn, async (req, res, next) => { //get /user/followers try { //패스포트에 로그인한 user id 값 : req.user.id const user = await User.findOne({ where: { id: req.user.id } }); const followers = await user.getFollowers(); res.status(200).json(followers) } catch (error) { console.error(error); next(error); } }); // 팔로잉 불러워기 시퀄라이즈에서 다음과 같이 Followings 처리를 해서 getFollowings 적용 됨 router.get('/followings', isLoggedIn, async (req, res, next) => { //get /user/followings try { const user = await User.findOne({ where: { id: req.user.id } }); const followings = await user.getFollowings(); res.status(200).json(followings); } catch (error) { console.error(error); next(error); } }); module.exports = router;
프론트 엔드
components/FollowButton.js
~ if (post.User.id === me.id) { return null; } ~
components/FollowList.js
import React from 'react'; import PropTypes from 'prop-types' import { Button, Card, List } from 'antd'; import { StopOutlined } from '@ant-design/icons' import { useDispatch } from 'react-redux'; import { REMOVE_FOLLOW_REQUEST, UNFOLLOW_REQUEST } from '../reducers/user'; const FollowList = ({ header, data }) => { const dispatch = useDispatch(); //반목문에서 고차함수 사용으로 파라미터 값을 전달시킬 수 있다 (고차 함수(Higher order function)는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수) const onCancle = (id) => () => { if (header === '팔로잉') { dispatch({ type: UNFOLLOW_REQUEST, data: id }) } else if (header === '팔로워') { dispatch({ type: REMOVE_FOLLOW_REQUEST, data: id }) } }; return ( <List style={{ marginBottom: 20 }} grid={{ gutter: 4, xs: 2, md: 3 }} size="small" header={<div>{header}</div>} loadMore={<div style={{ textAlign: 'center', margin: '10px 0' }}><Button>더 보기</Button></div>} bordered dataSource={data} renderItem={(item) => ( <List.Item style={{ marginTop: 20 }}> <Card actions={[<StopOutlined key="stop" onClick={onCancle(item.id)} />]} > <Card.Meta description={item.nickname} /> </Card> </List.Item > ) } > </List > ); }; FollowList.propTypes = { header: PropTypes.string.isRequired, data: PropTypes.array.isRequired } export default FollowList;
pages/profile.js
import React, { useEffect, useCallback } from 'react'; import AppLayout from './../components/AppLayout'; import Head from 'next/head'; import { useDispatch, useSelector } from 'react-redux'; import Router from 'next/router'; import FollowList from './../components/FollowList'; import NicknameEditForm from './../components/NicknameEditForm'; import { LOAD_FOLLOWERS_REQUEST, LOAD_FOLLOWINGS_REQUEST } from '../reducers/user'; const Profile = () => { const { me } = useSelector((state) => state.user); const dispatch = useDispatch(); useEffect(() => { dispatch({ type: LOAD_FOLLOWERS_REQUEST }); dispatch({ type: LOAD_FOLLOWINGS_REQUEST }); }, [LOAD_FOLLOWERS_REQUEST, LOAD_FOLLOWINGS_REQUEST]) ~
reducers/users.js
import produce from "immer"; export const initialState = { unfollowLoading: false,//언팔로우 시도중 unfollowDone: false, unfollowError: null, removeFollowLoading: false,//팔로워 차단 시도중 removeFollowDone: false, removeFollowError: null, followLoading: false,//팔로우 시도중 followDone: false, followError: null, logInLoading: false,//로그인 시도중 logInDone: false, logInError: null, logOutLoading: false, //로그아웃 시도중 logOutDone: false, logOutError: null, signUpLoading: false, //회원가입 시도중 signUpDone: false, signUpError: null, changeNicknameLoading: false, //닉네임 변경 changeNicknameDone: false, changeNicknameError: null, loadMyInfoLoading: false, //브라우저 새로고침시 유저정보 가져오기 loadMyInfoDone: false, loadMyInfoError: null, loadFollowersLoading: false, //팔로워 목록 가져오기 loadFollowersDone: false, loadFollowersError: null, loadFollowingsLoading: false, //팔로잉 목록 가져오기 loadFollowingsDone: false, loadFollowingsError: null, me: null, signUpdata: {}, loginData: {} } export const LOG_IN_REQUEST = "LOG_IN_REQUEST"; export const LOG_IN_SUCCESS = "LOG_IN_SUCCESS"; export const LOG_IN_FAILURE = "LOG_IN_FAILURE"; export const LOG_OUT_REQUEST = "LOG_OUT_REQUEST"; export const LOG_OUT_SUCCESS = "LOG_OUT_SUCCESS"; export const LOG_OUT_FAILURE = "LOG_OUT_FAILURE"; export const SIGN_UP_REQUEST = "SIGN_UP_REQUEST"; export const SIGN_UP_SUCCESS = "SIGN_UP_SUCCESS"; export const SIGN_UP_FAILURE = "SIGN_UP_FAILURE"; export const CHANGE_NICKNAME_REQUEST = "CHANGE_NICKNAME_REQUEST"; export const CHANGE_NICKNAME_SUCCESS = "CHANGE_NICKNAME_SUCCESS"; export const CHANGE_NICKNAME_FAILURE = "CHANGE_NICKNAME_FAILURE"; export const FOLLOW_REQUEST = "FOLLOW_REQUEST"; export const FOLLOW_SUCCESS = "FOLLOW_SUCCESS"; export const FOLLOW_FAILURE = "FOLLOW_FAILURE"; //팔로잉 제거 export const UNFOLLOW_REQUEST = "UNFOLLOW_REQUEST"; export const UNFOLLOW_SUCCESS = "UNFOLLOW_SUCCESS"; export const UNFOLLOW_FAILURE = "UNFOLLOW_FAILURE"; //팔로워 차단 = > 팔로워 제거 export const REMOVE_FOLLOW_REQUEST = "REMOVE_FOLLOW_REQUEST"; export const REMOVE_FOLLOW_SUCCESS = "REMOVE_FOLLOW_SUCCESS"; export const REMOVE_FOLLOW_FAILURE = "REMOVE_FOLLOW_FAILURE"; export const ADD_POST_TO_ME = 'ADD_POST_TO_ME'; export const REMOVE_POST_OF_ME = 'REMOVE_POST_OF_ME'; export const LOAD_MY_INFO_REQUEST = "LOAD_MY_INFO_REQUEST"; export const LOAD_MY_INFO_SUCCESS = "LOAD_MY_INFO_SUCCESS"; export const LOAD_MY_INFO_FAILURE = "LOAD_MY_INFO_FAILURE"; export const LOAD_FOLLOWERS_REQUEST = "LOAD_FOLLOWERS_REQUEST"; export const LOAD_FOLLOWERS_SUCCESS = "LOAD_FOLLOWERS_SUCCESS"; export const LOAD_FOLLOWERS_FAILURE = "LOAD_FOLLOWERS_FAILURE"; export const LOAD_FOLLOWINGS_REQUEST = "LOAD_FOLLOWINGS_REQUEST"; export const LOAD_FOLLOWINGS_SUCCESS = "LOAD_FOLLOWINGS_SUCCESS"; export const LOAD_FOLLOWINGS_FAILURE = "LOAD_FOLLOWINGS_FAILURE"; export const loginRequestAction = (data) => { return { type: LOG_IN_REQUEST, data } } export const logoutRequestAction = () => { return { type: LOG_OUT_REQUEST } } const reducer = (state = initialState, action) => produce(state, (draft) => { switch (action.type) { //팔로워 목록 가져오기 case LOAD_FOLLOWERS_REQUEST: draft.loadFollowersLoading = true; draft.loadFollowersDone = false; draft.loadFollowersError = null; break; case LOAD_FOLLOWERS_SUCCESS: draft.loadFollowersLoading = false; draft.loadFollowersDone = true; draft.me.Followers = action.data; break; case LOAD_FOLLOWERS_FAILURE: draft.loadFollowersLoading = false; draft.loadFollowersError = action.error; break; //팔로잉 목록 가져오기 case LOAD_FOLLOWINGS_REQUEST: draft.loadFollowingsLoading = true; draft.loadFollowingsDone = false; draft.loadFollowingsError = null; break; case LOAD_FOLLOWINGS_SUCCESS: draft.loadFollowingsLoading = false; draft.loadFollowingsDone = true; draft.me.Followings = action.data break; case LOAD_FOLLOWINGS_FAILURE: draft.loadFollowingsLoading = false; draft.loadFollowingsError = action.error; break; //브라우저 새로고침시 유저정보 가져오기 case LOAD_MY_INFO_REQUEST: draft.loadMyInfoLoading = true; draft.loadMyInfoDone = false; draft.loadMyInfoError = null; break; case LOAD_MY_INFO_SUCCESS: draft.loadMyInfoLoading = false; draft.loadMyInfoDone = true; draft.me = action.data break; case LOAD_MY_INFO_FAILURE: draft.loadMyInfoLoading = false; draft.loadMyInfoError = action.error; break; //팔로우 case FOLLOW_REQUEST: draft.followLoading = true; draft.followDone = false; draft.followError = null; break; case FOLLOW_SUCCESS: draft.followLoading = false; draft.followDone = true; draft.me.Followings.push({ id: action.data.UserId }); break; case FOLLOW_FAILURE: draft.followLoading = false; draft.followError = action.error; break; //언팔로우 => 팔로잉 제거 case UNFOLLOW_REQUEST: draft.unfollowLoading = true; draft.unfollowDone = false; draft.unfollowError = null; break; case UNFOLLOW_SUCCESS: draft.unfollowLoading = false; draft.unfollowDone = true; draft.me.Followings = draft.me.Followings.filter((v) => v.id !== action.data.UserId); break; case UNFOLLOW_FAILURE: draft.unfollowLoading = false; draft.unfollowError = action.error; break; //팔로워 차단 => 팔로워 제거 case REMOVE_FOLLOW_REQUEST: draft.removeFollowLoading = true; draft.removeFollowDone = false; draft.removeFollowError = null; break; case REMOVE_FOLLOW_SUCCESS: draft.removeFollowLoading = false; draft.removeFollowDone = true; draft.me.Followers = draft.me.Followers.filter((v) => v.id !== action.data.UserId); break; case REMOVE_FOLLOW_FAILURE: draft.removeFollowLoading = false; draft.removeFollowError = action.error; break; //로그인 case LOG_IN_REQUEST: draft.logInLoading = true; draft.logInDone = false; draft.logInError = null; break; case LOG_IN_SUCCESS: draft.logInLoading = false; draft.logInDone = true; draft.me = action.data; break; case LOG_IN_FAILURE: draft.logInLoading = false; draft.logInError = action.error; break; //로그 아웃 case LOG_OUT_REQUEST: draft.logOutLoading = true; draft.logOutDone = false; draft.logOutError = null; break; case LOG_OUT_SUCCESS: draft.logOutLoading = false; draft.logOutDone = true; draft.me = null; break; case LOG_OUT_FAILURE: draft.logOutLoading = false; draft.logOutError = action.error; draft; //회원가입 case SIGN_UP_REQUEST: console.log(" 회원 가입 리듀서 "); draft.signUpLoading = true; draft.signUpDone = false; draft.signUpError = null; break; case SIGN_UP_SUCCESS: draft.signUpLoading = false; draft.signUpDone = true; break; case SIGN_UP_FAILURE: draft.signUpLoading = false; draft.signUpError = action.error; break; //닉네임변경 case CHANGE_NICKNAME_REQUEST: draft.changeNicknameLoading = true; draft.changeNicknameDone = false; draft.changeNicknameError = null; break; case CHANGE_NICKNAME_SUCCESS: draft.me.nickname = action.data.nickname; draft.changeNicknameLoading = false; draft.changeNicknameDone = true; break; case CHANGE_NICKNAME_FAILURE: draft.changeNicknameLoading = false; draft.changeNicknameError = action.error; break; //게시글 수 숫자 증가 case ADD_POST_TO_ME: draft.me.Posts.unshift({ id: action.data }); break; // return { // ...state, // me: { // ...state.me, // Posts: [{ id: action.data }, ...state.me.Posts] // } // }; //게시글 수 숫자 감소 case REMOVE_POST_OF_ME: draft.me.Posts = draft.me.Posts.filter((v) => v.id !== action.data); break; // return { // ...state, // me: { // ...state.me, // Posts: state.me.Posts.filter((v) => v.id !== action.data), // } // } default: break; } }); export default reducer;
saga/user.js
import { all, fork, put, takeLatest, delay, call, throttle } from 'redux-saga/effects'; import axios from 'axios'; import { LOG_IN_REQUEST, LOG_IN_SUCCESS, LOG_IN_FAILURE, LOG_OUT_REQUEST, LOG_OUT_SUCCESS, LOG_OUT_FAILURE, SIGN_UP_REQUEST, SIGN_UP_SUCCESS, SIGN_UP_FAILURE, FOLLOW_REQUEST, FOLLOW_SUCCESS, FOLLOW_FAILURE, UNFOLLOW_REQUEST, UNFOLLOW_SUCCESS, UNFOLLOW_FAILURE, LOAD_MY_INFO_REQUEST, LOAD_MY_INFO_SUCCESS, LOAD_MY_INFO_FAILURE, CHANGE_NICKNAME_REQUEST, CHANGE_NICKNAME_SUCCESS, CHANGE_NICKNAME_FAILURE, LOAD_FOLLOWERS_SUCCESS, LOAD_FOLLOWERS_FAILURE, LOAD_FOLLOWERS_REQUEST, LOAD_FOLLOWINGS_REQUEST, LOAD_FOLLOWINGS_SUCCESS, LOAD_FOLLOWINGS_FAILURE, REMOVE_FOLLOW_REQUEST, REMOVE_FOLLOW_SUCCESS, REMOVE_FOLLOW_FAILURE } from '../reducers/user'; //팔로워 목록 가져오기 function loadFollowersAPI(data) { return axios.get('/user/followers', data); } function* loadFollowers(action) { try { const result = yield call(loadFollowersAPI, action.data) yield put({ type: LOAD_FOLLOWERS_SUCCESS, data: result.data }); } catch (err) { yield put({ type: LOAD_FOLLOWERS_FAILURE, error: err.response.data }); } } function* watchLoadFollowers() { yield takeLatest(LOAD_FOLLOWERS_REQUEST, loadFollowers); } //팔로잉 목록 가져오기 function loadFollowingsAPI(data) { return axios.get('/user/followings', data); } function* loadFollowings(action) { try { const result = yield call(loadFollowingsAPI, action.data) yield put({ type: LOAD_FOLLOWINGS_SUCCESS, data: result.data }); } catch (err) { yield put({ type: LOAD_FOLLOWINGS_FAILURE, error: err.response.data }); } } function* watchLoadFollowings() { yield takeLatest(LOAD_FOLLOWINGS_REQUEST, loadFollowings); } //닉네임 변경 function changeNicknameAPI(data) { return axios.patch('/user/nickname', { nickname: data }); } function* changeNickname(action) { try { const result = yield call(changeNicknameAPI, action.data) yield put({ type: CHANGE_NICKNAME_SUCCESS, data: result.data }); } catch (err) { yield put({ type: CHANGE_NICKNAME_FAILURE, error: err.response.data }); } } function* watchChangeNickname() { yield takeLatest(CHANGE_NICKNAME_REQUEST, changeNickname); } //브라우저 새로고침시 유저정보 가져오기 function loadMyInfoAPI() { return axios.get('/user'); } function* loadMyInfo(action) { try { const result = yield call(loadMyInfoAPI) yield put({ type: LOAD_MY_INFO_SUCCESS, data: result.data }); } catch (err) { yield put({ type: LOAD_MY_INFO_FAILURE, error: err.response.data }); } } function* watchLoadMyInfo() { yield takeLatest(LOAD_MY_INFO_REQUEST, loadMyInfo); } //팔로우 function followAPI(data) { return axios.patch(`/user/${data}/follow`); } function* follow(action) { try { const result = yield call(followAPI, action.data); yield put({ type: FOLLOW_SUCCESS, data: result.data }); } catch (err) { yield put({ type: FOLLOW_FAILURE, error: err.response.data }); } } function* watchFollow() { yield takeLatest(FOLLOW_REQUEST, follow); } //언팔로우 => 팔로잉 제거 function unfollowAPI(data) { return axios.delete(`/user/${data}/follow`); } function* unfollow(action) { try { const result = yield call(unfollowAPI, action.data); yield put({ type: UNFOLLOW_SUCCESS, data: result.data }); } catch (err) { yield put({ type: UNFOLLOW_FAILURE, error: err.response.data }); } } function* watchUnFollow() { yield takeLatest(UNFOLLOW_REQUEST, unfollow); } //팔로워 차단 => 팔로워 제거 function removeFollowAPI(data) { return axios.delete(`/user/follower/${data}`); } function* removeFollow(action) { try { const result = yield call(removeFollowAPI, action.data); yield put({ type: REMOVE_FOLLOW_SUCCESS, data: result.data }); } catch (err) { yield put({ type: REMOVE_FOLLOW_FAILURE, error: err.response.data }); } } function* watchRemoveFollow() { yield takeLatest(REMOVE_FOLLOW_REQUEST, removeFollow); } //1-1.로그인 처리 function logInAPI(data) { return axios.post('/user/login', data); } // const l = login({ type: "LOG_IN_REQUEST", data: { id: 'test@gmail.com' } }); // l.next(); // l.next(); //1-2.로그인 처리 function* login(action) { //put 을 dispatch //call 은 동기 함수 호출 //fork 는 비동기 함수 호출 try { const result = yield call(logInAPI, action.data); //yield delay(1000); yield put({ type: LOG_IN_SUCCESS, data: result.data }); console.log("5. 미들웨어로 사가 로그인 호출 result.data : ", result.data); } catch (err) { yield put({ type: LOG_IN_FAILURE, error: err.response.data }); } } //1-3 로그인 처리 function* watchLogIn() { //LOG_IN 실행 될때 까지 기다리겠다. console.log("2. watchLogIn "); yield takeLatest(LOG_IN_REQUEST, login); } //2-1.로그아웃 처리 function logOutAPI() { console.log(" 로그 아웃 !"); return axios.post('/user/logout'); } //2-2.로그아웃 처리 function* logOut(action) { //put 을 dispatch //call 은 동기 함수 호출 //fork 는 비동기 함수 호출 try { yield call(logOutAPI); yield put({ type: LOG_OUT_SUCCESS }); } catch (err) { yield put({ type: LOG_OUT_FAILURE, error: err.response.data }); } } //2-3 로그아웃 처리 function* watchLogOut() { yield takeLatest(LOG_OUT_REQUEST, logOut); } //3. 회원가입 function signUpAPI(data) { return axios.post('/user', data); } function* signUp(action) { try { const result = yield call(signUpAPI, action.data); //yield delay(1000); console.log(" 회원 가입 : ", result); yield put({ type: SIGN_UP_SUCCESS, }); } catch (err) { console.log(" 사가 에러 : ", err.response.data); yield put({ type: SIGN_UP_FAILURE, error: err.response.data }); } } function* watchSignUp() { yield takeLatest(SIGN_UP_REQUEST, signUp); } //all 하면 한방에 배열로 적은 함수들이 실행처리 된다. //fork , call 로 실행한다. all 은 fork 나 call 을 동시에 실행시키도록 한다. //call 은 동기 함수 호출 //fork 는 비동기 함수 호출 export default function* userSaga() { yield all([ fork(watchLoadFollowers), fork(watchLoadFollowings), fork(watchLogIn), fork(watchLogOut), fork(watchFollow), fork(watchUnFollow), fork(watchSignUp), fork(watchLoadMyInfo), fork(watchChangeNickname), fork(watchRemoveFollow), ]) }
댓글 ( 4)
댓글 남기기