diff --git a/ssr/server/routes/index.js b/ssr/server/routes/index.js index 84d32f2..393ad4f 100644 --- a/ssr/server/routes/index.js +++ b/ssr/server/routes/index.js @@ -2,20 +2,109 @@ import { Router } from "express"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; +import { + fetchMovieDetails, + fetchNowPlayingMovieItems, + fetchPopularMovieItems, + fetchTopRatedMovieItems, + fetchUpcomingMovieItems, +} from "../../src/apis/movies.js"; +import renderMovieList from "../../src/render/renderMovieList.js"; +import renderHeader from "../../src/render/renderHeader.js"; +import renderModal from "../../src/render/renderModal.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const router = Router(); -router.get("/", (_, res) => { +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 getTemplate = (templatePath) => fs.readFileSync(templatePath, "utf-8"); + +const getSectionTitles = () => ({ + "/now-playing": "상영 중인 영화", + "/popular": "지금 인기 있는 영화", + "/top-rated": "평점이 높은 영화", + "/upcoming": "개봉 예정 영화", +}); + +const replaceTemplatePlaceholders = ( + template, + headerHTML, + moviesHTML, + sectionTitle, + movieModalHTML = "" +) => { + return template + .replace("", headerHTML) + .replace(/\$\{sectionTitle\}/g, sectionTitle) + .replace("", moviesHTML) + .replace("", movieModalHTML); +}; + +const renderPage = async (req, res, isModal = false) => { const templatePath = path.join(__dirname, "../../views", "index.html"); - const moviesHTML = "

들어갈 본문 작성

"; + const currentPath = + req.path === "/" || req.path.startsWith("/detail") + ? "/now-playing" + : req.path; + const movies = await fetchMovieItems(currentPath); + const featuredMovie = movies[0]; + const headerHTML = renderHeader(featuredMovie); + const moviesHTML = renderMovieList(movies); + const sectionTitles = getSectionTitles(); + let template = getTemplate(templatePath); - const template = fs.readFileSync(templatePath, "utf-8"); - const renderedHTML = template.replace("", moviesHTML); + template = template.replace( + /
/g, + (_, tabName) => { + const isSelected = + currentPath === `/${tabName}` || + (currentPath === "/now-playing" && tabName === "now-playing"); + return `
`; + } + ); + + let movieModalHTML = ""; + if (isModal) { + const id = req.params.id; + const movieDetail = await fetchMovieDetails(id); + movieModalHTML = renderModal(movieDetail); + } + + const renderedHTML = replaceTemplatePlaceholders( + template, + headerHTML, + moviesHTML, + sectionTitles[currentPath], + movieModalHTML + ); res.send(renderedHTML); -}); +}; + +router.get("/", (req, res) => renderPage(req, res)); +router.get("/now-playing", (req, res) => renderPage(req, res)); +router.get("/popular", (req, res) => renderPage(req, res)); +router.get("/top-rated", (req, res) => renderPage(req, res)); +router.get("/upcoming", (req, res) => renderPage(req, res)); +router.get("/detail/:id", (req, res) => renderPage(req, res, true)); export default router; diff --git a/ssr/src/apis/movies.js b/ssr/src/apis/movies.js new file mode 100644 index 0000000..7218731 --- /dev/null +++ b/ssr/src/apis/movies.js @@ -0,0 +1,47 @@ +import { + FETCH_OPTIONS, + TMDB_MOVIE_DETAIL_URL, + TMDB_MOVIE_LISTS, +} from "../constants.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; +}; diff --git a/ssr/src/constants.js b/ssr/src/constants.js new file mode 100644 index 0000000..b26b9a7 --- /dev/null +++ b/ssr/src/constants.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/render/renderHeader.js b/ssr/src/render/renderHeader.js new file mode 100644 index 0000000..1e917fc --- /dev/null +++ b/ssr/src/render/renderHeader.js @@ -0,0 +1,31 @@ +const renderHeader = (bestMovie) => { + return /*html*/ ` +
+
+ +
+

+ MovieList +

+
+
+ + ${ + Math.round(bestMovie.vote_average * 10).toFixed(1) / 10 + } +
+
${bestMovie.title}
+ +
+
+
+
+ `; +}; + +export default renderHeader; diff --git a/ssr/src/render/renderModal.js b/ssr/src/render/renderModal.js new file mode 100644 index 0000000..95e95c3 --- /dev/null +++ b/ssr/src/render/renderModal.js @@ -0,0 +1,39 @@ +const renderModal = (movieDetail) => { + return /*html*/ ` + + + + `; +}; + +export default renderModal; diff --git a/ssr/src/render/renderMovieList.js b/ssr/src/render/renderMovieList.js new file mode 100644 index 0000000..1a02a7a --- /dev/null +++ b/ssr/src/render/renderMovieList.js @@ -0,0 +1,26 @@ +const renderMovieList = (movieItems = []) => + movieItems + .map( + ({ id, title, poster_path, vote_average }) => /*html*/ ` +
  • + +
    + ${title} +
    +

    ${ + Math.round(vote_average * 10).toFixed(1) / 10 + }

    + ${title} +
    +
    +
    +
  • + ` + ) + .join(""); + +export default renderMovieList; diff --git a/ssr/views/index.html b/ssr/views/index.html index a052396..76a8877 100644 --- a/ssr/views/index.html +++ b/ssr/views/index.html @@ -13,22 +13,7 @@
    -
    -
    - -
    -

    MovieList

    -
    -
    - - ${bestMovie.rate} -
    -
    ${bestMovie.title}
    - -
    -
    -
    -
    +
    • @@ -62,7 +47,7 @@

      상영 예정

    -

    지금 인기 있는 영화

    +

    ${sectionTitle}