diff --git a/ssr/package.json b/ssr/package.json index db1a534..4d26357 100644 --- a/ssr/package.json +++ b/ssr/package.json @@ -4,8 +4,8 @@ "description": "SSR 렌더링으로 영화 목록 불러오기", "main": "server/index.js", "scripts": { - "start": "NODE_TLS_REJECT_UNAUTHORIZED=0 node server/index.js", - "dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 nodemon server/index.js --watch" + "start": "NODE_TLS_REJECT_UNAUTHORIZED=1 node server/index.js", + "dev": "NODE_TLS_REJECT_UNAUTHORIZED=1 nodemon server/index.js --watch" }, "type": "module", "dependencies": { diff --git a/ssr/public/styles/main.css b/ssr/public/styles/main.css index 1cb07d9..59cae64 100644 --- a/ssr/public/styles/main.css +++ b/ssr/public/styles/main.css @@ -43,6 +43,13 @@ button.primary { border-radius: 4px; } +a.primary { + color: var(--color-white); + font-weight: bold; + background-color: var(--color-lightblue-90); + border-radius: 4px; +} + #wrap { min-width: 1440px; background-color: var(--color-bluegray-100); diff --git a/ssr/server/index.js b/ssr/server/index.js index 09efd9a..29bbcbf 100644 --- a/ssr/server/index.js +++ b/ssr/server/index.js @@ -15,6 +15,7 @@ const __dirname = path.dirname(__filename); app.use("/assets", express.static(path.join(__dirname, "../public"))); app.use("/", movieRouter); + // app.use("/members", membersRouter); // 본 미션 참고를 위한 코드이며 사전 미션에서는 사용하지 않습니다. // Start server diff --git a/ssr/server/routes/index.js b/ssr/server/routes/index.js index 84d32f2..90aab0a 100644 --- a/ssr/server/routes/index.js +++ b/ssr/server/routes/index.js @@ -2,20 +2,110 @@ import { Router } from "express"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; +import { + fetchMovieDetails, + fetchNowPlayingMovieItems, + fetchPopularMovieItems, + fetchTopRatedMovieItems, + fetchUpcomingMovieItems, +} from "../src/api/movie.js"; +import { renderMovieItems } from "../src/render/renderMovieItems.js"; +import { renderHeader } from "../src/render/renderHeader.js"; +import { renderMovieDetails } from "../src/render/renderMovieDetails.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const router = Router(); -router.get("/", (_, res) => { - const templatePath = path.join(__dirname, "../../views", "index.html"); - const moviesHTML = "

들어갈 본문 작성

"; +const fetchMovieItems = async (path) => { + switch (path) { + case "/": + case "/now-playing": + return await fetchNowPlayingMovieItems(); + case "/popular": + return await fetchPopularMovieItems(); + case "/top-rated": + return await fetchTopRatedMovieItems(); + case "/upcoming": + return await fetchUpcomingMovieItems(); + default: + throw new Error("Invalid path: 잘못된 경로 입니다."); + } +}; - const template = fs.readFileSync(templatePath, "utf-8"); - const renderedHTML = template.replace("", moviesHTML); +const renderMoviePage = async (req, res) => { + try { + const templatePath = path.join(__dirname, "../../views", "index.html"); + const currentPath = req.path === "/" ? "/now-playing" : req.path; - res.send(renderedHTML); -}); + const movies = await fetchMovieItems(currentPath); + const featuredMovie = movies[0]; + + const moviesHTML = renderMovieItems(movies); + const headerHTML = renderHeader(featuredMovie); + + let template = fs.readFileSync(templatePath, "utf-8"); + + const sectionTitles = { + "/now-playing": "상영 중인 영화", + "/popular": "지금 인기 있는 영화", + "/top-rated": "평점이 높은 영화", + "/upcoming": "개봉 예정 영화", + }; + + template = template.replace( + /
/g, + (_, tabName) => { + const isSelected = + currentPath === `/${tabName}` || + (currentPath === "/now-playing" && tabName === "now-playing"); + return `
`; + } + ); + + const renderedHTML = template + .replace("", headerHTML) + .replace(/\$\{sectionTitle\}/g, sectionTitles[currentPath]) + .replace("", moviesHTML); + + res.send(renderedHTML); + } catch (error) { + console.error(error.message); + res.status(500).send("Internal Server Error"); + } +}; + +const renderMovieDetailPage = async (req, res) => { + const id = req.params.id; + + try { + const templatePath = path.join(__dirname, "../../views", "index.html"); + + const movieDetail = await fetchMovieDetails(id); + const movieDetailHTML = renderMovieDetails(movieDetail); + + let template = fs.readFileSync(templatePath, "utf-8"); + + const renderedHTML = template.replace( + "", + movieDetailHTML + ); + + res.send(renderedHTML); + } catch (error) { + console.error(error.message); + res.status(500).send("Internal Server Error"); + } +}; + +router.get("/", renderMoviePage); +router.get("/now-playing", renderMoviePage); +router.get("/popular", renderMoviePage); +router.get("/top-rated", renderMoviePage); +router.get("/upcoming", renderMoviePage); +router.get("/detail/:id", renderMovieDetailPage); export default router; diff --git a/ssr/server/src/api/movie.js b/ssr/server/src/api/movie.js new file mode 100644 index 0000000..20555e2 --- /dev/null +++ b/ssr/server/src/api/movie.js @@ -0,0 +1,46 @@ +import { + FETCH_OPTIONS, + TMDB_MOVIE_DETAIL_URL, + TMDB_MOVIE_LISTS, +} from "../constant.js"; + +export const fetchPopularMovieItems = async () => { + const response = await fetch(TMDB_MOVIE_LISTS.POPULAR, FETCH_OPTIONS); + + const data = await response.json(); + + return data.results; +}; + +export const fetchNowPlayingMovieItems = async () => { + const response = await fetch(TMDB_MOVIE_LISTS.NOW_PLAYING, FETCH_OPTIONS); + + const data = await response.json(); + + return data.results; +}; + +export const fetchTopRatedMovieItems = async () => { + const response = await fetch(TMDB_MOVIE_LISTS.TOP_RATED, FETCH_OPTIONS); + + const data = await response.json(); + + return data.results; +}; + +export const fetchUpcomingMovieItems = async () => { + const response = await fetch(TMDB_MOVIE_LISTS.UPCOMING, FETCH_OPTIONS); + + const data = await response.json(); + + return data.results; +}; + +export const fetchMovieDetails = async (id) => { + const url = `${TMDB_MOVIE_DETAIL_URL}${id}?language=ko-KR`; + const response = await fetch(url, FETCH_OPTIONS); + + const data = await response.json(); + + return data.results; +}; diff --git a/ssr/server/src/constant.js b/ssr/server/src/constant.js new file mode 100644 index 0000000..b26b9a7 --- /dev/null +++ b/ssr/server/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/server/src/render/renderHeader.js b/ssr/server/src/render/renderHeader.js new file mode 100644 index 0000000..0af3c8c --- /dev/null +++ b/ssr/server/src/render/renderHeader.js @@ -0,0 +1,27 @@ +import { round } from "../../../../csr/src/utils.js"; + +export const renderHeader = ({ id, title, backdrop_path, vote_average }) => { + return /*html*/ ` +
+
+ +
+

+ MovieList +

+
+
+ + ${round(vote_average, 1)} +
+
${title}
+ 자세히 보기 +
+
+
+
+ `; +}; diff --git a/ssr/server/src/render/renderMovieDetails.js b/ssr/server/src/render/renderMovieDetails.js new file mode 100644 index 0000000..f4d187a --- /dev/null +++ b/ssr/server/src/render/renderMovieDetails.js @@ -0,0 +1,38 @@ +import { TMDB_THUMBNAIL_URL } from "../constant.js"; + +export const renderMovieDetails = (movieDetail = {}) => { + const { + title, + genres = [], + vote_average = 0, + poster_path, + overview, + } = movieDetail; + + return /*html*/ ` + +`; +}; diff --git a/ssr/server/src/render/renderMovieItems.js b/ssr/server/src/render/renderMovieItems.js new file mode 100644 index 0000000..3808113 --- /dev/null +++ b/ssr/server/src/render/renderMovieItems.js @@ -0,0 +1,25 @@ +import { round } from "../../../../csr/src/utils.js"; + +export const renderMovieItems = (movieItems = []) => + movieItems + .map( + ({ id, title, poster_path, vote_average }) => /*html*/ ` +
  • + +
    + ${title} +
    +

    + ${round(vote_average, 1)}

    + ${title} +
    +
    +
    +
  • + ` + ) + .join(""); diff --git a/ssr/views/index.html b/ssr/views/index.html index a052396..482c342 100644 --- a/ssr/views/index.html +++ b/ssr/views/index.html @@ -13,34 +13,19 @@
    -
    -
    - -
    -

    MovieList

    -
    -
    - - ${bestMovie.rate} -
    -
    ${bestMovie.title}
    - -
    -
    -
    -
    +