Twitterクローンを作ります #28 ツイート表示ページ(1)

ツイート表示ページには下記を表示することにしましょう。

- 対象のツイート
- 対象のツイートの返信先ツイート
- 対象のツイートへの返信ツイート

# API

ツイートの1件取得のAPIを新たに追加します。

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

@app.route("/api/posts/<post_id>", methods=["GET"])
@login_required()
def get_post(post_id):
    post = Post._collection.find_one({"_id": ObjectId(post_id)})
    if not post:
        return error("投稿が存在しません", 404)
    post = _populate(post, True)
    replies = []
    for reply in Post._collection.find({"reply_to": ObjectId(post_id)}, sort=[("created_at", pymongo.ASCENDING)]):
        replies.append(_populate(reply, True))
    post["replies"] = replies
    return jsonify(post)


def _populate(post, set_liking_and_retweeting=False):
    if not post:
        return post
    posted_by = User._collection.find_one({"_id": post["posted_by"]})
    if posted_by:
        del posted_by["hashed_password"]
        post["posted_by"] = posted_by
    if "retweeted_post" in post and post["retweeted_post"]:
        post["retweeted_post"] = _populate(
            Post._collection.find_one({"_id": post["retweeted_post"]}),
            set_liking_and_retweeting
        )
    if "reply_to" in post and post["reply_to"]:
        post["reply_to"] = _populate(
            Post._collection.find_one({"_id": post["reply_to"]}),
            set_liking_and_retweeting
        )
    if set_liking_and_retweeting:
        user_id = ObjectId(session["user"]["_id"])
        post_id = post["_id"]
        if not isinstance(post_id, ObjectId):
            post_id = ObjectId(post_id)
        post["liking"] = (
            Like._collection.find_one(
                {"liked_by": user_id, "post": post_id}
            )
            is not None
        )
        post["retweeting"] = (
            Retweet._collection.find_one(
                {"retweeted_by": user_id, "post": post_id}
            )
            is not None
        )
    return post


```

"対象のツイートへの返信ツイート" を `replies` フィールドで返すようにしています。

likingやretweetingの扱いがちょっと煩雑ですね。
実行効率を重視してこの形にしましたが、実装効率だけでみれば今回の_populateの修正の形で一律で設定するほうがシンプルではあります。

# 画面

まずは `/posts/XXXXX` でidがXXXXXのツイートを表示するページに遷移するようにしましょう。

```javascript:client/src/router/index.js
...

  {
    path: "/posts/:postId",
    name: "posts",
    meta: { loginRequired: true },
    component: () => import("../views/PostsView.vue"),
  },

...

```

PostsView.vue を作っていきましょう。

```vue:client/src/views/PostsView.vue
<template>
  <div>
    <h3 class="uk-margin-top">ツイート</h3>
    <post-panel
      v-if="Boolean(post.reply_to)"
      :post="post.reply_to"
    ></post-panel>
    <post-panel :post="post"></post-panel>
    <post-panel
      v-for="reply in post.replies"
      :post="reply"
      :key="reply._id"
    ></post-panel>
  </div>
</template>

<script>
import { computed, watch } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";

import PostPanel from "@/components/PostPanel.vue";

export default {
  components: {
    PostPanel,
  },
  setup() {
    const route = useRoute();
    const store = useStore();

    watch(
      () => route.params.postId,
      (postId) => {
        store.dispatch("loadPost", { postId });
      },
      {
        immediate: true,
      }
    );

    const post = computed(() => store.state.post);

    return {
      post,
    };
  },
};
</script>

```

![スクリーンショット 2022-03-24 8.16.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/a38eaf61-c7cd-8ef4-335a-a3b0ee7c2253.png)

おおまかに表示できましたね。次回は細かいところを修正していきましょう。