트위터 북마크 기능 구현하기(React, nodejs, sequelize)

이서우·2023년 3월 18일
0
post-thumbnail

이번주는 트위터 기능중 트윗 게시물을 저장할 수 있는 북마크 기능을 구현해보았다.


사진 속 북마크 버튼(형광테두리)을 클릭하면 북마크 기능이 실행된다.

✅ 기능 구현 과정

sequelize에 bookmark 테이블 생성하고 tweet_id을 기준으로 tweet데이터가 담겨져 있는 tweets 테이블과 관계형성하였다.

➡️ 관계를 형성한 이유는 나중에 북마크 페이지에서 데이터를 가지고 올때 tweet_id를 기준으로 Tweets테이블과 join하여 북마크 트윗게시물 데이터를 가지고 올 수 있기때문이다.

백엔드 코드

//table.ts


@Table({
  timestamps: false,
  tableName: "bookmark",
  charset: "utf8",
  collate: "utf8_general_ci",
})
export class Bookmark extends Model {
  @Column({
    type: DataType.INTEGER,
    autoIncrement: true,
    primaryKey: true,
  })
  id!: IntegerDataType;

  @Column({
    type: DataType.STRING,
    allowNull: true,
  })
  content!: string;
  
  @ForeignKey(() => Tweets)
  @Column({
    type: DataType.INTEGER,
    allowNull: false,
  })
  tweet_id!: IntegerDataType;

  @Column({
    type: DataType.INTEGER,
    allowNull: true,
  })
  user_id!: IntegerDataType;

  @BelongsTo(() => Tweets)
  tweets!: Tweets;
}

제일 처음 페이지를 로딩했을때 모든 트윗 데이터를 가져오기 위해 사용하는 getTweet API 코드이다.

isBookmark라는 변수를 하나 만들어서 해당 트윗 북마크 유무를 알려준다.

현재 로그인한 유저 데이터(currentUser)를 조회한 후
=> 유저가 북마크를 누른 tweet데이터가 있는지 찾아보고
=> 북마크된 트윗의 경우 is_bookmark를 true값으로 넘겨준다.(기본값은 false)


router.get(
  "/select",
  verifyRefreshToken,
  async (req: any, res: Response, next: NextFunction) => {
    const currentUser: Users[] = await Users.findOne({
      where: {
        email: req.email,
      },
    }).then((r: any) => {
      return r.user_id;
    });

    let pageNum = Number(res.req.query.pageCount);
 
    let offset = 0;
    if (pageNum > 1) {
      offset = 10 * (pageNum - 1);
    }

    const selectCurrentTweets = await Tweets.findAll({
      include: [Likes, Bookmark, Comments],
      offset: offset,
      limit: 10,
    }).then((d: any) => {
      return d.map((d: any) => {
        let isLike = false;
        //기본값은 false로 넘겨줌
        let isBookmark = false;

        if (
          d.like.some((i: any) => {
            return i.user_id === currentUser;
          })

        ) {
          isLike = true;
        }

		//트윗데이터중에서 현재 유저가 북마크를 한 데이터가 존재하는지 탐색 후 북마크된 데이터는 isBookmark를 true값으로 넘겨줌.
        
        if (
          d.bookmark.some((i: any) => {
            return i.user_id === currentUser;
          })
        ) {
          isBookmark = true;
        }

        return {
          tweet_id: d.tweet_id,
          content: d.content,
          email: d.email,
          like: d.like,
          tag: d.tag,
          user_id: d.user_id,
          write_date: d.write_date,
          upload_file: d.upload_file,
          reply_tweet_id: d.reply_tweet_id,
          is_like: isLike,
          is_bookmark: isBookmark,
          comment: [],
          is_opened: false,
          retweet_opened: false,
        };
      
    let count = await Tweets.count();
    let totalPageNumber = Math.round((await Tweets.count()) / 10);

    res.status(200).json({
      data: selectCurrentTweets,
      count,
      user_id: currentUser,
      totalPageNumber: totalPageNumber,
    });
  }
);

saveBookmark.ts

  • 북마크를 저장할때 : /saveBookmark (create기능 사용)
  • 북마크를 삭제할때 : /saveBookmark/delete(destroy기능을 사용해서 프론트에서 넘겨준 tweet_id, user_id를 사용해서 해당 데이터를 삭제)
router.post(
  "/",
  verifyAccessToken,
  verifyRefreshToken,
  async (req: any, res: Response, next: NextFunction) => {
    await Bookmark.create({
      tweet_id: req.body.tweet_id,
      user_id: req.body.id,
    }).then((result) => {
      res.status(201).json(result);
    });
  }
);

router.post(
  "/delete",
  verifyAccessToken,
  verifyRefreshToken,
  async (req: any, res: Response, next: NextFunction) => {
    await Bookmark.destroy({
      where: {
        tweet_id: req.body.tweet_id,
        user_id: req.body.id,
      },
    }).then((result) => {
      res.status(201).json(result);
    });
  }
);

module.exports = router;

프론트 코드

// 북마크 추가 
 const addBookmark = (tweet_id: number) => {
    customAxios
      .post("/saveBookmark", {
        tweet_id: tweet_id,
        id: id,
      })
      .then((res) => {
        customAxios
          .get("/getTweets/select", {
            params: { getCurrentPage },
          })
          .then((result: any) => {
            queryClient.invalidateQueries(["select"]);
          });
      });
  };
  
  // 북마크 삭제
  const deleteBookmark = (prop: number) => {
    customAxios
      .post("/saveBookmark/delete", {
        tweet_id: prop,
        id: id,
      })
      .then((res) => {
        customAxios
          .get("/getTweets/select", {
            params: { getCurrentPage },
          })
          .then((result: any) => {
            queryClient.invalidateQueries(["select"]);
            //mutation 사용해보기
          });
      });
  };




// return 
<img
                        className="w-6 h-4 pl-2"
                        alt="#"
                        src={
                          t.is_bookmark
                            ? "/assets/bookmark.png"
                            : "/assets/bookmark_before.png"
                        }
                        onClick={() => {
                          if (t.is_bookmark === true) {
                            deleteBookmark(t.tweet_id);
                          }

                          if (t.is_bookmark === false) {
                            addBookmark(t.tweet_id);
                          }
                        }}
                      />
profile
프론트엔드 개발 지망생입니다:)

0개의 댓글