피드관리 - Part 2 피드조회

Project Diary/React + MariaDB (PicShare WebApp)

사용자가 피드를 필터링하여 조회할 수 있는 기능을 구현합니다. 이 글에서는 모든 피드와 사용자가 팔로우한 사용자의 피드만을 필터링하여 조회하는 방법과 필터링된 피드를 가져와 화면에 표시하는 과정을 다룹니다.


프론트엔드

 

1. 피드 필터링: MainFeedCategorySection

모든 피드(All)와 팔로잉한 사용자의 피드(Following)만 필터링하여 조회합니다.

// 피드 필터링 컴포넌트
const MainFeedCategorySection = ({ setFilter, filter }) => {
  const dispatch = useDispatch();
  const currentUser = useSelector((state) => state.members.user);

  // 필터 버튼 클릭 핸들러
  const handleFilterClick = async (filterType) => {
    if (filterType === "all") {
      // 모든 피드 필터 설정
      setFilter({ type: "all" });
    } else if (filterType === "following" && currentUser) {
      // 팔로잉 피드 필터 설정
      try {
        const response = await dispatch(fetchFollowingList(currentUser.userNo));
        const followingUserNos = response.map((user) => user.userNo);
        setFilter({ type: "following", userNos: followingUserNos });
      } catch (error) {
        console.error("팔로잉 필터 실패:", error);
      }
    }
  };

  return (
    <div>
      <button onClick={() => handleFilterClick("all")}>All</button>
      <button onClick={() => handleFilterClick("following")}>Following Feed</button>
    </div>
  );
};

export default MainFeedCategorySection;
  • handleFilterClick 함수는 필터 타입에 따라 모든 피드 또는 팔로잉 피드를 설정합니다.
  • "all"일 때는 모든 피드를, "following"일 때는 현재 사용자가 팔로우하는 사용자의 피드만 필터링합니다.

2. 피드 데이터 가져오기 및 처리: MainFeedSection

// 필터링된 피드 데이터를 가져와 화면에 표시하는 컴포넌트
const MainFeedSection = ({ filter }) => {
  const dispatch = useDispatch();
  const { feeds, loading, error } = useSelector((state) => state.feeds);

  // 필터가 변경될 때마다 피드 데이터를 가져옴
  useEffect(() => {
    if (filter.type === "all") {
      dispatch(fetchAllFeed());
    } else if (filter.type === "following") {
      dispatch(fetchAllFeed(filter));
    }
  }, [filter, dispatch]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      {feeds.map((post, index) => (
        <div key={post.postId}>
          <div>{post.userNickname}</div>
          <div>{post.content}</div>
          {/* 이미지와 해시태그를 표시하는 로직 */}
        </div>
      ))}
    </div>
  );
};

export default MainFeedSection;

필터링된 피드를 feeds 상태로 받아와 화면에 렌더링합니다.

 

3. Redux Slice: 피드 상태 관리

import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const serverUrl = import.meta.env.VITE_API_URL;

// Redux Slice 생성
const feedSlice = createSlice({
  name: "feed",
  initialState: {
    feeds: [],
    loading: false,
    error: null,
  },
  reducers: {
    initFeedStart(state) {
      state.loading = true;
      state.error = null;
    },
    initFeedSuccess(state, action) {
      state.feeds = action.payload;
      state.loading = false;
    },
    initFeedFail(state, action) {
      state.error = action.payload;
      state.loading = false;
    },
  },
});

export const { initFeedStart, initFeedSuccess, initFeedFail } = feedSlice.actions;

// 모든 피드를 가져오는 Thunk 함수
export const fetchAllFeed = (filter) => async (dispatch) => {
  dispatch(initFeedStart());

  try {
    let url = `${serverUrl}/feed/all`;
    if (filter && filter.type === "following") {
      url += `?userNos=${filter.userNos.join(",")}`;
    }
    const response = await axios.get(url);
    dispatch(initFeedSuccess(response.data));
  } catch (error) {
    dispatch(initFeedFail(error.toString()));
  }
};

export default feedSlice.reducer;
  • fetchAllFeed 함수로 필터 조건에 따라 모든 피드 또는 팔로잉 피드를 가져옵니다.

 

백엔드

 

1. 피드 데이터 조회 API 엔드포인트

데이터베이스에서 피드 데이터를 조회하여 필터링된 피드를 반환합니다.

import express from "express";
import { db } from "../db.js";

const feedRouter = express.Router();

// 모든 피드를 가져오는 API 엔드포인트
feedRouter.get("/all", (req, res) => {
  const { userNos } = req.query;
  let query = `SELECT p.*, u.userNickname, u.profilePicture 
               FROM posts p 
               JOIN users u ON p.userNo = u.userNo`;

  if (userNos) {
    query += ` WHERE p.userNo IN (${userNos.split(",").map(Number).join(",")})`;
  }

  query += ` ORDER BY p.created_at DESC`;

  db.query(query, (err, posts) => {
    if (err) {
      console.log(err);
      return res.json({
        message: "서버 오류가 발생했습니다. 다시 시도해주세요.",
      });
    }

    const postIds = posts.map((post) => post.postId);

    // 피드 이미지 가져옴
    db.query(
      `SELECT i.* FROM images i WHERE i.postId IN (?)`,
      [postIds],
      (err, imagesData) => {
        if (err) {
          console.log(err);
          return res.json({
            message: "피드의 이미지를 가져오던 중 오류가 발생했습니다.",
          });
        }

        // 해시태그 데이터 가져옴
        db.query(
          `SELECT ph.postId, h.tag
           FROM post_hashtags ph
           JOIN hashtags h ON ph.hashtagId = h.hashtagId
           WHERE ph.postId IN (?)`,
          [postIds],
          (err, hashtagsData) => {
            if (err) {
              console.log(err);
              return res.json({
                message: "피드의 해시태그를 가져오던 중 오류가 발생했습니다.",
              });
            }

            // 각각의 이미지를 해당 포스트에 매핑
            const imagesByPostId = imagesData.reduce((acc, image) => {
              if (!acc[image.postId]) acc[image.postId] = [];
              acc[image.postId].push(image);
              return acc;
            }, {});

            // 각각의 해시태그를 해당 포스트에 매핑
            const hashtagsByPostId = hashtagsData.reduce((acc, hashtag) => {
              if (!acc[hashtag.postId]) acc[hashtag.postId] = [];
              acc[hashtag.postId].push(hashtag.tag);
              return acc;
            }, {});

            // 결과를 최종 포스트 데이터에 합침
            const result = posts.map((post) => ({
              ...post,
              feedImages: imagesByPostId[post.postId] || [],
              feedHashtags: hashtagsByPostId[post.postId] || [],
            }));

            console.log("서버에서 보내주는 올피드데이터", result);
            res.send(result);
          }
        );
      }
    );
  });
});

export default feedRouter;

 

sql 쿼리 : 기본쿼리
SELECT p.*, u.userNickname, u.profilePicture FROM posts p JOIN users u ON p.userNo = u.userNo
  • 모든 포스트와 해당 포스트를 작성한 사용자의 닉네임과 프로필 사진을 가져옵니다.
필터링 조건
(${userNos.split(",").map(Number).join(",")})
  • 특정 사용자(userNos)가 작성한 포스트만 필터링합니다.
sql 쿼리 : 이미지 데이터 조회
SELECT i.* FROM images i WHERE i.postId IN (?)
  • 특정 포스트의 이미지를 가져옵니다.
sql 쿼리 : 해시태그 데이터 조회
SELECT ph.postId, h.tag FROM post_hashtags ph JOIN hashtags h ON ph.hashtagId = h.hashtagId WHERE ph.postId IN (?)
  • 특정 포스트의 해시태그를 가져옵니다.