From 10cb74ac676802343332db07c13f8b103632fbe7 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 20:53:52 +0900 Subject: [PATCH 1/6] =?UTF-8?q?chore:=20HTML=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 가장 인기있는 영화의 모달을 트리거하는 버튼과, 영화의 종류를 변경하는 탭 버튼들을 replace 함수 적용을 위한 플레이스 홀더로 수정 --- ssr/views/index.html | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/ssr/views/index.html b/ssr/views/index.html index a052396..4e12f01 100644 --- a/ssr/views/index.html +++ b/ssr/views/index.html @@ -14,51 +14,29 @@
-
+
-

MovieList

+

+ MovieList +

${bestMovie.rate}
${bestMovie.title}
- +
From 52b607855816a740e7901508c770fabdb5c767ac Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 21:01:00 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D,=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC?= =?UTF-8?q?=20=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20api=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssr/apis/movie.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 ssr/apis/movie.js diff --git a/ssr/apis/movie.js b/ssr/apis/movie.js new file mode 100644 index 0000000..44b66f8 --- /dev/null +++ b/ssr/apis/movie.js @@ -0,0 +1,24 @@ +import { + FETCH_OPTIONS, + TMDB_MOVIE_DETAIL_URL, + TMDB_MOVIE_LISTS, +} from "../constants/api.js"; + +export const fetchMovies = async (movieType) => { + const fetchEndPoint = TMDB_MOVIE_LISTS[movieType]; + + const response = await fetch(fetchEndPoint, FETCH_OPTIONS); + const allMovieData = await response.json(); + + return allMovieData.results; +}; + +export const fetchMovieDetail = async (movieId) => { + const url = `${TMDB_MOVIE_DETAIL_URL}${movieId}`; + const params = new URLSearchParams({ + language: "ko-KR", + }); + const response = await fetch(url + "?" + params, FETCH_OPTIONS); + + return await response.json(); +}; From 3edfd0fe0a326c2317ac05422389556242832b74 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 21:01:55 +0900 Subject: [PATCH 3/6] =?UTF-8?q?chore:=20=EC=98=81=ED=99=94=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=A5=BC=20=EA=B7=B8=EB=A6=AC=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=A7=A4=EC=A7=81=EB=84=98=EB=B2=84=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - window location path, api endpoint, replace placeholder, movie tabs --- ssr/constants/api.js | 22 ++++++++++++++++++++++ ssr/constants/path.js | 24 ++++++++++++++++++++++++ ssr/constants/placeholder.js | 9 +++++++++ ssr/constants/tabs.js | 18 ++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 ssr/constants/api.js create mode 100644 ssr/constants/path.js create mode 100644 ssr/constants/placeholder.js create mode 100644 ssr/constants/tabs.js diff --git a/ssr/constants/api.js b/ssr/constants/api.js new file mode 100644 index 0000000..dcd1f5e --- /dev/null +++ b/ssr/constants/api.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 = { + nowPlaying: BASE_URL + "/now_playing?language=ko-KR&page=1", + popular: BASE_URL + "/popular?language=ko-KR&page=1", + topRated: 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/constants/path.js b/ssr/constants/path.js new file mode 100644 index 0000000..d0bf975 --- /dev/null +++ b/ssr/constants/path.js @@ -0,0 +1,24 @@ +export const MOVIE_PAGE_PATH = { + home: "/", + nowPlaying: "/now-playing", + popular: "/popular", + topRated: "/top-rated", + upcoming: "/upcoming", + detail: "/detail/:movieId", +}; + +const switchMoviePaths = (originalPaths, rootPath) => { + const switchedPaths = {}; + + Object.entries(originalPaths).forEach(([key, value]) => { + if (value === "/") { + switchedPaths[value] = rootPath; + } else { + switchedPaths[value] = key; + } + }); + + return switchedPaths; +}; + +export const MOVIE_TYPE_KEY = switchMoviePaths(MOVIE_PAGE_PATH, "nowPlaying"); diff --git a/ssr/constants/placeholder.js b/ssr/constants/placeholder.js new file mode 100644 index 0000000..7b9198c --- /dev/null +++ b/ssr/constants/placeholder.js @@ -0,0 +1,9 @@ +export const HTML_PLACEHOLDERS = { + movieList: "", + movieTabs: "", + movieDetailModal: "", + bestMovieDetailTrigger: "", + bestMovieRate: "${bestMovie.rate}", + bestMovieTitle: "${bestMovie.title}", + bestMovieBackgroundImage: "${background-container}", +}; diff --git a/ssr/constants/tabs.js b/ssr/constants/tabs.js new file mode 100644 index 0000000..4369b37 --- /dev/null +++ b/ssr/constants/tabs.js @@ -0,0 +1,18 @@ +export const MOVIE_TABS = [ + { + href: "/now-playing", + label: "상영 중", + }, + { + href: "/popular", + label: "인기순", + }, + { + href: "/top-rated", + label: "평점순", + }, + { + href: "/upcoming", + label: "상영 에정", + }, +]; From 38b8098c70520452b750326d7950852e1fd7c2d4 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 21:02:25 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=ED=8E=98=EC=B9=AD=ED=95=98=EA=B3=A0=20HTM?= =?UTF-8?q?L=EC=9D=84=20=EA=B7=B8=EB=A6=AC=EB=8A=94=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssr/server/handler/movie-page.js | 13 +++++ ssr/server/render/movie-page.js | 93 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 ssr/server/handler/movie-page.js create mode 100644 ssr/server/render/movie-page.js diff --git a/ssr/server/handler/movie-page.js b/ssr/server/handler/movie-page.js new file mode 100644 index 0000000..30eab89 --- /dev/null +++ b/ssr/server/handler/movie-page.js @@ -0,0 +1,13 @@ +import { renderMoviePageHTML } from "../render/movie-page.js"; + +import { fetchMovies } from "../../apis/movie.js"; +import { MOVIE_TYPE_KEY } from "../../constants/path.js"; + +export const fetchAndRenderMoviePage = async (req, res) => { + const movieListType = MOVIE_TYPE_KEY[req.path]; + + const movieList = await fetchMovies(movieListType); + const renderedHTML = renderMoviePageHTML(req.path, movieList, movieList[0]); + + res.send(renderedHTML); +}; diff --git a/ssr/server/render/movie-page.js b/ssr/server/render/movie-page.js new file mode 100644 index 0000000..f49977b --- /dev/null +++ b/ssr/server/render/movie-page.js @@ -0,0 +1,93 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import { MOVIE_TABS } from "../../constants/tabs.js"; +import { MOVIE_PAGE_PATH } from "../../constants/path.js"; +import { TMDB_BANNER_URL } from "../../constants/api.js"; +import { HTML_PLACEHOLDERS } from "../../constants/placeholder.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export const renderMoviePageHTML = (currentPath, movieList = [], bestMovie) => { + const templatePath = path.join(__dirname, "../../views", "index.html"); + const template = fs.readFileSync(templatePath, "utf-8"); + + const { backdrop_path, vote_average, title, id: bestMovieId } = bestMovie; + const backdropImageUrl = `${TMDB_BANNER_URL}${backdrop_path}`; + const roundedRate = vote_average.toFixed(1); + + const movieListHTML = getMovieListHTML(movieList); + const movieTabsHTML = getMovieTabsHTML(currentPath); + const bestMovieDetailModalTriggerHTML = + getBestMovieDetailModalTriggerHtml(bestMovieId); + + return template + .replace(HTML_PLACEHOLDERS.bestMovieBackgroundImage, backdropImageUrl) + .replace( + HTML_PLACEHOLDERS.bestMovieDetailTrigger, + bestMovieDetailModalTriggerHTML + ) + .replace(HTML_PLACEHOLDERS.movieTabs, movieTabsHTML) + .replace(HTML_PLACEHOLDERS.bestMovieTitle, title) + .replace(HTML_PLACEHOLDERS.bestMovieRate, roundedRate) + .replace(HTML_PLACEHOLDERS.movieList, movieListHTML); +}; + +const checkIsSelectedTab = (currentPath, tabHref) => { + if ( + currentPath === MOVIE_PAGE_PATH.home && + tabHref === MOVIE_PAGE_PATH.nowPlaying + ) + return true; + + return currentPath !== MOVIE_PAGE_PATH.home && currentPath === tabHref; +}; + +const getBestMovieDetailModalTriggerHtml = (id) => { + return /*html*/ ``; +}; + +export const getMovieTabsHTML = (currentPath) => { + return MOVIE_TABS.map(({ href, label }) => { + const isSelectedTab = checkIsSelectedTab(currentPath, href); + + return /*html*/ ` +
  • + +
    +

    ${label}

    +
    +
    +
  • + `; + }).join(""); +}; + +export const getMovieListHTML = (movieList = []) => { + return movieList + .map(({ id, title, poster_path, vote_average }) => { + const thumbnailUrl = `${TMDB_BANNER_URL}${poster_path}`; + const roundedRate = vote_average.toFixed(1); + + return /*html*/ ` +
  • + +
    + ${title} +
    +

    ${roundedRate}

    + ${title} +
    +
    +
    +
  • + `; + }) + .join(""); +}; From 1569d1e83ff6c440d13e72a95cce9d957b556445 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 21:02:41 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EC=98=81=ED=99=94=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=ED=8E=98=EC=B9=AD?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20HTML=EC=9D=84=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=8A=94=20=EB=AA=A8=EB=93=88=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssr/server/handler/movie-detail-modal.js | 19 ++++++++ ssr/server/render/movie-detail-modal.js | 56 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 ssr/server/handler/movie-detail-modal.js create mode 100644 ssr/server/render/movie-detail-modal.js diff --git a/ssr/server/handler/movie-detail-modal.js b/ssr/server/handler/movie-detail-modal.js new file mode 100644 index 0000000..bdf724e --- /dev/null +++ b/ssr/server/handler/movie-detail-modal.js @@ -0,0 +1,19 @@ +import { renderMovieDetailModal } from "../render/movie-detail-modal.js"; +import { renderMoviePageHTML } from "../render/movie-page.js"; + +import { fetchMovieDetail, fetchMovies } from "../../apis/movie.js"; + +export const fetchAndRenderMovieDetailModal = async (req, res) => { + const { movieId } = req.params; + + const movieList = await fetchMovies("nowPlaying"); + const movieDetail = await fetchMovieDetail(movieId); + const moviePageTemplate = renderMoviePageHTML( + req.path, + movieList, + movieList[0] + ); + const renderedHTML = renderMovieDetailModal(moviePageTemplate, movieDetail); + + res.send(renderedHTML); +}; diff --git a/ssr/server/render/movie-detail-modal.js b/ssr/server/render/movie-detail-modal.js new file mode 100644 index 0000000..da3cffe --- /dev/null +++ b/ssr/server/render/movie-detail-modal.js @@ -0,0 +1,56 @@ +import { HTML_PLACEHOLDERS } from "../../constants/placeholder.js"; +import { TMDB_ORIGINAL_URL } from "../../constants/api.js"; +import { MOVIE_PAGE_PATH } from "../../constants/path.js"; + +export const renderMovieDetailModal = (moviePageTemplate, movieDetail) => { + const movieDetailHTML = getMovieDetailModalHTML(movieDetail); + + return moviePageTemplate.replace( + HTML_PLACEHOLDERS.movieDetailModal, + movieDetailHTML + ); +}; + +const getMovieDetailModalHTML = (movieDetail) => { + const { poster_path, title, genres, overview, vote_average, description } = + movieDetail; + + const genresNames = genres.map((g) => g.name); + const posterImageUrl = `${TMDB_ORIGINAL_URL}${poster_path}`; + const roundedRate = vote_average.toFixed(1); + + return /*html */ ` + + + + `; +}; From c4d3546631834b380db78e21e93b4a16de9c7953 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Thu, 3 Oct 2024 21:03:10 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20ssr=20routing=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ssr/server/index.js | 4 ++-- ssr/server/routes/index.js | 23 ++++++++++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ssr/server/index.js b/ssr/server/index.js index 09efd9a..5948d60 100644 --- a/ssr/server/index.js +++ b/ssr/server/index.js @@ -4,7 +4,9 @@ import path from "path"; import { fileURLToPath } from "url"; import movieRouter from "./routes/index.js"; + // import membersRouter from "./routes/members.js"; // 본 미션 참고를 위한 코드이며 사전 미션에서는 사용하지 않습니다. +// app.use("/members", membersRouter); // 본 미션 참고를 위한 코드이며 사전 미션에서는 사용하지 않습니다. 뉑~~~ const app = express(); const PORT = 3000; @@ -13,9 +15,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); app.use("/assets", express.static(path.join(__dirname, "../public"))); - app.use("/", movieRouter); -// app.use("/members", membersRouter); // 본 미션 참고를 위한 코드이며 사전 미션에서는 사용하지 않습니다. // Start server app.listen(PORT, () => { diff --git a/ssr/server/routes/index.js b/ssr/server/routes/index.js index 84d32f2..d86e0ee 100644 --- a/ssr/server/routes/index.js +++ b/ssr/server/routes/index.js @@ -1,21 +1,18 @@ import { Router } from "express"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +import { MOVIE_PAGE_PATH } from "../../constants/path.js"; -const router = Router(); +import { fetchAndRenderMovieDetailModal } from "../handler/movie-detail-modal.js"; +import { fetchAndRenderMoviePage } from "../handler/movie-page.js"; -router.get("/", (_, res) => { - const templatePath = path.join(__dirname, "../../views", "index.html"); - const moviesHTML = "

    들어갈 본문 작성

    "; +const router = Router(); - const template = fs.readFileSync(templatePath, "utf-8"); - const renderedHTML = template.replace("", moviesHTML); +router.get(MOVIE_PAGE_PATH.home, fetchAndRenderMoviePage); +router.get(MOVIE_PAGE_PATH.nowPlaying, fetchAndRenderMoviePage); +router.get(MOVIE_PAGE_PATH.popular, fetchAndRenderMoviePage); +router.get(MOVIE_PAGE_PATH.topRated, fetchAndRenderMoviePage); +router.get(MOVIE_PAGE_PATH.upcoming, fetchAndRenderMoviePage); - res.send(renderedHTML); -}); +router.get(MOVIE_PAGE_PATH.detail, fetchAndRenderMovieDetailModal); export default router;