diff --git a/client/src/App.jsx b/client/src/App.jsx index 6b242a6..3968464 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -8,6 +8,7 @@ import Profile from "./pages/Profile"; import useAuthLoader from "./hooks/useAuthLoader"; import MoviePage from "./pages/MoviePage"; import Navbar from "./components/Navbar"; +import TmdbMovie from "./pages/MoviesPage"; function isTokenExpired(token) { if (!token) return true; @@ -79,6 +80,15 @@ export default function App() { } /> + + : + } + /> + { + const res = await axiosInstance.get('/movies/trending'); + return res; +} \ No newline at end of file diff --git a/client/src/components/HistoryTab.jsx b/client/src/components/HistoryTab.jsx index 90755cc..d874d20 100644 --- a/client/src/components/HistoryTab.jsx +++ b/client/src/components/HistoryTab.jsx @@ -83,11 +83,11 @@ export default function HistoryTab() { > {/* THE TIMELINE HEADER */} - + {date} - + {/* MOVIE GRID */} diff --git a/client/src/components/Tabs.jsx b/client/src/components/Tabs.jsx index d0eee77..69fc474 100644 --- a/client/src/components/Tabs.jsx +++ b/client/src/components/Tabs.jsx @@ -70,7 +70,7 @@ export default function Tabs({ profileData, isMyProfile }) { {/* 2. CONTENT AREA */} - + {loading ? ( - {/* Cinematic Vignette Overlay */} - + {/* Vignette Overlay */} + - {/* Rating Badge - Updated to Glassmorphism */} + {/* Rating Badge */} {rating && ( diff --git a/client/src/pages/MoviePage.jsx b/client/src/pages/MoviePage.jsx index dd8f003..e08e6bc 100644 --- a/client/src/pages/MoviePage.jsx +++ b/client/src/pages/MoviePage.jsx @@ -1,8 +1,7 @@ import { useEffect, useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; +import { motion } from "framer-motion"; import { getMovieById } from "../api/movie.api"; import Loader from "../components/ui/Loader"; -import { useParams } from "react-router-dom"; export default function MoviePage() { const [movieData, setMovieData] = useState(null); diff --git a/client/src/pages/MoviesPage.jsx b/client/src/pages/MoviesPage.jsx new file mode 100644 index 0000000..5e6095f --- /dev/null +++ b/client/src/pages/MoviesPage.jsx @@ -0,0 +1,150 @@ +import { useEffect, useState, useRef } from "react"; +import { motion } from "framer-motion"; +import { getTrendingMovies } from "../api/tmdb.api"; +import MovieCard from "../components/ui/MovieCard"; +import Loader from "../components/ui/Loader"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; + +export default function MoviesPage() { + const [loading, setLoading] = useState(true); + const [trendingMovies, setTrendingMovies] = useState([]); + const [featuredMovie, setFeaturedMovie] = useState(null); + + useEffect(() => { + const fetchTrending = async () => { + try { + const res = await getTrendingMovies(); + const movies = res.data || []; + setTrendingMovies(movies); + + if (movies.length > 0) { + const randomIndex = Math.floor(Math.random() * movies.length); + setFeaturedMovie(movies[randomIndex]); + } + } catch (error) { + console.error("Error fetching trending movies: ", error); + } finally { + setLoading(false); + } + }; + + fetchTrending(); + }, []); + + if (loading) return ; + + return ( + + + {/* 1. FEATURED HERO SECTION */} + + + + {/* Gradient & Content Overlay */} + + + + Featured Spotlight + + + {featuredMovie?.title || featuredMovie?.name} + + + {featuredMovie?.overview} + + + + + + + {/* 2. TRENDING SECTION */} + + + + ); +} + +function MovieSlider({ title, subtitle, movies }) { + const scrollRef = useRef(null); + + const scroll = (direction) => { + if (scrollRef.current) { + const { scrollLeft, clientWidth } = scrollRef.current; + const scrollAmount = clientWidth * 0.8; + const scrollTo = direction === "left" ? scrollLeft - scrollAmount : scrollLeft + scrollAmount; + scrollRef.current.scrollTo({ left: scrollTo, behavior: "smooth" }); + } + }; + + if (!movies || movies.length === 0) return null; + + return ( + + + + + + {subtitle} + + {title} + + + + scroll("left")} + className="w-12 h-12 rounded-2xl bg-white border border-stone-200 flex items-center justify-center text-stone-400 hover:text-stone-900 hover:border-stone-400 active:scale-95 transition-all shadow-sm" + > + + + scroll("right")} + className="w-12 h-12 rounded-2xl bg-white border border-stone-200 flex items-center justify-center text-stone-400 hover:text-stone-900 hover:border-stone-400 active:scale-95 transition-all shadow-sm" + > + + + + + + + {movies.map((movie) => ( + + + + ))} + + + ); +} \ No newline at end of file diff --git a/client/src/pages/Profile.jsx b/client/src/pages/Profile.jsx index 171bf36..c7ad3b9 100644 --- a/client/src/pages/Profile.jsx +++ b/client/src/pages/Profile.jsx @@ -190,7 +190,7 @@ export default function Profile() { {/* Modal for Cover Selection */} - + {/* Header */} @@ -208,7 +208,6 @@ export default function Profile() { - {/* Content Area */} {banners.length > 0 ? ( @@ -219,11 +218,11 @@ export default function Profile() { animate={{ opacity: 1, y: 0 }} transition={{ delay: index * 0.05 }} whileHover={{ scale: 1.02 }} - className={`group relative aspect-video rounded-[2rem] overflow-hidden cursor-pointer bg-stone-200 border-4 + className={`group relative aspect-video rounded-4xl overflow-hidden cursor-pointer bg-stone-200 border-4 ${selectedBackdrop === banner?.backdropPath ? "border-amber-500/70" : "border-white"} shadow-xl transition-all hover:shadow-amber-500/20 hover:border-amber-500/40`} onClick={() => { - setSelectedBackdrop(banner?.backdropPath); // β Select banner + setSelectedBackdrop(banner?.backdropPath); }} > - + @@ -263,7 +262,6 @@ export default function Profile() { )} - {/* β Save & Cancel Buttons */} {selectedBackdrop && ( Save Cover diff --git a/server/app.js b/server/app.js index 19f705f..e2d5b0a 100644 --- a/server/app.js +++ b/server/app.js @@ -12,20 +12,27 @@ import reviewRouter from './routes/review.route.js'; import commentRouter from './routes/comment.route.js'; import profileRouter from './routes/features/profile.route.js'; import movieRouter from './routes/movie.route.js'; +import tmdbRouter from './routes/tmdb.routes.js'; +import "./cron/trending.cron.js"; +import { fetchAndStoreTrending } from './services/trending.service.js'; const app = express(); app.use(cors({ - origin: "http://localhost:5173", // allow your React frontend + origin: "http://localhost:5173", methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], - credentials: true // if youβre sending cookies + credentials: true })); app.use(express.json()); app.use(express.urlencoded({ extended: false})); +// Remove this line when deployed, as the cron will handle it +fetchAndStoreTrending().then(() => console.log("π₯ Initial trending fetched")); + app.use(`${BASE_URL}/auth`, authRouter); app.use(`${BASE_URL}/user`, userRouter); +app.use(`${BASE_URL}/movies`, tmdbRouter); app.use(`${BASE_URL}/movies`, movieRouter); app.use(`${BASE_URL}/history`, historyRouter); app.use(`${BASE_URL}/watchlist`, watchListRouter); diff --git a/server/config/env.js b/server/config/env.js index f4db4ea..271689a 100644 --- a/server/config/env.js +++ b/server/config/env.js @@ -1,43 +1,3 @@ -// // import { config } from 'dotenv'; - -// // config({ path: `.env.${process.env.NODE_ENV || 'development'}.local`}); - -// // export const { -// // PORT, -// // BASE_URL, -// // NODE_ENV, -// // DB_URI, -// // JWT_SECRET, -// // JWT_EXPIRES_IN, -// // TMDB_BASE_URL, -// // TMDB_KEY -// // } = process.env; - -// import { config } from 'dotenv'; - -// // Choose env file based on NODE_ENV -// let envFile = ".env.development.local"; // default - -// if (process.env.NODE_ENV === "production") { -// envFile = ".env.production.local"; -// } else if (process.env.NODE_ENV === "test") { -// envFile = ".env.test.local"; -// } - -// config({ path: envFile }); - -// export const { -// PORT, -// BASE_URL, -// NODE_ENV, -// DB_URI, -// JWT_SECRET, -// JWT_EXPIRES_IN, -// TMDB_BASE_URL, -// TMDB_KEY -// } = process.env; - - import { config } from 'dotenv'; const env = process.env.NODE_ENV || "development"; config({ path: `.env.${env}.local` }); diff --git a/server/controllers/history.controller.js b/server/controllers/history.controller.js index dc15243..faa6bae 100644 --- a/server/controllers/history.controller.js +++ b/server/controllers/history.controller.js @@ -1,7 +1,7 @@ import mongoose from "mongoose"; import History from "../models/history.model.js"; import WatchList from "../models/watchList.model.js"; -import { getOrCreateMovie } from "../utils/movie.utils.js"; +import { getOrCreateMovie } from "../services/movie.service.js"; /** * Get current user's watched movies diff --git a/server/controllers/trending.controller.js b/server/controllers/trending.controller.js new file mode 100644 index 0000000..8e58b60 --- /dev/null +++ b/server/controllers/trending.controller.js @@ -0,0 +1,21 @@ +import TrendingMovies from "../models/trending.model.js"; + +export const getTrendingMovies = async (req, res) => { + try { + const today = new Date().toISOString().split("T")[0]; + + let trending = await TrendingMovies.findOne({ date: today }) + .populate("movies"); + + // π₯ fallback if today not available + if (!trending) { + trending = await TrendingMovies.findOne() + .sort({ createdAt: -1 }) + .populate("movies"); + } + + return res.status(200).json(trending?.movies || []); + } catch (err) { + return res.status(500).json({ message: err.message }); + } +}; \ No newline at end of file diff --git a/server/controllers/watchList.controller.js b/server/controllers/watchList.controller.js index 3f7ae73..2de0214 100644 --- a/server/controllers/watchList.controller.js +++ b/server/controllers/watchList.controller.js @@ -1,6 +1,6 @@ import WatchList from '../models/watchList.model.js'; import History from '../models/history.model.js'; -import { getOrCreateMovie } from '../utils/movie.utils.js'; +import { getOrCreateMovie } from '../services/movie.service.js'; export const getWatchListMovie = async (req, res) => { try { diff --git a/server/cron/trending.cron.js b/server/cron/trending.cron.js new file mode 100644 index 0000000..c44be76 --- /dev/null +++ b/server/cron/trending.cron.js @@ -0,0 +1,8 @@ +import cron from "node-cron"; +import { fetchAndStoreTrending } from "../services/trending.service.js"; + +// runs every day at midnight +cron.schedule("0 0 * * *", async () => { + console.log("β³ Running trending cron..."); + await fetchAndStoreTrending(); +}); \ No newline at end of file diff --git a/server/models/review.model.js b/server/models/review.model.js index fde280d..70453e0 100644 --- a/server/models/review.model.js +++ b/server/models/review.model.js @@ -5,8 +5,8 @@ const reviewSchema = new mongoose.Schema({ movieId: { type: mongoose.Schema.Types.ObjectId, ref: 'Movie', required: true }, review: { type: String, required: true }, likedBy: { type: [mongoose.Schema.Types.ObjectId], ref: 'User', default: [] }, - createdAt: { type: Date, default: Date.now() }, - updatedAt: { type: Date, default: Date.now() } + createdAt: { type: Date, default: Date.now }, + updatedAt: { type: Date, default: Date.now } }, { timestamps: true }); export default mongoose.model("Review", reviewSchema); \ No newline at end of file diff --git a/server/models/trending.model.js b/server/models/trending.model.js new file mode 100644 index 0000000..a9a200b --- /dev/null +++ b/server/models/trending.model.js @@ -0,0 +1,10 @@ +import mongoose from "mongoose"; + +const trendingMoviesSchema = new mongoose.Schema({ + movies: [ + { type: mongoose.Schema.Types.ObjectId, ref: "Movie" } + ], + date: { type: String, required: true, unique: true } +}, { timestamps: true }); + +export default mongoose.model("TrendingMovies", trendingMoviesSchema); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 8a11e61..5623f0b 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -17,7 +17,8 @@ "express": "^4.22.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.0", - "morgan": "^1.10.1" + "morgan": "^1.10.1", + "node-cron": "^4.2.1" }, "devDependencies": { "@babel/core": "^7.29.0", @@ -3351,9 +3352,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -3714,9 +3715,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -5183,9 +5184,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -5500,9 +5501,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -7737,6 +7738,15 @@ "node": ">= 0.6" } }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -7812,9 +7822,9 @@ } }, "node_modules/nodemon/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8243,9 +8253,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/picocolors": { @@ -8256,9 +8266,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8527,9 +8537,9 @@ } }, "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { diff --git a/server/package.json b/server/package.json index 0973626..566510e 100644 --- a/server/package.json +++ b/server/package.json @@ -17,7 +17,8 @@ "express": "^4.22.1", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.0", - "morgan": "^1.10.1" + "morgan": "^1.10.1", + "node-cron": "^4.2.1" }, "devDependencies": { "@babel/core": "^7.29.0", diff --git a/server/routes/tmdb.routes.js b/server/routes/tmdb.routes.js new file mode 100644 index 0000000..02b2702 --- /dev/null +++ b/server/routes/tmdb.routes.js @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import authorize from '../middlewares/auth.middleware.js'; +import { getTrendingMovies } from '../controllers/trending.controller.js'; + +const tmdbRouter = Router(); + +tmdbRouter.get('/trending', authorize, getTrendingMovies); + +export default tmdbRouter; \ No newline at end of file diff --git a/server/services/movie.service.js b/server/services/movie.service.js new file mode 100644 index 0000000..5e4b365 --- /dev/null +++ b/server/services/movie.service.js @@ -0,0 +1,46 @@ +import axios from 'axios'; + +import Movie from '../models/movie.model.js'; +import { TMDB_BASE_URL, TMDB_KEY } from '../config/env.js'; +import { genreMap } from '../utils/movieGenre.utils.js'; + +/** + * Get or create a Movie document from TMDb + * @param {string} title + * @returns Movie document + */ +export const getOrCreateMovie = async (title) => { + // Check DB + let movieData = await Movie.findOne({ title }); + if (movieData) return movieData; + + // Only fetch from TMDb if not found + const tmdbRes = await axios.get(`${TMDB_BASE_URL}/search/movie`, { + params: { api_key: TMDB_KEY, query: title } + }); + + const results = tmdbRes.data.results; + if (!results.length) return null; + + const tmdbMovie = results[0]; + + movieData = await Movie.create({ + movieId: tmdbMovie.id, + adult: tmdbMovie.adult, + backdropPath: tmdbMovie.backdrop_path, + genreIds: tmdbMovie.genre_ids, + genreNames: tmdbMovie.genre_ids.map(id => genreMap[id]), + originalLanguage: tmdbMovie.original_language, + originalTitle: tmdbMovie.original_title, + overview: tmdbMovie.overview, + popularity: tmdbMovie.popularity, + posterPath: tmdbMovie.poster_path, + releaseDate: tmdbMovie.release_date, + title: tmdbMovie.title, + video: tmdbMovie.video, + voteAverage: tmdbMovie.vote_average, + voteCount: tmdbMovie.vote_count + }); + + return movieData; +}; \ No newline at end of file diff --git a/server/services/trending.service.js b/server/services/trending.service.js new file mode 100644 index 0000000..cd8b50a --- /dev/null +++ b/server/services/trending.service.js @@ -0,0 +1,60 @@ +import axios from "axios"; +import { TMDB_BASE_URL, TMDB_KEY } from "../config/env.js"; +import Movie from "../models/movie.model.js"; +import TrendingMovies from "../models/trending.model.js"; +import { genreMap } from '../utils/movieGenre.utils.js'; + +export const fetchAndStoreTrending = async () => { + try { + const today = new Date().toISOString().split('T')[0]; + + const existing = await TrendingMovies.findOne({ date: today }); + if (existing) return; + + const res = await axios.get(`${TMDB_BASE_URL}/trending/movie/day`, { + params: { api_key: TMDB_KEY } + }); + + const moviesFromTMDB = res.data.results; + + console.log(moviesFromTMDB); + + const moviesDocs = []; + + for (const movie of moviesFromTMDB) { + const doc = await Movie.findOneAndUpdate( + { movieId: movie.id }, + { + movieId: movie.id, + adult: movie.adult, + backdropPath: movie.backdrop_path, + genreIds: movie.genre_ids, + genreNames: movie.genre_ids.map(id => genreMap[id]), + originalLanguage: movie.original_language, + originalTitle: movie.original_title, + overview: movie.overview, + popularity: movie.popularity, + posterPath: movie.poster_path, + releaseDate: movie.release_date, + title: movie.title, + video: movie.video, + voteAverage: movie.vote_average, + voteCount: movie.vote_count + }, + { upsert: true, returnDocument: 'after' } + ); + + moviesDocs.push(doc._id); + } + + await TrendingMovies.create({ + movies: moviesDocs, + date: today + }); + + console.log("β Trending movies updated"); + + } catch (error) { + console.error("Trending cron failed: ", error.message); + } +} \ No newline at end of file diff --git a/server/utils/movie.utils.js b/server/utils/movie.utils.js deleted file mode 100644 index 68d9397..0000000 --- a/server/utils/movie.utils.js +++ /dev/null @@ -1,51 +0,0 @@ -import axios from 'axios'; - -import Movie from '../models/movie.model.js'; -import { TMDB_BASE_URL, TMDB_KEY } from '../config/env.js'; - -const genreMap = { - 28: "Action", 12: "Adventure", 16: "Animation", 35: "Comedy", - 80: "Crime", 99: "Documentary", 18: "Drama", 10751: "Family", - 14: "Fantasy", 36: "History", 27: "Horror", 10402: "Music", - 9648: "Mystery", 10749: "Romance", 878: "Science Fiction", - 10770: "TV Movie", 53: "Thriller", 10752: "War", 37: "Western" -}; - -/** - * Get or create a Movie document from TMDb - * @param {string} title - * @returns Movie document - */ -export const getOrCreateMovie = async (title) => { - const tmdbRes = await axios.get(`${TMDB_BASE_URL}/search/movie`, { - params: { api_key: TMDB_KEY, query: title } - }); - - const results = tmdbRes.data.results; - if (!results.length) return null; - - const tmdbMovie = results[0]; - - let movieData = await Movie.findOne({ movieId: tmdbMovie.id }); - if (!movieData) { - movieData = await Movie.create({ - movieId: tmdbMovie.id, - adult: tmdbMovie.adult, - backdropPath: tmdbMovie.backdrop_path, - genreIds: tmdbMovie.genre_ids, - genreNames: tmdbMovie.genre_ids.map(id => genreMap[id]), - originalLanguage: tmdbMovie.original_language, - originalTitle: tmdbMovie.original_title, - overview: tmdbMovie.overview, - popularity: tmdbMovie.popularity, - posterPath: tmdbMovie.poster_path, - releaseDate: tmdbMovie.release_date, - title: tmdbMovie.title, - video: tmdbMovie.video, - voteAverage: tmdbMovie.vote_average, - voteCount: tmdbMovie.vote_count - }); - } - - return movieData; -}; \ No newline at end of file diff --git a/server/utils/movieGenre.utils.js b/server/utils/movieGenre.utils.js new file mode 100644 index 0000000..c144d10 --- /dev/null +++ b/server/utils/movieGenre.utils.js @@ -0,0 +1,7 @@ +export const genreMap = { + 28: "Action", 12: "Adventure", 16: "Animation", 35: "Comedy", + 80: "Crime", 99: "Documentary", 18: "Drama", 10751: "Family", + 14: "Fantasy", 36: "History", 27: "Horror", 10402: "Music", + 9648: "Mystery", 10749: "Romance", 878: "Science Fiction", + 10770: "TV Movie", 53: "Thriller", 10752: "War", 37: "Western" +}; \ No newline at end of file
+ {featuredMovie?.overview} +