${movieDetail.title}
-
${movieDetail.releaseYear} · ${movieDetail.genres.join(", ")}
+
${
+ movieDetail.releaseYear
+ } · ${movieDetail.genres.join(", ")}
7.7
diff --git a/ssr/server/routes/index.js b/ssr/server/routes/index.js
index 84d32f2..4c7685e 100644
--- a/ssr/server/routes/index.js
+++ b/ssr/server/routes/index.js
@@ -2,20 +2,130 @@ import { Router } from "express";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
+import { fetchMovieDetail, fetchMovies } from "../../src/fetchMovies.js";
+import {
+ renderDetailModal,
+ renderMovieItems,
+ renderTabs,
+} from "../../views/template.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const router = Router();
-router.get("/", (_, res) => {
+const getMovieList = async (category) => {
+ const movieData = await fetchMovies(category);
+ return parseMovies(movieData);
+};
+
+const getMovieDetail = async (movieId) => {
+ const movieDetailData = await fetchMovieDetail(movieId);
+ return parseMovieDetail(movieDetailData);
+};
+
+const renderMoviesPage = async (category) => {
+ const movieList = await getMovieList(category);
+ const templatePath = path.join(__dirname, "../../views", "index.html");
+ let template = fs.readFileSync(templatePath, "utf-8");
+ return renderMoviesTemplate(template, movieList, category);
+};
+
+const renderMoviesDetailPage = async (category, movieId) => {
+ const movieList = await getMovieList(category);
+ const movieDetail = await getMovieDetail(movieId);
+
const templatePath = path.join(__dirname, "../../views", "index.html");
- const moviesHTML = "
들어갈 본문 작성
";
+ let template = fs.readFileSync(templatePath, "utf-8");
+ template = renderMoviesTemplate(template, movieList, category);
+ template = renderModalTemplate(template, movieDetail);
+
+ return template;
+};
+
+export const renderMoviesTemplate = (template, movieList, category) => {
+ const bestMovieItem = movieList[0];
+ const moviesHTML = renderMovieItems(movieList).join("");
+ const tabsHTML = renderTabs(category).join("");
+
+ template = template.replace("", moviesHTML);
+ template = template.replace("", tabsHTML);
+ template = template.replace(
+ "${background-container}",
+ "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/" +
+ bestMovieItem.background
+ );
+ template = template.replace("${bestMovie.rate}", bestMovieItem.rate);
+ template = template.replace("${bestMovie.title}", bestMovieItem.title);
+
+ return template;
+};
+
+export const renderModalTemplate = (template, movieDetailItem) => {
+ return template.replace(
+ "",
+ renderDetailModal(movieDetailItem)
+ );
+};
+
+export const parseMovies = async (movieData) => {
+ const formattedMovieData = movieData.results.map((movie) => ({
+ id: movie.id,
+ title: movie.title,
+ thumbnail: movie.poster_path,
+ rate: movie.vote_average,
+ background: movie.backdrop_path,
+ }));
+
+ return formattedMovieData;
+};
+
+export const parseMovieDetail = async (movie) => {
+ return {
+ id: movie.id,
+ title: movie.title,
+ thumbnail: movie.poster_path,
+ releaseYear: movie.release_date,
+ genres: movie.genres.map((genre) => genre.name),
+ description: movie.overview,
+ rate: movie.vote_average,
+ };
+};
+
+const handleMovieRoute = async (res, category) => {
+ try {
+ let template = await renderMoviesPage(category);
+ res.send(template);
+ } catch (error) {
+ console.error("Error rendering page:", error);
+ res.status(500).send("Internal Server Error");
+ }
+};
+
+const handleDetailRoute = async (res, movieId) => {
+ try {
+ const moviesPageTemplate = await renderMoviesDetailPage(
+ "NOW_PLAYING",
+ movieId
+ );
+ res.send(moviesPageTemplate);
+ } catch (error) {
+ console.error("Error rendering page:", error);
+ res.status(500).send("Internal Server Error");
+ }
+};
- const template = fs.readFileSync(templatePath, "utf-8");
- const renderedHTML = template.replace("", moviesHTML);
+router.get("/", async (_, res) => handleMovieRoute(res, "NOW_PLAYING"));
+router.get("/now-playing", async (_, res) =>
+ handleMovieRoute(res, "NOW_PLAYING")
+);
+router.get("/popular", async (_, res) => handleMovieRoute(res, "POPULAR"));
+router.get("/top-rated", async (_, res) => handleMovieRoute(res, "TOP_RATED"));
+router.get("/upcoming", async (_, res) => handleMovieRoute(res, "UPCOMING"));
- res.send(renderedHTML);
+router.get("/detail/:id", async (req, res) => {
+ const { id } = req.params;
+ handleDetailRoute(res, id);
});
export default router;
diff --git a/ssr/src/constant.js b/ssr/src/constant.js
new file mode 100644
index 0000000..b26b9a7
--- /dev/null
+++ b/ssr/src/constant.js
@@ -0,0 +1,22 @@
+export const BASE_URL = "https://api.themoviedb.org/3/movie";
+
+export const TMDB_THUMBNAIL_URL =
+ "https://media.themoviedb.org/t/p/w440_and_h660_face/";
+export const TMDB_ORIGINAL_URL = "https://image.tmdb.org/t/p/original/";
+export const TMDB_BANNER_URL =
+ "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/";
+export const TMDB_MOVIE_LISTS = {
+ POPULAR: BASE_URL + "/popular?language=ko-KR&page=1",
+ NOW_PLAYING: BASE_URL + "/now_playing?language=ko-KR&page=1",
+ TOP_RATED: BASE_URL + "/top_rated?language=ko-KR&page=1",
+ UPCOMING: BASE_URL + "/upcoming?language=ko-KR&page=1",
+};
+export const TMDB_MOVIE_DETAIL_URL = "https://api.themoviedb.org/3/movie/";
+
+export const FETCH_OPTIONS = {
+ method: "GET",
+ headers: {
+ accept: "application/json",
+ Authorization: "Bearer " + process.env.TMDB_TOKEN,
+ },
+};
diff --git a/ssr/src/fetchMovies.js b/ssr/src/fetchMovies.js
new file mode 100644
index 0000000..a9ca132
--- /dev/null
+++ b/ssr/src/fetchMovies.js
@@ -0,0 +1,21 @@
+import {
+ FETCH_OPTIONS,
+ TMDB_MOVIE_DETAIL_URL,
+ TMDB_MOVIE_LISTS,
+} from "./constant.js";
+
+export const fetchMovies = async (category) => {
+ const endpoint = TMDB_MOVIE_LISTS[category];
+ const response = await fetch(endpoint, FETCH_OPTIONS);
+ return await response.json();
+};
+
+export const fetchMovieDetail = async (id) => {
+ const url = TMDB_MOVIE_DETAIL_URL + id;
+ const params = new URLSearchParams({
+ language: "ko-KR",
+ });
+ const response = await fetch(url + "?" + params, FETCH_OPTIONS);
+
+ return await response.json();
+};
diff --git a/ssr/tab.js b/ssr/tab.js
new file mode 100644
index 0000000..e69de29
diff --git a/ssr/views/index.html b/ssr/views/index.html
index a052396..6542a40 100644
--- a/ssr/views/index.html
+++ b/ssr/views/index.html
@@ -14,10 +14,15 @@
-
+
-

+
+
+

@@ -32,34 +37,10 @@

+
지금 인기 있는 영화
diff --git a/ssr/views/template.js b/ssr/views/template.js
new file mode 100644
index 0000000..454d542
--- /dev/null
+++ b/ssr/views/template.js
@@ -0,0 +1,83 @@
+export const renderMovieItems = (movieItems = []) =>
+ movieItems.map(
+ ({ id, title, thumbnail, rate }) => /*html*/ `
+
+
+
+

+
+
${rate}
+
${title}
+
+
+
+
+ `
+ );
+
+export const renderTabs = (selectedCategory) => {
+ const categories = {
+ NOW_PLAYING: "상영 중",
+ POPULAR: "인기순",
+ TOP_RATED: "평점순",
+ UPCOMING: "상영 예정",
+ };
+
+ return Object.entries(categories).map((category) => {
+ const isSelected = selectedCategory === category[0] ? "selected" : "";
+ const href = `/${category[0].toLowerCase().replace("_", "-")}`;
+
+ return (
+ /*html*/
+ `
+
+ ${category[1]}
+
+`
+ );
+ });
+};
+
+export const renderDetailModal = (movieDetail) => {
+ return /*html*/ `
+
+
+
+
+
+

+
+
+
${movieDetail.title}
+
${
+ movieDetail.releaseYear
+ } · ${movieDetail.genres.join(", ")}
+
${
+ movieDetail.rate
+ }
+
+
+ ${movieDetail.description}
+
+
+
+
+
+
+
+`;
+};
diff --git a/static/index.html b/static/index.html
index ae95f53..54f1505 100644
--- a/static/index.html
+++ b/static/index.html
@@ -15,10 +15,13 @@
-

+
+
+
-
-
9.5
+
+

+
9.5
인사이드 아웃2
@@ -53,331 +56,7 @@

지금 인기 있는 영화
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
- -
-
-

-
-
7.7
-
인사이드 아웃 2
-
-
-
+