View on GitHub

評価値の登録

Home

評価値の登録

API

評価値登録 API

src/frontend/src/api/ratings/postRating.ts

import { ApiContext } from "@/types/data";
import { ERROR_MESSAGES } from "@/constants";
import { fetcher } from "@/utils";

const context: ApiContext = {
  apiRootUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
};

/**
 * 評価値登録API
 * @param movieId - 映画ID
 * @param rating - 評価値
 */
const postRating = async (movieId: number, rating: number): Promise<void> => {
  const access =
    typeof window !== "undefined" ? localStorage.getItem("access") : null;
  if (!access) throw new Error(ERROR_MESSAGES.USER_NOT_AUTHENTICATED);

  const url = `${context.apiRootUrl?.replace(/\/$/g, "")}/online/ratings/`;
  const headers: Record<string, string> = {
    Accept: "application/json",
    "Content-Type": "application/json",
    Authorization: `Bearer ${access}`,
  };
  const body = JSON.stringify({
    movie_id: movieId,
    rating: rating,
  });

  return await fetcher(url, {
    method: "POST",
    headers: headers,
    body: body,
  });
};

export default postRating;

コンポーネント

スターコンポーネント

src/frontend/src/app/components/ui/rating/Star.tsx

......
interface Props {
  index: number;
  width: number;
  rating: number;
  setRating: Function;
  handleRatingClick: (rating: number) => void; // <- 追加
}

const Star = (props: Props) => {
  const handleRatingClick = useCallback(() => {
    const rating = (props.index + 1) / 2;
    props.setRating(rating);
    props.handleRatingClick(rating); // <- 追加
  }, [props.setRating, props.handleRatingClick]); // <- props.handleRatingClickを追加
  ......
};

export default Star;

評価値コンポーネント

src/frontend/src/app/components/ui/rating/StarRating.tsx

......
interface Props {
  starWidth: number;
  rating: number;
  handleRatingClick: (rating: number) => void; // <- 追加
}

const StarRating = (props: Props) => {
  const [rating, setRating] = useState<number>(props.rating);

  return (
    <>
      <div className={`${STYLES.STAR_RATING}`}>
        {(function () {
          const stars = [];
          for (let i = 0; i < 10; i++) {
            stars.push(
              <Star
                key={i}
                index={i}
                width={props.starWidth}
                rating={rating}
                setRating={setRating}
                handleRatingClick={props.handleRatingClick} // <- 追加
              />
            );
          }
          return <div>{stars}</div>;
        })()}
      </div>
    </>
  );
};

export default StarRating;

映画詳細カードコンポーネント

src/frontend/src/app/components/ui/card/CardMovieDetail.tsx

......
import { Movie, User } from "@/types/data";
import { MESSAGES, STYLES, ERROR_MESSAGES } from "@/constants";
import getMovie from "@/api/movies/getMovie";
import getMyAccount from "@/api/auth/getMyAccount";
import postRating from "@/api/ratings/postRating"; // <- 追加
......
/**
 * 映画(詳細)カードコンポーネント
 */
const CardMovieDetail = (props: Props) => {
  ......
  if (!movie) {
    notFound();
  }

  // ↓追加
  const handleRatingClick = async (rating: number) => {
    try {
      await postRating(movie.movie_id, rating);
    } catch (e) {
      console.error("Failed to post rating:", e);
    }
  };
  // ↑追加

  return (
    <div className={`${STYLES.MOVIE_DETAIL_PAGE}`}>
      <article className={`${STYLES.MOVIE_DETAIL_CARD}`} key={movie.movie_id}>
        ...(略)...
        {!loadingUser && user && (
          <StarRating
            starWidth={STYLES.MOVIE_DETAIL_STAR_WIDTH}
            rating={movie.rating?.rating!}
            handleRatingClick={handleRatingClick} // <- 追加
          />
        )}
      </article>
    </div>
  );
};

export default CardMovieDetail;

映画カードコンポーネント

src/frontend/src/app/components/ui/card/CardMovie.tsx

......
import { Movie, User } from '@/types/data';
import { STYLES, MESSAGES } from '@/constants';
import postRating from '@/api/ratings/postRating'; // <- 追加
......
/**
 * 映画カードコンポーネント
 */
const CardMovie = (props: Props) => {
  ......
  // ↓追加
  const handleRatingClick = async (rating: number) => {
    try {
      await postRating(props.movie.movie_id, rating);
    } catch (e) {
      console.error('Failed to post rating:', e);
    }
  };
  // ↑追加

  return (
    <article className={`${STYLES.MOVIE_CARD}`} key={props.movie.movie_id}>
      ...(略)...
      {props.user ? (
        <StarRating
          starWidth={STYLES.MOVIE_STAR_WIDTH}
          rating={props.movie.rating?.rating!}
          handleRatingClick={handleRatingClick} // <- 追加
        />
      ) : (
        <></>
      )}
    </article>
  );
};

export default CardMovie;

実行確認

ブラウザで下記 URL にアクセスしてください。

サインイン後、任意の評価値をクリックすると、下記のようにratingsテーブルに評価値が登録されます。下記の例では、movie_id=1の映画に対して、評価値3.5を与えています。

recsys_full=# SELECT * FROM t_users_movies_rating ORDER BY rated_at DESC;
                     id                      | rating |           rated_at            | movie_id |               user_id
---------------------------------------------+--------+-------------------------------+----------+--------------------------------------
 <USER_ID>_000001                            |    3.5 | 2026-08-30 10:45:07.612773+09 |        1 | <USER_ID>
...(略)...

ブラウザで下記 URL にアクセスしてください。

同様に、映画リスト上でも任意の評価値をクリックすることで、ratingsテーブルに評価値が登録されます。ただし、現時点では、バックエンドサーバから評価値を取得していませんので、映画リスト上で評価値を更新しても、ブラウザを更新したり、ページを切り替えると、元の表示に戻ります。