Source: lib/controllers/campground-controller.js

"use strict";

const 	
		/**
		 * express module
		 * @const
		 */
		express 			= require("express"), 
		/**
		 * middleware module.
		 * If file is not specified than it will require index.js in specified folder
		 * @const
		 */
		middleware			= require("../middleware"), 
		/**
		 * multer module
		 * @const
		 */
		multer 				= require("multer"),
		/**
		 * errors module
		 * @const
		 */
		errors				= require("../../bin/errors/errors"),
		/**
		 * api-helper class module
		 * @const
		 */
		APIHelper			= require("../../bin/helpers/api/api-helper"),
		/**
		 * geocoder-helper class module
		 * @const
		 */
		GeocoderHelper		= require("../../bin/helpers/api/geocoder-helper"),
		/**
		 * cloudinary-helper class module
		 * @const
		 */
		CloudinaryHelper	= require("../../bin/helpers/api/cloudinary-helper"),
		/**
		 * general-helper class module
		 * @const
		 */
		GeneralHelper		= require("../../bin/helpers/general/general-helper");

// requiring models
const	
		/**
		 * User resource class module
		 * @const
		 */
		User				= require("../models/user"),
		/**
		 * Campground resource class module
		 * @const
		 */
	  	Campground 			= require("../models/campground"),
		/**
		 * Comment resource class module
		 * @const
		 */
	  	Comment				= require("../models/comment"),
		/**
		 * Notification resource class module
		 * @const
		 */
	  	Notification		= require("../models/notification");

/**
@const
@default
*/
const DB_PROVIDER = "MongoDB";

/** 
* Class representing a Campground controller.
* @module lib/controllers/campground-controller
* @requires express
* @requires multer
* @requires lib/middleware/middleware
* @requires bin/errors/errors
* @requires bin/helpers/api/api-helper
* @requires bin/helpers/api/geocoder-helper
* @requires bin/helpers/api/cloudinary-helper
* @requires bin/helpers/general/general-helper
* @requires lib/models/user
* @requires lib/models/campground
* @requires lib/models/comment
* @requires lib/models/notification
* @author Jose Nicolas Mora
*/
class CampgroundController {
	/**
	* Create a CampgroundController.
	*/
	constructor() {
		
	}
	/**
	* Display a listing of the Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundIndexGet(req, res) {
		const perPage = 8;
		const pageQuery = parseInt(req.query.page);
		const pageNumber = pageQuery ? pageQuery : 1;
		
		let campgrounds = [];
		try {
			// Get all campgrounds from DB
			
			let query = {};
			let search = false;
			if(req.query.search) {
				const regex = new RegExp(CampgroundController.escapeRegex(req.query.search), 'gi');
				query = {name: regex};
				search = req.query.search;
			}
			
			let noMatch = null;
			campgrounds = await Campground.find(query).skip((perPage * pageNumber) - perPage).limit(perPage).populate("likes");
			if(!campgrounds.length) {
				noMatch = "Sorry, no campgrounds match your search";
			}

			const count = await Campground.countDocuments(query).exec();
			// const count = campgrounds.length;
			const pages = Math.ceil(count / perPage);

			return res.render("campgrounds/index",{campgrounds: campgrounds, 
												   current: pageNumber,
												   pages: pages,
												   noMatch: noMatch,
												   search: search,
												  });
		}
		catch(err) {
			console.log(err);
			req.flash('error', err.message);
			return res.redirect("back");
		}
	}

	/**
	* Set the geographic coordinates of a campground.
	* @param {module:lib/models/campground} campground - The Campground object.
	* @return {module:lib/models/campground} The modified campground.
	*/
	static async setCampgroundLocData(campground) {
		try {
			let geocoderHelper = new GeocoderHelper();
			geocoderHelper.setParams({query: campground.location, resource: "geographic coordinates"});
			await geocoderHelper.geocodeData();
			geocoderHelper.setLocData(campground);
			return campground;
		}
		catch(err) {
			throw new errors.ExtendedError({errorCause: err});
		}
	}

	/**
	* Upload Campground image to Cloudinary, and add image link to campground object.
	* @param {Request} req - The HTTP Request.
	* @return {module:lib/models/campground} The modified campground.
	*/
	static async uploadCampgroundImage(req) {
		try {
			let cloudinaryHelper = new CloudinaryHelper();
			cloudinaryHelper.setParams({query: req.file.path});
			await cloudinaryHelper.uploadImageToCloudinary();
			req.body.campground.image = cloudinaryHelper.response.secure_url;
			return req.body.campground;
		}
		catch(err) {
			throw new errors.ExtendedError({errorCause: err});
		}
	}

	/**
	* Create notifications of a newly created campground for all followers of the campground author.
	* @param {module:lib/models/user} loggedInUser - The logged in user.
	* @param {module:lib/models/campground} campground - The modified campground.
	*/
	static async createNewCampgroundNotifications(loggedInUser, campground) {
		try {
			// Get followers of campground author
			const query = {_id: loggedInUser._id};
			let user = await User.findOne(query).populate("followers").exec();
			if(!user) {
				const data = {provider: DB_PROVIDER, query: query, resource: User.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}

			// Create notification object for followers
			let newNotification = {
				followedUser: user._id,
				campgroundSlug: campground.slug,
			}

			// For each follower create an individual notification model object and add it to their notifications array
			for(const follower of user.followers) {
				newNotification.followerUser = follower._id;
				let notification = await Notification.create(newNotification);
				await follower.notifications.push(notification);
				await follower.save();
			}
		}
		catch(err) {
			throw new errors.ExtendedError({errorCause: err, data: {loggedInUser: loggedInUser, campground: campground}});
		}

	}
	
	/**
	* Create a new Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundCreatePost(req, res) {
		try {
			await CampgroundController.setCampgroundLocData(req.body.campground);
			await CampgroundController.uploadCampgroundImage(req);
			req.body.campground.author = req.user;
			const campground = await Campground.create(req.body.campground);
			await CampgroundController.createNewCampgroundNotifications(req.user, campground);
			
			await req.user.campgrounds.push(campground);
			await req.user.save();
			
			req.flash("success", "Successfully created campground");
			// return res.redirect(`/campgrounds/${campground._id}`);
			return res.redirect(`/campgrounds/${campground.slug}`);
		}
		catch(err) {
			console.log(err);
			req.flash('error', err.message);
			return res.redirect("back");
		}
	}

	/**
	* Show the form for creating a new Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static campgroundNewGet(req, res) {
		res.render("campgrounds/new");
	}

	/**
	* Display the specified Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundShowGet(req, res) {
		try {
			const query = {slug: req.params.slug};
			const campground = await Campground.findOne(query)
				.populate("author")
				.populate("comments")
				.populate("likes")
				.exec();
			if(!campground) {
				const data = {provider: DB_PROVIDER, query: query, 
							  resource: Campground.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}
			
			await Comment.populateAll(campground.comments);
			
			const baseUrl = "campgrounds/show";
			return res.render(baseUrl, {campground, baseUrl: baseUrl});
		}
		catch(err) {
			console.log(err);
			req.flash("error", err.message);
			return res.redirect("back");
		}
	}
	
	/**
	* Show to the form for editing the specified Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundEditGet(req, res) {
		try {
			// const query = {_id: req.params.id};
			const query = {slug: req.params.slug};
			const campground = await Campground.findOne(query);
			if(!campground) {
				const data = {provider: DB_PROVIDER, query: query, resource: Campground.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}
			return res.render("campgrounds/edit", {campground: campground});
		}
		catch(err) {
			console.log(err);
			req.flash('error', err.message);
			return res.redirect("back");
		}
	}
	
	/**
	* Update the specified Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundUpdatePut(req, res) {
		try {

			await CampgroundController.setCampgroundLocData(req.body.campground);
			// const query = {_id: req.params.id};
			const query = {slug: req.params.slug};
			let campground = await Campground.findOne(query, {new: true});
			if(!campground) {
				const data = {provider: DB_PROVIDER, query: query, resource: Campground.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}
			// Object.assign(campground, req.body.campground); // update campground
			GeneralHelper.setAttr(campground, req.body.campground, ["name", "price", "image", "description", "location", "lat", "lng"]);
			await campground.save();
			req.flash("success", "Successfully updated campground!")
			// res.redirect("/campgrounds/" + req.params.id);
			res.redirect("/campgrounds/" + campground.slug);
		}
		catch(err) {
			console.log(err);
			req.flash('error', err.message);
			return res.redirect("back");
		}
	}
	
	/**
	* Remove the specified Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundDestroyDelete (req, res) {
		try {
			// const query = {_id: req.params.id}
			const query = {slug: req.params.slug}
			const campground = await Campground.findOneAndDelete(query);
			if(!campground) {
				const data = {provider: DB_PROVIDER, query: query, resource: Campground.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}
			req.flash("success", "Campground deleted!");
		} catch (err) {
			console.log(err);
			req.flash("error", err.message);
		}
		res.redirect("/campgrounds");
	}

	/** Replaces characters of a search query. */
	static escapeRegex(text) {
		return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
	};
	
	/**
	* Like/dislike a Campground resource.
	* @param {Request} req - The HTTP request.
	* @param {Response} res - The HTTP response object .
	*/
	static async campgroundLikePost(req, res) {
		try {
			const query = {slug: req.params.slug};
			let campground = await Campground.findOne(query);
			if(!campground) {
				const data = {provider: DB_PROVIDER, query: query, resource: Campground.schema.COLLECTION_NAME};
				throw new errors.ResourceNotFoundError({error: {message: `Unable to find ${data.resource}`}, data: data});
			}
			// check if req.user._id exists in foundCampground.likes
			let foundUserLike = campground.likes.some(function (like) {
				return like.equals(req.user._id);
			});
			
			if (foundUserLike) {
				// user already liked, removing like
				campground.likes.pull(req.user._id);
			} else {
				// adding the new user like
				campground.likes.push(req.user);
			}

			campground.save();
			return res.redirect("/campgrounds/" + campground.slug);
		}
		catch(err) {
			console.log(err);
			req.flash('error', err.message);
			return res.redirect("/campgrounds");
		}
	}
}

module.exports = CampgroundController;