Skip to content

Commit

Permalink
Merge pull request #12 from justinhartman/develop
Browse files Browse the repository at this point in the history
Merge watchlist and style changes to main
  • Loading branch information
justinhartman authored Jun 4, 2024
2 parents 9dd770c + b5a28a0 commit 2156567
Show file tree
Hide file tree
Showing 28 changed files with 654 additions and 427 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ Application and then follow these steps.
sudo systemctl restart binger.service
```

5. You can now test your app URL paths like `./register`, `./login` and `./profile` paths to see if data is being
stored correctly.
5. You can now test your app URL paths like `./user/register`, `./user/login` and `./user/profile` paths to see if data
is being stored correctly.

## OMDb API

Expand Down
20 changes: 12 additions & 8 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ const app = express();
const path = require('path');

const appConfig = require('./config/app');
const connectDB = require('./config/db');
const appHelper = require('./helpers/appHelper');
const appRoutes = require('./routes/app');
const authRoutes = require('./routes/auth');

/**
* Middleware function that sets the APP_URL as a local variable for the views.
Expand Down Expand Up @@ -44,13 +43,18 @@ app.set('views', path.join(__dirname, 'views'));
app.use(express.static('public'));

/**
* Conditionally uses the provided routes middleware for the root path ('/').
* If MONGO_DB_URI is not empty, it uses both appRoutes and authRoutes middlewares.
* Otherwise, it only uses appRoutes middleware.
* @param {Object} routes - The routes middleware to be used for the root path.
* @returns {void} - No return value.
* Load standard routes and conditionally use additional routes based on the value of useAuth boolean.
* The method checks if MONGO_DB_URI is true then connects to MongoDB and uses additional middleware.
*/
appConfig.MONGO_DB_URI !== '' ? app.use('/', appRoutes, authRoutes) : app.use('/', appRoutes);
app.use('/', require('./routes/app'));
// Test if MONGO_DB_URI is set.
if (appHelper.useAuth) {
// Connect to MongoDB instance.
connectDB().catch(e => console.log(e.message));
// Use additional routes.
app.use('/user', require('./routes/auth'));
app.use('/watchlist', require('./routes/watchlist'));
}

/**
* Starts the server and listens on the specified port.
Expand Down
95 changes: 95 additions & 0 deletions controllers/appController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const axios = require("axios");
const asyncHandler = require('express-async-handler');

const {
fetchOmdbData,
fetchAndUpdatePosters
} = require('../helpers/appHelper');

/**
* @module appController
* @description This module contains the standard application controller functions.
*/
const appController = {
/**
* @function getHome
* @memberof appController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This handles the rendering the home page with new movies and TV shows.
* It sets the canonical URL and renders the 'index' template with the specified parameters.
* @return {Promise<void>}
*/
getHome: asyncHandler(async (req, res) => {
const query = req.query.q || '';
const type = req.query.type || 'ovie';
const canonical = res.locals.APP_URL;
let newMovies = [];
let newSeries = [];

/**
* Fetch new movies from VidSrc.
* You can switch to new movies instead of the default 'added' with 'https://vidsrc.to/vapi/movie/new'
* @type {axios.AxiosResponse<any>}
* @docs https://vidsrc.to/#api
*/
const axiosMovieResponse = await axios.get('https://vidsrc.to/vapi/movie/add');
newMovies = axiosMovieResponse.data.result.items || [];
await fetchAndUpdatePosters(newMovies);

/**
* Fetch new TV shows from VidSrc.
* You can switch to new movies instead of the default 'added' with 'https://vidsrc.to/vapi/tv/new'
* @type {axios.AxiosResponse<any>}
* @docs https://vidsrc.to/#api
*/
const axiosSeriesResponse = await axios.get('https://vidsrc.to/vapi/tv/add');
newSeries = axiosSeriesResponse.data.result.items || [];
await fetchAndUpdatePosters(newSeries);

res.render('index', { newMovies, newSeries, query, type, canonical, card: res.locals.CARD_TYPE, user: req.user });
}),

/**
* @function getView
* @memberof appController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the rendering of a movie or TV video player page.
* It sets the canonical URL and renders the 'view' template and returns the iFrame VidSrc URL
* along with parsed OMDB data for the template.
* @returns {Promise<void>}
*/
getView: asyncHandler(async (req, res) => {
const query = req.params.q || '';
const id = req.params.id;
let type = req.params.type;
let t = 'movie';
if (type === 'series') t = 'tv'
const iframeSrc = `https://vidsrc.to/embed/${t}/${id}`;
const canonical = `${res.locals.APP_URL}/view/${id}/${type}`;
const data = await fetchOmdbData(id, false);
res.render('view', { data, iframeSrc, query, id, type, canonical, user: req.user });
}),

/**
* @function getSearch
* @memberof appController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the rendering of the search results page.
* It sets the canonical URL and renders the 'search' template with the specified parameters.
* @return {Promise<void>}
*/
getSearch: asyncHandler(async (req, res) => {
const query = req.query.q.trim();
const type = req.query.type || 'movie';
const omdbSearch = await fetchOmdbData(query, true, type);
const results = omdbSearch.Search || [];
const canonical = `${res.locals.APP_URL}/search/?q=${query}&type=${type}`;
if (!query) res.redirect('/');
res.render('search', { query, results, type, canonical, card: res.locals.CARD_TYPE, user: req.user });
})
};

module.exports = appController;
87 changes: 65 additions & 22 deletions controllers/authController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const passport = require('passport');

require('../config/passport')(passport);
const User = require('../models/User');

Expand All @@ -9,57 +8,101 @@ const User = require('../models/User');
*/
const authController = {
/**
* @function register
* @function getRegister
* @memberof authController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the rendering of the registration page.
* It sets the canonical URL and renders the 'register' template with the specified parameters.
* @return {Promise<void>}
*/
getRegister: async (req, res) => {
const canonical = `${res.locals.APP_URL}/user/register`;
res.render('register', { canonical, query: '', type: 'movie' });
},

/**
* @function postRegister
* @memberof authController
* @param {Object} req - Express request object.
* @param {Object} res - Express response object.
* @description This function handles the registration of a new user. It checks if the user already exists, creates a new user if not, and redirects accordingly.
* @returns {void}
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the registration of a new user.
* It checks if the user already exists, creates a new user if not, and redirects accordingly.
* @return {Promise<void>}
*/
register: async (req, res) => {
postRegister: async (req, res) => {
try {
const { username, password } = req.body;
let user = await User.findOne({ username });
if (user) return res.redirect('/register');
if (user) return res.redirect('/user/register');
user = new User({ username, password });
await user.save();
req.flash('success_msg', 'You are now registered and can log in');
res.redirect('/login');
res.redirect('/user/login');
} catch (error) {
req.flash('error_msg', `Failed to register. ${error.message}`);
res.redirect('/register');
res.redirect('/user/register');
}
},

/**
* @function getLogin
* @memberof authController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the rendering of the login page.
* It sets the canonical URL and renders the 'login' template with the specified parameters.
* @return {Promise<void>}
*/
getLogin: async (req, res) => {
const canonical = `${res.locals.APP_URL}/user/login`;
res.render('login', { canonical, query: '', type: 'movie' });
},

/**
* @function login
* @memberof authController
* @param {Object} req - Express request object.
* @param {Object} res - Express response object.
* @description This function handles the login process using Passport. It redirects to the profile page on successful authentication, and to the login page on failure.
* @returns {void}
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the login process using Passport.
* It redirects to the profile page on successful authentication, and to the login page on failure.
* @return {Promise<void>}
*/
login: async (req, res) => {
postLogin: async (req, res) => {
try {
await passport.authenticate('local', {
successRedirect: '/profile',
failureRedirect: '/login',
successRedirect: '/user/profile',
failureRedirect: '/user/postLogin',
failureFlash: true,
})(req, res);
} catch (error) {
req.flash('error_msg', `Failed to authenticate. ${error.message}`);
res.redirect('/login');
res.redirect('/user/login');
}
},

/**
* @function getProfile
* @memberof authController
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @description This function handles the rendering of the user profile page.
* It sets the canonical URL, retrieves the user's watchlist, and renders the 'profile' template with the specified parameters.
* @return {Promise<void>}
*/
getProfile: async (req, res) => {
const canonical = `${res.locals.APP_URL}/user/profile`;
// const watchlist = await Watchlist.find({ userId: req.user.id });
res.render('profile', { canonical, query: '', type: 'movie', user: req.user });
},

/**
* @function logout
* @memberof authController
* @param {Object} req - Express request object.
* @param {Object} res - Express response object.
* @param {Function} next - Express next middleware function.
* @param {Request} req - Express request object.
* @param {Response} res - Express response object.
* @param {NextFunction} next - Express next middleware function.
* @description This function handles the logout process. It logs the user out and redirects to the home page.
* @returns {void}
*/
logout: (req, res, next) => {
req.logout((error) => {
Expand Down
68 changes: 46 additions & 22 deletions controllers/watchlistController.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
const User = require('../models/User');
const Watchlist = require('../models/Watchlist');

/**
* @module watchlistController
* @description This module contains functions for managing a user's watchlist.
*/
const watchlistController = {
/**
* @function getWatchlist
* @description Retrieves the user's watchlist from the database.
* @param {Request} req - The request object containing the user's ID.
* @param {Response} res - The response object.
* @returns {Promise<void>} - A promise that resolves when the watchlist is retrieved or an error occurs.
*/
getWatchlist: async (req, res) => {
try {
const canonical = `${res.locals.APP_URL}/watchlist`;
const watchlist = await Watchlist.find({ userId: req.user.id });
if (!watchlist) {
req.flash('error_msg', 'No watchlist found.');
res.redirect('/watchlist');
}
res.render('watchlist', { watchlist, canonical, query: '', type: 'movie' });
} catch (error) {
req.flash('error_msg', `Failed to retrieve watchlist. ${error.message}`);
res.redirect('/watchlist');
}
},

/**
* @function addToWatchlist
* @description Adds a movie or TV show to the user's watchlist.
Expand All @@ -13,19 +35,19 @@ const watchlistController = {
* @returns {Promise<void>} - A promise that resolves when the movie or TV show is added to the watchlist.
*/
addToWatchlist: async (req, res) => {
const { imdbID, title, poster, type } = req.body;
const { imdbId, title, poster, type } = req.body;
try {
const user = await User.findById(req.user.id);
if (!user.watchlist.some(item => item.imdbID === imdbID)) {
user.watchlist.push({ imdbID, title, poster, type });
await user.save();
let watchlist = await Watchlist.findOne({ userId: req.user.id });
if (!watchlist) watchlist = new Watchlist({ userId: req.user.id, items: [] });
if (!watchlist.items.some(item => item.imdbId === imdbId)) {
watchlist.items.push({ imdbId, title, poster, type });
await watchlist.save();
}
req.flash('success_msg', `Added ${title} to watchlist.`);
res.redirect('/profile');
res.redirect('/watchlist');
} catch (error) {
console.error(error.message);
req.flash('error_msg', `Failed to add to watchlist. ${error.message}`);
res.redirect('/');
res.redirect('/watchlist');
}
},

Expand All @@ -37,19 +59,21 @@ const watchlistController = {
* @returns {Promise<void>} - A promise that resolves when the movie or TV show is removed from the watchlist.
*/
deleteFromWatchlist: async (req, res) => {
const { imdbID } = req.body;
const user = await User.findById(req.user.id);
if (user.watchlist.some(item => item.imdbID === imdbID)) {
await User.findByIdAndUpdate(
user.id,
{ $pull: { watchlist: { imdbID: imdbID } } },
{ new: true }
);
req.flash('success_msg', 'Removed item from your watchlist.');
res.redirect('/profile');
} else {
req.flash('error_msg', 'Could not find item in your watchlist.');
res.redirect('/profile');
const { imdbId } = req.body;
try {
const watchlist = await Watchlist.findOne({ userId: req.user.id });
if (watchlist && watchlist.items.some(item => item.imdbId === imdbId)) {
watchlist.items = watchlist.items.filter(item => item.imdbId !== imdbId);
await watchlist.save();
req.flash('success_msg', 'Removed item from your watchlist.');
res.redirect('/watchlist');
} else {
req.flash('error_msg', 'Could not find item in your watchlist.');
res.redirect('/watchlist');
}
} catch (error) {
req.flash('error_msg', `Failed to remove from watchlist. ${error.message}`);
res.redirect('/watchlist');
}
}
};
Expand Down
24 changes: 24 additions & 0 deletions docs/tags/1.0.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<img align="right" src="https://github.com/justinhartman/imdb-app/raw/main/public/images/favicons/apple-touch-icon.png" />

# IMDb Movie & TV Search Engine WebApp

_This file contains the changelog revisions for the IMDb Movie & TV Search Engine WebApp._

### Bug Fixes

- [42b0cf61](https://github.com/justinhartman/imdb-app/commit/42b0cf61982264da7a7fad7c37c74a166fd98c5d): Fix error where DB connects to test DB incorrectly

### Core Updates

- [e1054ca2](https://github.com/justinhartman/imdb-app/commit/e1054ca297137ed28d1c2ff12a38d124d3e5a6e9): Set mongoose to connect on IPV4 by default

### Yarn Package Updates

- [9f1c5d7f](https://github.com/justinhartman/imdb-app/commit/9f1c5d7fb98700b73b3d187cbd7a4bf4881e6a7b): Remove duplicate script command

### Pull Requests Merged

- [9dd770cf](https://github.com/justinhartman/imdb-app/commit/9dd770cf93655715d720a535963258b969cf1db4): Merge pull request #10 from justinhartman/develop
- [a085865f](https://github.com/justinhartman/imdb-app/commit/a085865f793b4ad2462f0d10940f62cc0c85452d): Merge pull request #9 from justinhartman/develop
- [c4bacfaf](https://github.com/justinhartman/imdb-app/commit/c4bacfaff39cb1bb3fd9d10af0d7d6c61c3b59ea): Merge pull request #8 from justinhartman/main

Loading

0 comments on commit 2156567

Please sign in to comment.