Twitterクローンを作ります #17 リツイート機能(2)

前回リツイート機能を実装しましたが、空の投稿が表示される問題があったので、こちらを修正していきましょう。

# 画面の修正

通常の投稿であれば本文等は post.content を表示してあげればよいのですが、リツイートの場合は post.retweeted_post.content を表示しなければなりません。

ということで、PostPanelで displayPost という変数を導入して、通常の投稿であれば post を、リツイートであれば post.retweeted_post を設定し、こちらを表示するようにしましょう。

```vue:client/src/components/PostPanel.vue
<template>
    <div class="uk-margin-right">
        <div class="uk-margin-small-left uk-margin-small-bottom" v-if="isRetweet">{{post.posted_by.display_name}}がリツイート</div>
        <div class="uk-flex uk-flex-top">
            <img :src="url" class="uk-border-circle avatar">
            <div class="uk-flex uk-flex-left uk-flex-column uk-margin-left uk-flex-1">
                <div class="uk-flex uk-flex-middle uk-flex-row">
                    <span class="uk-text-bold">{{ displayPost.posted_by.display_name }}</span>
                    <span class="uk-margin-small-left uk-text-muted">@{{ displayPost.posted_by.username }}</span>
                    <span class="uk-margin-small-left uk-text-muted">{{ displayPost.created_at }}</span>
                    <span class="uk-flex-1"></span>
                    <span uk-icon="icon: close; ratio: 0.8"></span>
                </div>
                <div class="uk-flex uk-margin-small-top">
                    {{ displayPost.content }}
                </div>
                <div class="uk-flex uk-flex-between uk-margin-small-top">
                    <span class="uk-flex-1" uk-icon="comment"></span>
                    <span class="uk-flex-1 retweet" :class="{ active: displayPost.retweeting }">
                        <span uk-icon="bolt" @click="onRetweetClick"></span>
                        <span class="uk-margin-small-left">{{ displayPost.retweet_count ? displayPost.retweet_count : '' }}</span>
                    </span>                    
                    <span class="uk-flex-1 like" :class="{ active: displayPost.liking }">
                        <span uk-icon="heart" @click="onLikeClick"></span>
                        <span class="uk-margin-small-left">{{ displayPost.like_count ? displayPost.like_count : '' }}</span>
                    </span>
                </div>
            </div>
        </div>
        <hr>
    </div>
</template>

<script>
import { imageUrl } from '@/functions/avatar.js';
import { useStore } from 'vuex';

export default {
    props: {
        post: Object
    },
    setup(props) {
        const store = useStore();

        const isRetweet = Boolean(props.post.retweeted_post);
        const displayPost = isRetweet ? props.post.retweeted_post : props.post;

        const url = imageUrl(displayPost.posted_by);


        const onLikeClick = () => {
            if (displayPost.liking) {
                store.dispatch('unlikePost', displayPost);
            } else {
                store.dispatch('likePost', displayPost);
            }
        };

        const onRetweetClick = () => {
            if (displayPost.retweeting) {
                store.dispatch('unretweetPost', displayPost);
            } else {
                store.dispatch('retweetPost', displayPost);
            }
        };
        return {
            isRetweet,
            displayPost,
            url,
            onLikeClick,
            onRetweetClick
        };
    },
}
</script>

<style scoped>
.avatar {
    width: 50px;
    height: 50px;
}

.like.active {
    color: red;
}

.retweet.active {
    color: green;
}

.like,
.retweet {
    cursor: pointer;
}
</style>
```

![スクリーンショット 2022-03-17 16.33.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/d6b5a453-afbf-1411-4ee1-1a4427af1bc1.png)

大体の内容が表示されるようになりました。

ただ、likingやretweetingがうまく連動していないので修正していきます。

# store の修正

updatePost でリツイートされた投稿も更新されるように修正します。

```javascript:client/src/store/index.js
...
    updatePost (state, post) {
      const orig = state.posts.find(p => p._id === post._id);
      if (orig) {
        Object.assign(orig, post);
      }
      state.posts.filter(p => p.retweeted_post && p.retweeted_post._id === post._id)
        .forEach(p => {
          Object.assign(p.retweeted_post, post);
        });
    }
...
```

# APIの修正

一覧取得のAPIでリツイートされたものがあればlikingとretweetingを設定してあげるように修正します。

```python:server/apis/posts.py
...

@app.route('/api/posts', methods=['GET'])
@login_required()
def list_posts():
    results = []
    for post in Post._collection.find(sort=[('created_at', pymongo.DESCENDING)]):
        results.append(_populate(post))
    
    if results:
        user_id = ObjectId(session['user']['_id'])
        term = {
            '$gte': results[-1]['created_at'],
            '$lte': results[0]['created_at']
        }

        liked_posts = { str(like['post']) for like in Like._collection.find({'liked_by': user_id, 'posted_at': term}) }
        retweeted_posts = { str(retweet['post']) for retweet in Retweet._collection.find({'retweeted_by': user_id, 'posted_at': term}) }
        for result in results:
            result['liking'] = result['_id'] in liked_posts
            result['retweeting'] = result['_id'] in retweeted_posts
        
        for result in results:
            if 'retweeted_post' in result and '_id' in result['retweeted_post']:
                retweeted_post_id = ObjectId(result['retweeted_post']['_id'])
                result['retweeted_post']['liking'] = Like._collection.find_one({'liked_by': user_id, 'post': retweeted_post_id}) is not None
                result['retweeted_post']['retweeting'] = Retweet._collection.find_one({'retweeted_by': user_id, 'post': retweeted_post_id}) is not None

    return jsonify(results)

...
```

![スクリーンショット 2022-03-17 16.54.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/0459a4b1-a82a-43f0-ec8a-dda9f8000b7c.png)

これでリツイート機能はひとまず完成です。