• 카테고리

    질문 & 답변
  • 세부 분야

    풀스택

  • 해결 여부

    해결됨

게시글 좋아요 누르기 및 좋아요 취소하기 질문입니다

22.05.18 05:09 작성 조회수 642

1

계속 고민하다 이렇게 질문을 남깁니다.

우선 제가 시도한 방법부터 알려드리자면

db.User.belongsToMany(db.Post, { through: 'Like'});
db.Post.belongsToMany(db.User, { through: 'Like', as: 'Liker' });

User과 Post모델 사이에 M:N관계를 정립합니다

passport.deserializeUser((id, done) => {
        User.findOne({
            where: { id },
            include: [{
                model: User,
                attributes: ['id', 'nick'],
                as: 'Followers',
            }, {
                model: User,
                attributes: ['id', 'nick'],
                as: 'Followings',
            }, {
                model: User,
                attributes: ['id', 'nick'],
                as: 'Liker',
            }],
        })
        .then(user => done(null, user))
        .catch(err => done(err));
  });

그 다음에 세션에 로그인하면 팔로우/팔로잉 목록과 함께 좋아요 목록을 req.user에 저장합니다

res.locals.LikeIdList = req.user ? req.user.Liker.map(l => l.id) : [];

page.js 에 req.user로 받은 Liker배열을 id만 따로 배열로 만들어 전역변수 LikeIdList에 넣습니다

{% if not LikeIdList.includes(twit.User.id) and twit.User.id !== user.id %}
  <button class="like">좋아요</button>
{% elif LikeIdList.includes(twit.User.id) and twit.User.id !== user.id %}
  <button class="not-like">좋아요취소</button>
{% endif %}

그 후 main.html에 이미지div밑에 좋아요와 좋아요 취소 버튼을 만들었습니다.

router.post('/:id/like', async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        await post.addLiker(req.user.id);
        res.send('succes');
    } catch (error) {
        console.log(error);
        next(error);
    }
})

router.delete('/:id/not-like', async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        await post.removeLiker(req.user.id);
        res.send('succes');
    } catch (error) {
        console.log(error);
        next(error);
    }
})

마지막으로 이벤트리스너를 활용하여 서버로 url을 보내고 라우터에서 처리하는 코드를 만들었습니다.

 

그 후 코드를 실행하고 로그인을 하고 나니

User is associated to User multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.

로그인 하자마자 에러가 뜹니다.

해석해보자면 User과 User이 여러번 열결이 되니 include를 쓸 때 'as'를 써라입니다

하지만 위에 보시다시피 전 as를 확실히 썼습니다.

 

여기서 네트워크를 봤습니다

  1. 요청 URL:
    http://localhost:8001/
  2. 요청 메서드:
    GET
  3. 상태 코드:
    500 Internal Server Error
  4. 원격 주소:
    [::1]:8001
  5. 리퍼러 정책:
    strict-origin-when-cross-origin

GET / 라우터에 에러가 발생했구나 싶어서 코드를 살펴보니

router.get('/', async (req, res, next) => {
    try {
    const posts = await Post.findAll({
        include: {
            model: User,
            attributes: ['id', 'nick'],
        },
        order: [['createdAt', 'DESC']],
    });
    console.log(posts);
    res.render('main', {
        title: 'NodeBird',
        twits: posts,
    });
    } catch (err) {
        console.log(err)
        next(err);
    }
});

문제가 없어 보입니다.

사실 이전까지 아무런 문제가 없는 코드라서 제 머릿속을 점점 미궁으로 빠집니다.

아마 저 include코드에서 뭔가 발생한거 같은데 제 짧은 지식으로는 아무리 봐도 문제가 없어 보입니다.

 

제 방식이 어디가 잘못된건지 알려주세요

 

 

 

 

 

답변 1

답변을 작성해보세요.

0

user랑 user는 associate되지 않았는데 user include user as likers 해서 그렇습니다. user는 post와 associate입니다. 게시글을 불러올 때 likers를 불러와서 post.Likers에 내 아이디가 있는지 찾아야합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

그럼 User가 아니라 Post모듈을 불러서 Post를 include하라는 말씀이신가요? 

그러나 as는 User모델에 'Liker'라고 붙였는데 Post모델에도 as를 따로 붙여서 include하면 되나요?

그리고 post.Likers에 아이디를 찾아야 한다는게 무슨 뜻인지 이해가 안갑니다.

 

Post includes User as 'Likers' 하면 post.Likers가 생깁니다. 게시물을 불러올 때 그 게시물을 좋아요 누른 사람을 불러오면 됩니다.

deserializeUser에서 하는 게 아닙니다.(이건 내 정보를 불러오는 건데 이렇게 하려면 내가 좋아요 누른 게시물들을 불러와야죠)

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

선생님께서 말씀하신걸 토대로

deserializeUser에 있던 Liker모델을 GET / 라우터로 옮겼습니다

const posts = await Post.findAll({
        include: [{
            model: User,
            attributes: ['id', 'nick'],
        }, {
            model: User,
            attributes: ['id', 'nick'],
            as: 'Liker',
        }],
        order: [['createdAt', 'DESC']],
  });

그리고 posts.Liker에서 아이디를 찾으라 하셔서

{% if not posts.Liker.includes(twit.User.id) and twit.User.id !== user.id %}
<button class="like">좋아요</button>
{% elif posts.Liker.includes(twit.User.id) and twit.User.id !== user.id %}
<button class="not-like">좋아요취소</button>

이렇게 바꿨습니다.

그리고 실행을 해보니

\Desktop\github\nodebird-project\views\main.html) [Line 42, Column 41] Error: Unable to call `post["Liker"]["includes"]`, which is undefined or falsey

이런 오류가 발생합니다.

posts을 콘솔로 찍어보면 Liker배열이 뜨긴 합니다

어떻게 정의해야 하나요

 

posts는 post들을 묶어놓은 배열이고요. for post in posts문 쓰셨으면 post로 해야 합니다. 근데 왜 아래 에러 메시지는 post인가요?

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

처음에 post로 해보다가 posts로 렌더링하길래 posts로 해봤는데 둘다 저런 에러가 뜹니다

post중에 Liker가 없거나 undefined인 게 있을 수 있습니다.

post.Liker && post.Liker.includes... 이렇게 하셔야 합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

{% if not post.Liker && post.Liker.includes(twit.User.id) and twit.User.id !== user.id %}
 <button class="like">좋아요</button>
{% elif post.Liker && post.Liker.includes(twit.User.id) and twit.User.id !== user.id %}
<button class="not-like">좋아요취소</button>
{% endif %}

말씀하신대로 해보니

\Desktop\github\nodebird-project\views\main.html) [Line 43, Column 37] expected block end in if statement

이런 오류가....

if문에서 블록의 끝이 예상된다는데

콘솔 찍힌걸 보면 템플릿 렌더링 오류라는데 도저히 감이 안잡힙니다 ㅜㅜ

(post.Liker && post.Liker.includes(twit.User.id)) 이렇게 괄호로 한 번 더 감싸보세요.

그리고 에러메시지에 나온대로 43번째 줄이 뭔지 저한테 알려주시면 제가 더 편하겠죠?

twit.User.id !== user.id

는 자기 게시글에 좋아요를 못 누르게 하려고 하시는 건가요?

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

아 위 코드가 43번쨰 줄입니다

자기 게시글에 좋아요 못 누르게 하는거 맞습니다. 생각해보니 굳이 필요없는 코드네요

말씀하신 괄호로 한 번 더 감싸니

\Desktop\github\nodebird-project\views\main.html) [Line 43, Column 23] parseAggregate: expected comma after expression

이번엔 이런 오류가.... 똑같이 템플릿 렌더링 에러입니다

표현식 뒤에 콤마가 예상된다.. 번역에선 필요하다는거 같은데 이해가 안갑니다.

if not 문 대신 if 문을 사용해서 다시 해보세요. 좋아요와 좋아요 취소 코드의 순서를 바꾸세요.

{% if post.Liker and post.Liker.includes(twit.User.id) %}

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

{% if (post.Liker and post.Liker.includes(twit.User.id)) %}
 <button class="not-like">좋아요 취소</button>
{% elif (post.Liker and post.Liker.includes(twit.User.id)) %}
 <button class="like">좋아요</button>
{% endif %}

말씀하신대로 하니 실행이 됩니다.

그러나 이번에 좋아요버튼이 사라졌습니다

에러도 안뜨니 매우 당황스럽네요;;

 

두번째 elif도 조건문이 처음이랑 같으니 안 뜨죠. elif 말고 else 하세요.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

{% if (post.Liker and post.Liker.includes(twit.User.id)) %}
  <button class="not-like">좋아요 취소</button>
{% else (post.Liker and post.Liker.includes(twit.User.id)) %}
   <button class="like">좋아요</button>
{% endif %}

이번에도 렌더링 오류가 뜹니다.

\Desktop\github\nodebird-project\views\main.html) [Line 45, Column 22] expected block end in else statement

else문장이 45번 라인인데 비슷한 오류가 계속해서 뜹니다.

템플릿 렌더링 오류라 전혀 감을 못잡아서 계속해서 질문하게 되는데 죄송합니다ㅜㅜ

 

{% else %} 입니다.

else는 조건이 없습니다. If문을 생각해보세요.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

{% if (post.Liker and post.Liker.includes(twit.User.id)) %}
 <button class="not-like">좋아요 취소</button>
{% else %}
  <button class="like">좋아요</button>
{% endif %}

아 감사합니다. 실행은 됩니다.

콘솔은 확인해보면 정상적으로 POST /post/:id/like를 요청합니다.

또 Post객체 안에 Liker도 들어있습니다.

그러나 그러나 좋아요를 눌러도 좋아요취소가 안뜹니다.

게다가

처음 좋아요를 누르면 현재 접속된 user의 아이디가 post의 Liker에 배열로 데이터 값에 들어갑니다.

문제는 이후에 또 다른 게시글에 좋아요를 눌러도 Liker에 User의 데이터가 들어가지 않습니다.

이건 버튼 클릭 이벤트를 잘못 구현하신 것 아닌가요?

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

document.querySelectorAll('.like').forEach(function(tag) {
      tag.addEventListener('click', function() {
        const myId = document.querySelector('#my-id');
        if (myId) {
        axios.post(`/post/${userId}/like`)
          .then(() => {
            location.reload();
          })
          .catch((err) => {
            console.error(err);
          });
        }
      });
    });
    document.querySelectorAll('.not-like').forEach(function(tag) {
      tag.addEventListener('click', function() {
        const myId = document.querySelector('#my-id');
        if (myId) {
        axios.post(`/post/${userId}/not-like`)
          .then(() => {
            location.reload();
          })
          .catch((err) => {
            console.error(err);
          });
        }
      });
  });

버튼 클릭 이벤트는 이렇게 했습니다.

 

여기서 이상한 점 두가지는 userId와 myId의 관계, 그리고 게시글 좋아요인데 게시글 아이디가 없다는 겁니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

document.querySelectorAll('.like').forEach(function(tag) {
      tag.addEventListener('click', function() {
        const myId = document.querySelector('#my-id');
        if (myId) {
          const postId = tag.parentNode.querySelector('.twit-post-id').value;
            if (confirm('좋아요를 누르시겠습니까?')) {
              axios.post(`/post/${postId}/like`)
                .then(() => {
                  location.reload();
                })
                .catch((err) => {
                  console.error(err);
                });
            }
        }
      });
  });

myId는 value가 user.id인 태그로 if를 사용해 로그인 된 상태에서 좋아요를 누를 수 있도록 했습니다.

그 다음 postId를 가져와 axios로 post 라우터로 넘깁니다.

router.post('/:id/like', async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        await post.addLiker(req.user.id);
        res.send('succes');
    } catch (error) {
        console.log(error);
        next(error);
    }
})

post 라우터에서는 params로 :id값을 추출해서 좋아요를 누를 게시글을 Post모델에서 찾습니다.

그 후 user.id를 Liker 모델에 추가합니다

만약 Liker에 user.id가 있으면 좋아요 취소 버튼이 나타납니다.

그런데 post.id를 어떻게 가져와야 할지 고민입니다.

<input type="hidden" value="{{twit.Post.id}}" class="twit-post-id">

twit.User.id로 user.id 가져온거 처럼 Post도 똑같이 해봤지만 저 twits.Post.id에 값은 없었습니다.

 

아니죠. twit이라는 변수가 이미 존재하니까 단순히 {{twit.id}} 하면 되죠.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

네트워크도, 데이터도 정상적으로 작동하는데 좋아요가 좋아요 취소로 바뀌지 않는 것으로 보아 넌적스에서 잘못 코딩된거 같습니다.

post.Liker는 배열인데 그럼혹시

{% if (post.Liker and post.Liker.includes(twit.User.id)) %}
              <button class="not-like">좋아요 취소</button>
            {% else %}
              <button class="like">좋아요</button>
          {% endif %}

여기서 배열을 id만 따로 뽑아서 사용해야 하나요?

 

post.Liker가 무슨 모양인지 확인해보시겠어요? [1,2,3]이런 모양인지 [{ id: 1}, {id: 2}]이런 모양인지가 중요합니다. 후자는 includes가 아니라 find 써야 하거든요.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

콘솔로그에 Post객체의 데이터값은 Liker: [Array]로 되어있습니다

find로 바꿔서 해봤지만 includes를 한 결과랑 같은 결과나 나왔습니다

find로 바꿔서 어떻게하셨는지 정확하게 알려주세요. 정확하게 알려주실수록 더 빠르게 해결됩니다.

Liker: [Array]로 되어있으면 Liker[0] 콘솔로그 찍으셔서 더 자세하게 확인하시고요.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

위 코드의 includes위치에 find로 바꿔서 코딩했습니다.

Liker[0]콘솔로그를 찍는데

console.log(Liker[0]);

이렇게 찍었더니 Liker가 정의되지 않았다고 뜹니다.

console.log(Post.Liker[0]);

그래서 이렇게 찍어봤더니 '0'이 정의되지 않았다고 뜹니다

이건 User.Liker[0]으로 찍어도 마찬가지 입니다.

그냥

console.log(Post.Liker);

이렇게 찍으면 

undefined라는 로그가 찍힙니다

 

includes와 find의 사용법은 완전 다릅니다. 단순히 바꾼다해서 되지 않습니다. Array의 find 메서드 찾아보세요.

post는 그럼 어떻게 Liker: [Array]가 뜬 건가요? 스크린샷이라도 보여주세요.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.18

 

posts를 콘솔로 찍은 결과입니다

지금 제가 이해가 안 되는게 console.log(posts)를 하셨다고 했는데 위에서 말씀하신 console.log(Post.Liker)는 뭔가요? 왜 변수가 달라지나요? 또한 왜 posts는 배열이 아닌가요? 제가 파악이 아예 안 됩니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.19

Liker[0] 콘솔로그 찍어보라고 하셔서 찍었더니 오류가 떴습니다

그래서 이것저것 (Post.Liker이라든가 ) 찍어본겁니다.

위 스크린 샷은 posts배열에서 Post객체 하나만 잘라서 가져온 것입니다

그러니까 결과물을 콘솔로그 코드와 함께 전체를 보여주세요. 그리고 임의로 자르지 마세요. 잘려나간 부분에 문제의 원인이 있을 수 있는데 그걸 잘라내시면 중요한 힌트가 사라지는 겁니다. 귀찮아서 잘라서 올리셨을 수도 있는데 그렇게 잘라서 올리다가 이렇게 스무고개하듯 더 귀찮아집니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.19

router.get('/', async (req, res, next)

이 라우터에

console.log(posts);

콘솔로그를 찍은 결과입니다

게시글은 좋아요1과 좋아요2가 있으며 좋아요1에만 좋아요 버튼을 누른 상태입니다

콘솔로그의 결과는

이러한 결과가 나왔습니다

 

네 지금 보시면 게시글 2개에 하나는 Liker가 빈 배열이고 다음 것은 [User]가 들어 있습니다.

console.log(posts[0].Liker) 해서 빈 배열이 나오는지와

console.log(posts[1].Liker[0]); 해서 [User]이 나오는지

console.log(posts[1].Liker[0][0]); 해서 User는 어떤 모양으로 생겼는지 확인이 필요합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.19

console.log(posts[0].Liker);

이렇게 하면

빈 배열이 찍힙니다

console.log(posts[1].Liker[0]);

이렇게 찍으면

User객체가 나옵니다

console.log(posts[1].Liker[0][0]);

다만 이렇게 찍으면

정의되지 않았다고 뜹니다

 

그럼 posts[1].Liker[0]은 있는 거네요.

post.Liker?.find(function(v) { return v.id === twit.User.id; }) 하시면  되겠습니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.19

            {% if post.Liker.find(function(v) {return v.id === twit.User.id; }) %}
              <button class="not-like">좋아요 취소</button>
            {% else %}
              <button class="like">좋아요</button>
          {% endif %}

이렇게 치면

parseSignature: expected comma after expression

이런 에러가 납니다. 서명을 파싱할 때 에러가 난거 같은데 

위에 선생님께서 올리신 코드 중에 Liker뒤에 ?가 있던데 이건 어떻게 처리해야 할까요?

 

넌적스가 최신 문법 및 복잡한 지원하지 않아서 그렇습니다.

post.Liker and post.Liker.find... 로 변경해도 안 되면 앞으로 진행하기 매우 어려워집니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.19

            {% if post.Liker and post.Liker.find(function(v) {return v.id === twit.User.id; }) %}
              <button class="not-like">좋아요 취소</button>
            {% else %}
              <button class="like">좋아요</button>
          {% endif %}

이렇게 해도 똑같이

parseSignature: expected comma after expression

이런 오류가 뜹니다....

넌적스를 새롭게 업데이트를 해야 하나요?  앞으로 진행하기 어렵다는 게 무슨 뜻인가요 이 부분을 넘겨야 하는 건가요?

nunjucks 문법의 한계로 진행하려면 더 복잡한 방법을 써야 합니다. 넌적스 부분은 간단하게 만들고 서버 라우터 부분에서 복잡한 로직을 두는 거죠.

예를 들어 posts가 있으니

posts.forEach((post) => {
  post.liked = !!post.Likers.find((v) => v.id === post.User.id);
});

이런 식으로 라우터에서 로직을 넣어서 넌적스에서는 간단하게

{% if twit.liked %} 만 쓰는 겁니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.20

이 방식으로 해보았더니 서버는 정상적으로 돌아갑니다. 그러나 서비스에 문제가 생깁니다.

posts.forEach((post) => post.liked = !!post.Liker.find((v) => v.id === post.User.id));

일단 이 문장은 posts배열요소 각각에 

post.User.id와 post.Liker.id가 같은 경우 true, 다른 경우 fasle를 반환하여 post.liked에 넣는 콜백함수를 실행하는것이라 이해했습니다.

※여기 있는 post매개변수는 Post모델의 각각의 객체 라고 이해해도 되나요?

            {% if twit.liked %}
              <button class="not-like">좋아요 취소</button>
            {% else %}
              <button class="like">좋아요</button>
          {% endif %}

 

 
<input type="hidden" value="{{twit.id}}" class="twit-post-id">
...
const postId = tag.parentNode.querySelector('.twit-post-id').value;

또한 twit.id를 postId로 받아서(여기서 twid.id는 Post모델에 include한 User모델의 id입니다)

axios.post(`/post/${postId}/like`)

서버로 보냅니다

const post = await Post.findOne({ where: { id: req.params.id } });

서버에선 postId를 가진 Post모델을 찾아서

await post.addLiker(req.user.id);

Liker에 req.user.id를 넣습니다.

 

자기가 쓴 게시글과 자신이 누른 좋아요의 관계는 정확히 알려줍니다

 

그러나 다른 아이디로 접속하면 post.Liker.id에 현재 접속한 아이디가 없기에 '좋아요'버튼이 출력되어야 하는데 '좋아요 취소'버튼이 생깁니다

이상해서 몇번 실험을 해보니

'자기가 쓴 포스트만 좋아요 버튼을 누르거나 취소할수 있다'는 사실을 알아냈습니다.

어떻게 해결해야 할지 도움을 구해봅니다

 

post.User.id가 아니라 req.user.id였네요.

또한 twit.id를 postId로 받아서(여기서 twid.id는 Post모델에 include한 User모델의 id입니다)

에서 twit.id는 post의 id입니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.21

posts.forEach((post) => post.liked = !!post.Liker.find((v) => v.id === req.user.id));

이렇게 고치면

Cannot read properties of undefined (reading 'id')

id가 정의 되지 않는다는 에러가 뜹니다

req.user를 가져오는 코드가 따로 있는건가요?

아니면 이 코드가 req.user가 생성되기 이전에 쓰여진 코드라서 그런건가요?

로그인에 들어가면 정상적으로 작동합니다.

if문으로 해결이 가능할까요?

wdhgood123님의 프로필

wdhgood123

질문자

2022.05.21

if(req.user) 로 해결 했습니다. 그동안 귀찮았을텐데 감사했습니다

req.user?.id 하시는게 더 짧습니다.