diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..323a898 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +*.env + +# System Files +.DS_Store +Thumbs.db + +/backend/database/ +/backend/node_modules/ + +package-lock.json diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..063b78f --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: npm start diff --git a/README.md b/README.md index 9ab8554..73dd10c 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,67 @@ -# PolyNotFound +# PolyNotFound - Partie Backend en local -Le projet est séparé en 3 parties : +## Lancement du Backend -- 2 Frontend : - - 1 partie Administrateur - - 1 partie pour les utilisateurs et les publicitaires -- 1 Backend +### 1.1 Installation des différentes librairies avec npm -Nous avons décidé que chaque partie du projet a sa propre branche git et non leur propre répertoire git. +Si NodeJS est installé ([téléchargeable ici](https://nodejs.org/en/download/)), il suffit de faire un `npm install`. -En effet, les branches concernées par le projet est : -- Frontend partie Administrateur : `front-admin` -- Frontend partie pour les utilisateurs et les publicitaires : `front-user-advertiser` -- Backend : `backend` +### 1.2 Création d'une base de données MongoDB en local avec Docker -Nous pouvons récupérer une branche git avec `git checkout `. +Il faudra **Docker** ([téléchargeable ici](https://docs.docker.com/desktop/#download-and-install)) -# Lancer le projet en Local +Puis dans un terminal, pour lancer le serveur MongoDB -Dans un premier temps, il est obligatoire d'avoir NodeJS (version >12 au minimum), [téléchargeable sur cette page](https://nodejs.org/en/download/). +`docker run -d -p 27017:27017 --ip 127.0.0.1 --name polynotfound-mongodb mongo:latest` -Pour lancer le projet en local dans son ensemble, il faut impérativement avoir les branches concernées dans leur propre dossier. -Il faudra donc __clone__ le projet 3 fois. +_L'image de Mongo sera automatiquement téléchargé si elle n'existe pas en local._ -Dans un dossier nommé par exemple `Polynotfound`: -- frontend-admin : `git clone --branch frontend-admin https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-frontend-admin` -- front-user-advertiser : `git clone --branch front-user-advertiser https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-front-user-advertiser` -- backend : `git clone --branch backend https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-backend` +1. Si vous avez MongoDB Compass ([téléchargeable ici](https://www.mongodb.com/try/download/compass)): +- Se connecter au serveur `mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false` +- Create Database : + - Database Name : polynotfound + - Collection Name : users -Un README est disponible pour chaque branche pour lancer le projet en local soit en mode **production** soit en mode **développement**. +2. Sinon avec MongoShell ([téléchargeable ici](https://www.mongodb.com/try/download/shell)): +- Se connecter au serveur + - Linux: `mongo mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false` + - Windows: `mongo.exe mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false` +- Créer la base de données : `use polynotfound` -# Lancer le projet en ligne avec Heroku +### 1.3 Initialisation des variables d'environnements -Nous avons déployé le projet en ligne avec Heroku. +5 variables d'environnements sont nécessaires pour lancer le backend correctement. +- **DATABASE** : url de connexion à la base de données (utilisé seulement en production) + - Sur Windows : `set DATABASE=` + - Sur Linux : `export DATABASE=` +- Token de connexion + - **JWTRS256_PUBLIC_KEY** : clé publique pour les tokens de connexion + - **JWTRS256_PRIVATE_KEY** : clé privée pour les tokens de connexion + + Lancer le script de génération des clés `jwtRS256.sh`, un fichier `.env` sera créé. + Il faudra `set` ou `export` ces 2 variables dans vos variables d'environnements. + - Sur Windows : + - `set JWTRS256_PUBLIC_KEY=` + - `set JWTRS256_PRIVATE_KEY=` + - Sur Linux : + - `export JWTRS256_PUBLIC_KEY=` + - `export JWTRS256_PRIVATE_KEY=` -Le projet est disponible sur ces URL : -- Partie Utilisateurs et Publicitaires : https://polynotfound.herokuapp.com/ -- Partie Administrateur : https://admin-polynotfound.herokuapp.com/ -- API : https://api-polynotfound.herokuapp.com/ \ No newline at end of file +- **YOUTUBE_API_KEY** : Clé de connexion à l'API Youtube ([récupérable ici](https://developers.google.com/youtube/v3/getting-started?hl=fr)) +- **DAILYMOTION_API_KEY** : Clé de connexion à l'API Dailymotion ([récupérable ici](https://www.dailymotion.com/profile/developer)) + + _A noté que la clé d'API de Dailymotion n'est pas utilisée mais cela peut changer dans le temps._ + +Après l'initialisation de ces variables d'environnements, il faudra surement redémarrer votre ordinateur. (Fermer et rouvrir un terminal peut être suffisant dans certain cas) + +#### 1.3.1 Lancement en mode développement + +Pour lancer le backend en mode développement, il faudra simplement exécuter dans un terminal: +- Windows : `npm dev-win` +- Linux & Mac : `npm dev-nix` + +#### 1.3.2 Lancement en mode production + +Pour lancer le backend en mode production, il faudra simplement exécuter dans un terminal: +- Windows : `npm prod-win` +- Linux & Mac : `npm prod-nix` \ No newline at end of file diff --git a/config/cors.config.js b/config/cors.config.js new file mode 100644 index 0000000..6e73567 --- /dev/null +++ b/config/cors.config.js @@ -0,0 +1,20 @@ +const cors = require('cors'); +module.exports.cors = cors; + +const whitelist = [ + 'http://127.0.0.1:4200', + 'http://127.0.0.1:4201', + 'https://admin-polynotfound.herokuapp.com', + 'https://polynotfound.herokuapp.com' +]; + +module.exports.corsOptions = { + origin: function(origin, callback) { + console.log(whitelist, origin); + if (whitelist.indexOf(origin) !== -1) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + } +} \ No newline at end of file diff --git a/config/functions.config.js b/config/functions.config.js new file mode 100644 index 0000000..b229914 --- /dev/null +++ b/config/functions.config.js @@ -0,0 +1,26 @@ +const request = require("request"); +const VideoCategories = require("../models/objects/video.categories.model"); + +function asyncRequest(uri, option){ + return new Promise(function(resolve){ + request(uri, option,function (error, response, body){ + resolve({response: response, body: JSON.parse(body)}); + }); + }); +} +module.exports.asyncRequest = asyncRequest; + +function asyncInterest(interest, source){ + return new Promise(function(resolve){ + for(const i in VideoCategories){ + for(const j in VideoCategories[i].categories){ + if((VideoCategories[i].categories[j].name === interest || VideoCategories[i].categories[j].id === interest) + && VideoCategories[i].categories[j].source === source){ + resolve(VideoCategories[i].interest); + } + } + } + resolve(null); + }); +} +module.exports.asyncInterest = asyncInterest; diff --git a/config/host.config.js b/config/host.config.js new file mode 100644 index 0000000..6a62f28 --- /dev/null +++ b/config/host.config.js @@ -0,0 +1,26 @@ +if(process.env.YOUTUBE_API_KEY === undefined || + process.env.YOUTUBE_API_KEY === '' || + process.env.DAILYMOTION_API_KEY === undefined || + process.env.DAILYMOTION_API_KEY === ''){ + console.log('Error Env YOUTUBE_API_KEY & DAILYMOTION_API_KEY Variables'); + process.exit(); +} + +console.log('Env variables YOUTUBE_API_KEY & DAILYMOTION_API_KEY received'); + +module.exports = { + youtube: { + name: "Youtube", + shortname: "yt", + baseAPIUrl: 'https://youtube.googleapis.com/youtube/v3', + baseChannelUrl: 'https://www.youtube.com/channel/', + YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY + }, + dailymotion: { + name: "Dailymotion", + shortname: "dm", + baseAPIUrl: 'https://api.dailymotion.com', + baseChannelUrl: 'https://www.dailymotion.com/', + DAILYMOTION_API_KEY: process.env.DAILYMOTION_API_KEY + } +}; diff --git a/config/mongodb.config.js b/config/mongodb.config.js new file mode 100644 index 0000000..2180acc --- /dev/null +++ b/config/mongodb.config.js @@ -0,0 +1,4 @@ +module.exports = { + prodUrl: process.env.DATABASE, + devUrl: "mongodb://127.0.0.1:27017/polynotfound" +}; diff --git a/config/response.config.js b/config/response.config.js new file mode 100644 index 0000000..dfca2db --- /dev/null +++ b/config/response.config.js @@ -0,0 +1,9 @@ +function sendMessage (res, successCode, data, token=null) { + res.status(200).json({ status: 'success', successCode: successCode, token: token, data: data }); +} + +function sendError (res, statusCode, errorCode, reason, token=null) { + res.status(statusCode).json({ status: 'error', errorCode: errorCode, token: token, reason: reason}); +} + +module.exports = { sendMessage, sendError }; diff --git a/config/sessionJWT.config.js b/config/sessionJWT.config.js new file mode 100644 index 0000000..2fde996 --- /dev/null +++ b/config/sessionJWT.config.js @@ -0,0 +1,110 @@ +const sessionJWTConfig = require ('jsonwebtoken'); +require('dotenv').config({ path: './app-backend/.env' }); +const {sendError} = require ("./response.config"); + +if(process.env.JWTRS256_PRIVATE_KEY === undefined || process.env.JWTRS256_PUBLIC_KEY === undefined){ + console.log('Error Env JWTRS256_PRIVATE_KEY & JWTRS256_PUBLIC_KEY Variables'); + process.exit(); +} + +console.log('Env variables JWTRS256_PRIVATE_KEY & JWTRS256_PUBLIC_KEY received'); +const JWTRS256_PRIVATE_KEY = Buffer.from(process.env.JWTRS256_PRIVATE_KEY, 'base64').toString('utf-8'); +const JWTRS256_PUBLIC_KEY = Buffer.from(process.env.JWTRS256_PUBLIC_KEY, 'base64').toString('utf-8'); + + +function createSessionJWT (id, email, profileImageUrl, role) { + return sessionJWTConfig.sign( + { + id: id, + email: email, + profileImageUrl: profileImageUrl, + role: role, + midExp: Math.floor(Date.now() / 1000) + 1800 + }, + JWTRS256_PRIVATE_KEY, + { + algorithm: 'RS256', + expiresIn: '1h' + } + ); +} + +function createSessionCookie(req, res, payload) { + let jwtToken; + if (typeof payload.id !== 'undefined' && + typeof payload.email !== 'undefined' && + typeof payload.profileImageUrl !== 'undefined' && + typeof payload.role !== 'undefined' && + typeof payload.midExp !== 'undefined' && + (Math.floor(Date.now() / 1000) <= payload.midExp)) { + jwtToken = req.headers.cookie; + } + else { + jwtToken = createSessionJWT(payload.id, payload.email, payload.profileImageUrl, payload.role); + } + res.cookie('SESSIONID', jwtToken, {httpOnly: true, sameSite: 'None', secure: process.env.NODE_ENV === 'production'}); +} + +function decodeSessionCookie(sessionid) { + if (typeof sessionid === 'undefined') { + return {id: -1, email: -1, profileImageUrl: -1, role: -1}; + } + try { + const token = sessionJWTConfig.verify( + sessionid, + JWTRS256_PUBLIC_KEY, + {algorithms: ['RS256']}); + return {token: token}; + } + catch (err) { + return {id: -1, email: -1, profileImageUrl: -1, role: -1}; + } +} + +function getSession(sessionid) { + return decodeSessionCookie(sessionid); +} +module.exports.getSession = getSession + +function setSessionCookie (req, res, session) { + createSessionCookie(req, res, session); +} +module.exports.setSessionCookie = setSessionCookie; + +function getToken(session) { + if (typeof session === 'undefined' || typeof session.token === 'undefined') return -1; + return session.token; +} +module.exports.getToken = getToken; + +function checkLogin(req, res, role=null){ + if(typeof req.cookies !== 'undefined'){ + const session = getSession(req.cookies.SESSIONID); + const token = getToken(session); + console.log(token); + if(typeof token.email === 'undefined' || + token.email === -1 || + typeof token.id === 'undefined' || + token.id === -1){ + return sendError(res, 500, 102, "User not authenticated."); + } else { + token.midExp = new Date(token.midExp*1000); + token.iat = new Date(token.iat*1000); + token.exp = new Date(token.exp*1000); + if(role === null){ + return token; + } else { + if(typeof token.role !== 'undefined' && + ((Array.isArray(role) && role.includes(token.role)) || + ( typeof role === 'object' && typeof token.role.permission !== 'undefined' && token.role.permission >= role.permission && token.role.isAccepted === true))){ + return token; + } else { + return sendError(res, 500, 106, "User doesn't have permission.", token); + } + } + } + } else { + return sendError(res, 500, -1, "Cookies don't exist."); + } +} +module.exports.checkLogin = checkLogin; diff --git a/controllers/ad.controller.js b/controllers/ad.controller.js new file mode 100644 index 0000000..d92be1f --- /dev/null +++ b/controllers/ad.controller.js @@ -0,0 +1,290 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const roles = require("../models/objects/role.model"); +const Ad = db.ads; + +// Create a new Ad +exports.create = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && req.body.title){ + Ad.exists({title: req.body.title, userId: token.id, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Ad already exists.", token); + } else{ + if(docs === null) { + let ad; + + ad = new Ad({ + userId: token.id, + title: req.body.title, + images: req.body.images ? req.body.images : undefined, + url: req.body.url ? req.body.url : undefined, + interests: req.body.interests ? req.body.interests : undefined, + comment: req.body.comment ? req.body.comment : undefined, + isVisible: req.body.isVisible ? req.body.isVisible : undefined, + isActive: req.body.isActive ? req.body.isActive : undefined + }); + + // Save User in the database + ad + .save(ad) + .then(data => { + return sendMessage(res, 41, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Ad.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Ad ${req.body.title} already exists.`, token); + } + } + }); + } else { + return sendError(res, 500, -1, `No title given`, token); + } +}; + +// Retrieve all Ad from id if admin or session id +exports.findAll = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token){ + let query = {}; + let condition; + + const adId = req.query.adId; + condition = adId ? adId : undefined; + query._id = condition; + + let userId; + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + userId = req.query.userId; + } else { + userId = token.id; + } + condition = userId ? userId : undefined; + query.userId = condition; + + const title = req.query.title; + condition = title ? { $regex: new RegExp(title), $options: "i" } : undefined; + query.title = condition; + + const url = req.query.url; + condition = url ? { $regex: new RegExp(url), $options: "i" } : undefined; + query.url = condition; + + const interests = req.query.interests; + condition = interests ? {$in: interests.split(',')} : undefined; + query["interests.interest"] = condition + + const comment = req.query.comment; + condition = comment ? { $regex: new RegExp(comment), $options: "i" } : undefined; + query.comment = condition; + + const isVisible = req.query.isVisible; + condition = isVisible ? isVisible : undefined; + query.isVisible = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {title: 1}; + break; + case 'desc': + condition = {title: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {title: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Ad.find(query, {}, query_sort) + .then(data => { + if(data){ + return sendMessage(res, 42, data, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Ads.", token); + }); + } +}; + +// Find single Ad from id if admin or session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Ad.findById(id, {}) + .then(data => { + if(data){ + return sendMessage(res, 43, data, token); + } else { + return sendError(res,404,105,`Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a Ad with ad id +exports.update = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + let update = {}; + let condition; + + const title = req.body.title; + condition = title ? title : undefined; + update.title = condition; + + const images = req.body.images; + condition = images ? images : undefined; + update.images = condition; + + const url = req.body.url; + condition = url ? url : undefined; + update.url = condition; + + let interests = req.body.interests; + condition = interests ? {interests: [...new Map(interests.map(v => [v.id, v])).values()]} : undefined; + update.$addToSet = condition; + + const comment = req.body.comment; + condition = comment ? comment : undefined; + update.comment = condition; + + const isVisible = req.body.isVisible; + if(typeof isVisible !== 'undefined'){ + condition = isVisible; + } else{ + condition = undefined; + } + update.isVisible = condition; + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + if(id && ObjectId.isValid(id)){ + Ad.updateOne({_id: id, userId: token.id}, update) + .then(data => { + if(data) { + //Object.keys(update).forEach(key => data[key] = update[key]); + return sendMessage(res, 44, update, token); + } else { + return sendError(res, 404, -1, `Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete an Ad with ad id +exports.delete = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + let match = null; + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + match = {_id: id, isActive: true}; + } else { + match = {_id: id, userId: token.id, isActive: true}; + } + Ad.findOneAndUpdate(match, {isActive: false}, {useFindAndModify: false, new: true}) + .then(data => { + if(data) { + if(data.isActive !== true){ + return sendMessage(res, 45, {message: `Ad ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Ad ${id} was not deleted.`, token); + } + } else { + return sendError(res, 404, 105, `Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Ad from session id +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token) { + Ad.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 46, { + message: `${data.modifiedCount} Ads were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, -1, err.message || "Some error occurred while removing all Ads."); + }); + } +}; diff --git a/controllers/misc.controller.js b/controllers/misc.controller.js new file mode 100644 index 0000000..326e138 --- /dev/null +++ b/controllers/misc.controller.js @@ -0,0 +1,7 @@ +const {sendMessage} = require ("../config/response.config"); +const interests = require("../models/objects/video.categories.model"); + +// Get all interests available +exports.getInterests = (req, res) => { + return sendMessage(res, 51, interests, null) +}; diff --git a/controllers/playlist.controller.js b/controllers/playlist.controller.js new file mode 100644 index 0000000..51442a1 --- /dev/null +++ b/controllers/playlist.controller.js @@ -0,0 +1,407 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest, asyncInterest} = require("../config/functions.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const Playlist = db.playlists; +const Video = db.videos; + +// Create a new Playlist +exports.create = (req, res) => { + const token = checkLogin(req, res); + if(token && req.body.name){ + const video = req.body.video; + if(typeof video !== 'undefined' && + video !== null && + typeof video.videoId !== 'undefined' && + video.videoId !== null && + typeof video.source !== 'undefined' && + video.source !== null && + typeof video.interest !== 'undefined' && + video.interest !== null + ){ + Video.exists({userId: token.id, videoId: video.videoId, source: video.source, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Video already exists.", token); + } else{ + if(docs === null) { + let video; + + video = new Video({ + userId: token.id, + videoId: id, + source: req.body.source, + interest: req.body.interest, + watchedDates: [new Date()] + }); + + // Save Video in the database + video + .save(video) + .then(data => { + if(data) { + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: data._id ? [data._id] : undefined, + isActive: true + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Video.", token); + }); + } else{ + const id = docs._id.toString(); + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: [id], + isActive: true + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + } + }); + } else { + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: req.body.videoIds ? req.body.videoIds : undefined, + isActive: req.body.isActive ? req.body.isActive : undefined + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + } +}; + +// Retrieve all Playlist from id if admin or session id +exports.findAll = (req, res) => { + const token = checkLogin(req, res); + if(token){ + let query = {}; + let condition; + + const playlistId = req.query.playlistId; + condition = playlistId ? playlistId : undefined; + query._id = condition; + + const userId = token.id; + condition = userId ? userId : undefined; + query.userId = condition; + + const videoIds = req.query.videoIds; + condition = videoIds ? {$in: videoIds} : undefined; + query.videoIds = condition; + + const name = req.query.name; + condition = name ? { $regex: new RegExp(name), $options: "i" } : undefined; + query.name = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {name: 1}; + break; + case 'desc': + condition = {name: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {name: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Playlist.find(query, {}, query_sort) + .then(data => { + return sendMessage(res, 22, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Playlists.", token); + }); + } +}; + +// Find single Playlist from session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Playlist.aggregate([ + {$match: {_id: new ObjectId(id), userId: token.id, isActive: true}}, + {$unwind: '$videoIds'}, + {$project: { + userId: true, + name: true, + isActive: true, + createdAt: true, + updatedAt: true, + videoIds: {$toObjectId: '$videoIds'} + }}, + {$lookup: { + from: 'videos', + localField: 'videoIds', + foreignField: '_id', + as: 'videos' + }}, + {$unwind: '$videos'}, + {$group: { + _id: '$_id', + userId: {$first: "$userId"}, + name: {$first: "$name"}, + isActive: {$first: "$isActive"}, + createdAt: {$first: "$createdAt"}, + updatedAt: {$first: "$updatedAt"}, + videos: {$push: "$videos"} + }} + ]) + .then(async data => { + let yt_results = []; + let dm_results = []; + let yt_videoIds = ""; + let dm_videoIds = ""; + + for (const i in data[0].videos) { + if (data[0].videos[i].source === youtube.name) { + yt_videoIds = yt_videoIds + data[0].videos[i].videoId + ","; + } else if (data[0].videos[i].source === dailymotion.name) { + dm_videoIds = dm_videoIds + data[0].videos[i].videoId + ","; + } + } + if (yt_videoIds !== "") { + const uri = youtube.baseAPIUrl + '/videos' + '?part=snippet&part=statistics&id=' + yt_videoIds.slice(0, -1) + '&key=' + youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if (dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0) { + yt_results = dataVideos.body.items; + } + } + + if (dm_videoIds !== "") { + const uri = dailymotion.baseAPIUrl + '/videos?ids=' + dm_videoIds.slice(0, -1) + '&fields=thumbnail_480_url%2Ctitle%2Cid'; + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if (response.statusCode === 200) { + dm_results = jsonBody.list; + } + } + for (const i in data[0].videos) { + if (data[0].videos[i].source === youtube.name) { + const obj = yt_results.filter(obj => obj.id === data[0].videos[i].videoId); + data[0].videos[i].imageUrl = obj[0].snippet.thumbnails.medium.url ? obj[0].snippet.thumbnails.medium.url : null; + data[0].videos[i].interest = obj[0].snippet.categoryId ? await asyncInterest(obj[0].snippet.categoryId, youtube.name): null; + data[0].videos[i].title = obj[0].snippet.title ? obj[0].snippet.title : null; + data[0].videos[i].views = obj[0].statistics.viewCount ? parseInt(obj[0].statistics.viewCount) : null; + data[0].videos[i].publishedAt = obj[0].snippet.publishedAt ? obj[0].snippet.publishedAt : null; + } else if (data[0].videos[i].source === dailymotion.name) { + const obj = dm_results.filter(obj => obj.id === data[0].videos[i].videoId); + data[0].videos[i].imageUrl = obj[0].thumbnail_480_url ? obj[0].thumbnail_480_url : null; + data[0].videos[i].interest = obj[0]['channel.name'] ? await asyncInterest( obj[0]['channel.name'], dailymotion.name) : null; + data[0].videos[i].title = obj[0].title ? obj[0].title : null; + data[0].videos[i].views = obj[0].views_total ? parseInt(obj[0].views_total) : null; + data[0].videos[i].publishedAt = obj[0].created_time ? new Date(obj[0].created_time * 1000) : null + } + } + return sendMessage(res, 12, data[0], token) + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Playlist with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a Playlist with playlist id +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + const ids = id.split(','); + let update = {}; + let condition; + + const name = req.body.name; + condition = name ? name : undefined; + update.name = condition; + + const videoIds = req.body.videoIds; + condition = videoIds ? videoIds : undefined; + update.videoIds = condition; + + const videoId = req.body.videoId; + if(typeof videoId !== 'undefined' && typeof videoId.id !== 'undefined' && typeof videoId.action !== 'undefined'){ + if(videoId.action === 'add'){ + condition = videoId.id ? {videoIds: videoId.id} : undefined; + update.$addToSet = condition; + } else if(videoId.action === 'delete'){ + condition = videoId.id ? {videoIds: videoId.id} : undefined; + update.$pull = condition; + } + } + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + Playlist.updateMany({_id: {$in: ids}, userId: token.id, isActive: true}, update, {new: false}) + .then(data => { + if(data) { + if(data.modifiedCount > 0){ + return sendMessage(res, 24, update, token); + } else { + return sendError(res, 500, -1, `Video in Playlist ${data} already exists.`, token); + } + } else { + return sendError(res, 404, -1, `Playlist not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Playlist with id=${id}`, token); + }); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete a Playlist with playlist id +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Playlist.findByIdAndUpdate(id, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data) { + return sendMessage(res, 25, {message: `Playlist ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Playlist not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Playlist with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Playlists from session id +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res); + if(token) { + Playlist.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 26, { + message: `${data.modifiedCount} Playlists were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, -1, err.message || "Some error occurred while removing all Playlists."); + }); + } +}; diff --git a/controllers/user.controller.js b/controllers/user.controller.js new file mode 100644 index 0000000..d01b6d2 --- /dev/null +++ b/controllers/user.controller.js @@ -0,0 +1,544 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin, setSessionCookie} = require("../config/sessionJWT.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const roles = require("../models/objects/role.model"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest} = require("../config/functions.config"); +const User = db.users; +const Video = db.videos; +const Ad = db.ads; + +// Authenticate a User +exports.auth = (req, res) => { + // Validate request + if (!req.body.email || !req.body.hashPass) { + sendError(res, 400,-1,"Content can not be empty . (email and hashPass needed)"); + } else{ + // Check User in the database + User + .findOne({email: req.body.email, hashPass: req.body.hashPass, isActive: true, "role.isAccepted": true}, {role: true, profileImageUrl: true}) + .then(data => { + if (data !== null){ + User.findByIdAndUpdate(data._id.toString(), {lastConnexion: new Date()}, {useFindAndModify: false}, + function (err) { + if (err){ + return sendError(res, 400, 100,err.message || "Some error occurred while updating the User."); + } + else{ + const dataRes = {id: data._id.toString(), email: req.body.email, profileImageUrl: data.profileImageUrl, role: data.role}; + setSessionCookie(req, res, dataRes); + return sendMessage(res, 1, dataRes); + } + }); + } else { + setSessionCookie(req, res, {id: -1, email: -1, profileImageUrl: -1, role: -1}); + return sendError(res, 500, 101, "Invalid login or password."); + } + }) + .catch(err => { + return sendError(res, 400, 100,err.message || "Some error occurred while authenticating the User."); + }); + } +}; + +// Logout a User +exports.logout = (req, res) => { + const token = checkLogin(req, res); + if(token){ + setSessionCookie(req, res, {id: -1, email: -1, profileImageUrl: -1, role: -1}); + return sendMessage(res, 2, {message: "User disconnected"}); + } +}; + +// Request password reset with email +exports.resetPass = (req, res) => { + return sendError(res, 501, -1, "User.resetPass not Implemented", null); +}; + +// Create and Save a new User +exports.create = (req, res) => { + // Validate request + if (!req.body.email || !req.body.hashPass || !req.body.login) { + sendError(res, 400,-1,"Content can not be empty . (email, hashPass and login needed"); + } + else{ + User.exists({email: req.body.email}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the User already exists."); + } else{ + if(docs === null) { + let user; + let var_role; + if(typeof req.body.role !== 'undefined'){ + switch(req.body.role){ + case 'admin': + var_role = roles.Admin; + break; + case 'advertiser': + var_role = roles.Advertiser; + break; + default: + var_role = roles.User; + } + } else{ + var_role = roles.User; + } + + user = new User({ + email: req.body.email, + hashPass: req.body.hashPass, + login: req.body.login, + role: var_role, + company: req.body.company ? req.body.company : null, + dateOfBirth: req.body.dateOfBirth ? req.body.dateOfBirth : null, + gender: req.body.gender ? req.body.gender : null, + interests: req.body.interests ? req.body.interests : null, + }); + + // Save User in the database + user + .save(user) + .then(data => { + data.hashPass = undefined; // Hiding hashPass on return + return sendMessage(res, 4, data) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the User."); + }); + } else{ + return sendError(res, 500, 104, err || `Email ${req.body.email} already exists.`); + } + } + }); + } +}; + +// Retrieve all Users from the database if at least admin. +exports.findAll = (req, res) => { + const token = checkLogin(req, res, roles.Admin); + if(token){ + let query = {}; + let condition; + + const ids = req.query.userId; + condition = ids ? {$in: ids} : undefined; + query._id = condition; + + const email = req.query.email; + condition = email ? { $regex: new RegExp(email), $options: "i" } : undefined; + query.email = condition; + + const login = req.query.login; + condition = login ? { $regex: new RegExp(login), $options: "i" } : undefined; + query.login = condition; + + const role = req.query.role; + condition = role ? role : undefined; + query["role.name"] = condition; + + const company = req.query.company; + condition = company ? { $regex: new RegExp(company), $options: "i" } : undefined; + query.company = condition; + + const dateOfBirth = req.query.dateOfBirth; + condition = dateOfBirth ? new Date(dateOfBirth) : undefined; + query.dateOfBirth = condition; + + const gender = req.query.gender; + condition = gender ? gender : undefined; + query.gender = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const isAccepted = req.query.isAccepted; + if(isAccepted !== 'undefined'){ + switch (isAccepted){ + case 'true': + condition = true; + break; + case 'false': + condition = false; + break; + } + } + query["role.isAccepted"] = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {email: 1}; + break; + case 'desc': + condition = {email: -1}; + break; + case 'lastConnexionAsc': + condition = {lastConnexion: 1}; + break; + case 'lastConnexionDesc': + condition = {lastConnexion: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {email: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + User.find(query, {hashPass: false}, query_sort) + .then(data => { + return sendMessage(res, 5, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while retrieving users.", token); + }); + } +}; + +// Find a single User by session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + User.findById(id, {hashPass: false}) + .then(data => { + if(data){ + return sendMessage(res, 6, data, token); + } else { + return sendError(res,404,105,`User not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the User with id=${id}`, token); + }); + } else { + sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a User by the id in the request +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + let update = null; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + if(typeof req.body.role !== 'undefined' || + typeof req.body.isActive !== 'undefined' || + typeof req.body.lastConnexion !== 'undefined' || + typeof req.body.createdAt !== 'undefined'|| + typeof req.body.updatedAt !== 'undefined'){ + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + update = true; + } else{ + return sendError(res, 500, 106, `User do not have the permission to modify these keys.`, token); + } + } else{ + update = true; + } + } + if(update === true){ + User.findByIdAndUpdate(id, req.body, {useFindAndModify: false}) + .then(data => { + if(data) { + data.hashPass = undefined; + Object.keys(req.body).forEach(key => data[key] = req.body[key]); + sendMessage(res, 7, data, token); + } else { + sendError(res, 404, -1, `User not found with id=${id}`, token); + } + }) + .catch(err => { + sendError(res, 500, -1, err.message || `Some error occurred while updating the User with id=${id}`, token); + }); + } + } else { + sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + + + +// Delete a User with the specified id in the request +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + User.findByIdAndUpdate(id, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data) { + return sendMessage(res, 8, {message: `User ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `User not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the User with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Users from the database except superAdmin +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res, roles.SuperAdmin); + if(token) { + User.deleteMany({login: {$ne: "superAdmin"}}) + .then(data => { + return sendMessage(res, 9, { + message: `${data.deletedCount} Users were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, 100, err.message || "Some error occurred while removing all Users."); + }); + } +}; + +// Get all Roles depending on the role of the User +exports.roles = (req, res) => { + const token = checkLogin(req, res); + if(token){ + return sendMessage(res, 10, roles, token); + } +}; + +// Get 1 or multiple ad adapted to the User session id +exports.ad = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.query.quantity !== 'undefined'){ + const id = token.id; + const quantity = req.query.quantity; + // Interests from the user and from last 20 videos viewed if no ad matches -> find x ad from these interests + add date view to the ad + let interests = []; + const maxInterests = 20; + let limit = maxInterests; + User.findById(id, {_id: false, interests: true}) + .then(data => { + if(typeof data.interests !== 'undefined' && data.interests !== null){ + interests = interests.concat(data.interests); + limit = maxInterests-data.interests.length; + } + Video.aggregate([ + {$match: {userId: id}}, + {$project: {_id: false, interest: true}}, + {$sort: {watchedDates: -1}}, + {$limit: limit}, + {$unwind: '$interest'}, + {$group: {_id: null, interests: {$addToSet: '$interest'}}} + ]) + .then(data => { + if(typeof data[0] !== 'undefined' && + typeof data[0].interests !== 'undefined' && + data[0].interests !== [] && + data[0].interests !== null){ + interests = interests.concat(data[0].interests); + } + let match; + if(interests.length > 0){ + match = {$match: {isVisible: true, isActive: true, interests: {$elemMatch: {interest: {$in: interests}}}}}; + } else { + match = {$match: {isVisible: true, isActive: true}}; + } + const pick = {$sample: {size: parseInt(quantity, 10)}}; + Ad.aggregate([ + match, + pick + ]) + .then(data => { + if(data.length > 0){ + let ids = [] + for(const i in data){ids.push(data[i]._id);} + Ad.updateMany({_id: {$in: ids}}, {$push: {views: [new Date()]}}, {timestamps: false}) + .then(dataUpdate => { + if(dataUpdate && dataUpdate.modifiedCount > 0){ + return sendMessage(res, 11, data, token); + } else { + return sendError(res,500,101,`Some error occurred while updating ${quantity} ad(s) for the User.`, token); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while updating ${quantity} ad(s) for the User.`, token); + }); + } else { + Ad.aggregate([{$match: {isVisible: true, isActive: true}}, {$sample: {size: parseInt(quantity, 10)}}]) + .then(data => { + let ids = [] + for(const i in data){ids.push(data[i]._id);} + Ad.updateMany({_id: {$in: ids}}, {$push: {views: [new Date()]}}, {timestamps: false}) + .then(dataUpdate => { + if(dataUpdate && dataUpdate.modifiedCount > 0){ + return sendMessage(res, 11, data, token); + } else { + return sendError(res,500,101,`Some error occurred while updating ${quantity} ad(s) for the User.`, token); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while updating ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,102,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + } else { + sendError(res, 500, -1, `No quantity given`, token); + } +}; + +// Get History +exports.history = (req, res) => { + const token = checkLogin(req, res); + if(token){ + const id = token.id; + + Video.aggregate([ + {$match: {userId: id, $expr: {$gt: [{$size: "$watchedDates"}, 0]}}}, + {$limit: 300}, + {$project: { + videoId: true, + source: true, + tags: true, + interest: true, + views: {$size: '$watchedDates'}, + watchedDate: {$arrayElemAt: ["$watchedDates", -1]}, + createdAt: true, + updatedAt: true + }}, + {$sort: {watchedDate: -1}}]) + .then(async data => { + let yt_results = []; + let dm_results = []; + let yt_videoIds = ""; + let dm_videoIds = ""; + + for(const i in data) { + if(data[i].source === youtube.name) { + yt_videoIds = yt_videoIds + data[i].videoId + ","; + } else if (data[i].source === dailymotion.name) { + dm_videoIds = dm_videoIds + data[i].videoId + ","; + } + } + if(yt_videoIds !== ""){ + const uri = youtube.baseAPIUrl + '/videos' + '?part=snippet&part=statistics&id=' + yt_videoIds.slice(0, -1) + '&key=' + youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if (dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0) { + yt_results = dataVideos.body.items; + } + } + + if(dm_videoIds !== ""){ + const uri = dailymotion.baseAPIUrl + '/videos?ids='+dm_videoIds.slice(0, -1)+'&fields=thumbnail_480_url%2Ctitle%2Cid'; + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if(response.statusCode === 200){ + dm_results = jsonBody.list; + } + } + for(const i in data) { + if(data[i].source === youtube.name) { + const obj = yt_results.filter(obj => obj.id === data[i].videoId); + data[i].imageUrl = obj[0].snippet.thumbnails.medium.url ? obj[0].snippet.thumbnails.medium.url : null; + data[i].title = obj[0].snippet.title ? obj[0].snippet.title : null; + } else if (data[i].source === dailymotion.name) { + const obj = dm_results.filter(obj => obj.id === data[i].videoId); + data[i].imageUrl = obj[0].thumbnail_480_url ? obj[0].thumbnail_480_url : null; + data[i].title = obj[0].title ? obj[0].title : null; + } + } + return sendMessage(res, 12, data, token) + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while getting the User history.", token); + }); + } +}; diff --git a/controllers/video.controller.js b/controllers/video.controller.js new file mode 100644 index 0000000..7260588 --- /dev/null +++ b/controllers/video.controller.js @@ -0,0 +1,474 @@ +const db = require("../models/mongodb.model"); +const request = require('request'); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest, asyncInterest} = require("../config/functions.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const Video = db.videos; + + +// Search Videos +exports.search = async (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.query.q !== 'undefined'){ + const query = req.query.q; + const maxResults = req.query.maxResults ? req.query.maxResults : 45; + const pageToken = req.query.pageToken ? req.query.pageToken : undefined; + let sources; + if(typeof req.query.sources !== 'undefined' && req.query.sources !== ''){ + sources = req.query.sources.split(','); + } else { + sources = ["yt", "dm"]; + } + let yt_results = []; + let dm_results = []; + for(const i in sources){ + if(sources[i] === youtube.shortname){ + if(youtube.YOUTUBE_API_KEY !== 'undefined' && youtube.YOUTUBE_API_KEY !== ''){ + let uri; + if(query !== ''){ + if(typeof pageToken !== 'undefined'){ + uri = youtube.baseAPIUrl+'/search'+'?part=snippet&maxResults='+maxResults+'&q='+query+'&pageToken='+pageToken+'&key='+youtube.YOUTUBE_API_KEY; + } else{ + uri = youtube.baseAPIUrl+'/search'+'?part=snippet&maxResults='+maxResults+'&q='+query+'&key='+youtube.YOUTUBE_API_KEY; + } + const dataIds = await asyncRequest(uri, {}); + if(dataIds.response.statusCode === 200 && dataIds.body.items.length > 0){ + let yt_videoIds = ""; + dataIds.body.items.forEach(item => yt_videoIds = yt_videoIds+item.id.videoId+","); + uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&id='+yt_videoIds.slice(0, -1)+'&key='+youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if(dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0){ + yt_results = dataVideos.body.items; + } + } + } else { + uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&chart=mostPopular&maxResults='+maxResults+'&key='+youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if(dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0){ + yt_results = dataVideos.body.items; + } + } + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`, token); + } + } else if(sources[i] === dailymotion.shortname){ + if(dailymotion.DAILYMOTION_API_KEY !== 'undefined' && dailymotion.DAILYMOTION_API_KEY !== '') { + let uri; + if(query !== ''){ + uri = dailymotion.baseAPIUrl + '/videos?limit='+maxResults+'&search='+query+'&fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + } else { + uri = dailymotion.baseAPIUrl + '/videos?limit='+maxResults+'&sort=trending&fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + } + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if(response.statusCode === 200){ + dm_results = jsonBody.list; + } + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`, token); + } + } + } + + let results = []; + for(let i = 0; i < Math.max(dm_results.length, yt_results.length); i++){ + + // Youtube + if(yt_results.length > i){ + const yt_data = { + videoId: yt_results[i].id, + source: youtube.name, + imageUrl: yt_results[i].snippet.thumbnails.medium.url ? yt_results[i].snippet.thumbnails.medium.url : null, + title: yt_results[i].snippet.title ? yt_results[i].snippet.title : null, + channelTitle: yt_results[i].snippet.channelTitle ? yt_results[i].snippet.channelTitle : null, + channelUrl: youtube.baseChannelUrl+yt_results[i].snippet.channelId ? yt_results[i].snippet.channelId : null, + description: yt_results[i].snippet.description ? yt_results[i].snippet.description : null, + embedUrl: 'https://www.youtube.com/embed/'+yt_results[i].id, + interest: await asyncInterest(yt_results[i].snippet.categoryId, youtube.name), + views: yt_results[i].statistics.viewCount ? parseInt(yt_results[i].statistics.viewCount) : null, + likes: yt_results[i].statistics.likeCount ? parseInt(yt_results[i].statistics.likeCount) : null, + dislikes: yt_results[i].statistics.dislikeCount ? parseInt(yt_results[i].statistics.dislikeCount) : null, + publishedAt: yt_results[i].snippet.publishedAt ? yt_results[i].snippet.publishedAt : null + }; + results.push(yt_data); + } + + // Dailymotion + if(dm_results.length > i) { + const channelTitle = dm_results[i]['owner.username'] ? dm_results[i]['owner.username'] : null; + const dm_data = { + videoId: dm_results[i].id ? dm_results[i].id : null, + source: dailymotion.name, + imageUrl: dm_results[i].thumbnail_480_url ? dm_results[i].thumbnail_480_url : null, + title: dm_results[i].title ? dm_results[i].title : null, + channelTitle: channelTitle.charAt(0).toUpperCase() + channelTitle.slice(1), + channelUrl: dailymotion.baseChannelUrl + channelTitle, + description: dm_results[i].description ? dm_results[i].description : null, + embedUrl: dm_results[i].embed_url ? dm_results[i].embed_url : null, + interest: await asyncInterest(dm_results[i]['channel.name'], dailymotion.name), + views: dm_results[i].views_total ? parseInt(dm_results[i].views_total) : null, + likes: dm_results[i].likes_total ? parseInt(dm_results[i].likes_total) : null, + dislikes: null, + publishedAt: dm_results[i].created_time ? new Date(dm_results[i].created_time * 1000) : null + }; + results.push(dm_data); + } + } + return sendMessage(res, 31, results, token); + } else{ + return sendError(res, 500, -1, `No q given`, token); + } +}; + +// Get Video with id of source +exports.get = (req, res) => { + if(typeof req.query.source !== 'undefined' && typeof req.params.id !== 'undefined'){ + const source = req.query.source; + const id = req.params.id; + if(source === youtube.shortname){ + if(youtube.YOUTUBE_API_KEY !== 'undefined' && youtube.YOUTUBE_API_KEY !== ''){ + const uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&id='+id+'&key='+youtube.YOUTUBE_API_KEY; + request(uri,{},async function (error, response, body){ + if(typeof body !== 'undefined'){ + const jsonBody = JSON.parse(body); + if(jsonBody.items.length !== 0 && + typeof jsonBody.items[0] !== 'undefined' && + typeof jsonBody.items[0].id !== 'undefined' && + jsonBody.items[0].id === id){ + const imageUrl = jsonBody.items[0].snippet.thumbnails.standard.url ? jsonBody.items[0].snippet.thumbnails.standard.url : null; + const title = jsonBody.items[0].snippet.title ? jsonBody.items[0].snippet.title : null; + const channelId = jsonBody.items[0].snippet.channelId ? jsonBody.items[0].snippet.channelId : null; + const channelTitle = jsonBody.items[0].snippet.channelTitle ? jsonBody.items[0].snippet.channelTitle : null; + const description = jsonBody.items[0].snippet.description ? jsonBody.items[0].snippet.description : null; + //const embedUrl = jsonBody.embed_url ? jsonBody.embed_url : null; + const publishedAt = jsonBody.items[0].snippet.publishedAt ? jsonBody.items[0].snippet.publishedAt : null; + const interest = jsonBody.items[0].snippet.categoryId ? await asyncInterest(jsonBody.items[0].snippet.categoryId, youtube.name): null; + const views = jsonBody.items[0].statistics.viewCount ? parseInt(jsonBody.items[0].statistics.viewCount) : null; + const likes = jsonBody.items[0].statistics.likeCount ? parseInt(jsonBody.items[0].statistics.likeCount) : null; + const dislikes = jsonBody.items[0].statistics.dislikeCount ? parseInt(jsonBody.items[0].statistics.dislikeCount) : null; + const data = { + videoId: id, + source: youtube.name, + imageUrl: imageUrl, + title: title, + channelTitle: channelTitle, + channelUrl: youtube.baseChannelUrl+channelId, + description: description, + embedUrl: 'https://www.youtube.com/embed/'+id, + interest: interest, + views: views, + likes: likes, + dislikes: dislikes, + publishedAt: publishedAt + }; + return sendMessage(res, 32, data); + } else{ + return sendError(res, 404, -1, `No result`); + } + } else{ + return sendError(res, 500, -1, error); + } + }); + } else{ + return sendError(res, 500, -1, `Error Env Variable YOUTUBE_API_KEY missing, please contact the admin.`); + } + } else if(source === dailymotion.shortname){ + if(dailymotion.DAILYMOTION_API_KEY !== 'undefined' && dailymotion.DAILYMOTION_API_KEY !== ''){ + const uri = dailymotion.baseAPIUrl+'/video/'+id+'?fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + request(uri,{},async function (error, response, body) { + if (typeof body !== 'undefined') { + const jsonBody = JSON.parse(body); + if(response.statusCode === 200 && + typeof jsonBody.id !== 'undefined' && + jsonBody.id === id){ + const imageUrl = jsonBody.thumbnail_480_url ? jsonBody.thumbnail_480_url : null; + const title = jsonBody.title ? jsonBody.title : null; + //const channelId = jsonBody['owner.id'] ? jsonBody['owner.id'] : null; + const channelTitle = jsonBody['owner.username'] ? jsonBody['owner.username'] : null; + const description = jsonBody.description ? jsonBody.description : null; + const embedUrl = jsonBody.embed_url ? jsonBody.embed_url : null; + const publishedAt = jsonBody.created_time ? new Date(jsonBody.created_time * 1000) : null; + const interest = jsonBody['channel.name'] ? await asyncInterest(jsonBody['channel.name'], dailymotion.name) : null; + const views = jsonBody.views_total ? parseInt(jsonBody.views_total) : null; + const likes = jsonBody.likes_total ? parseInt(jsonBody.likes_total) : null; + const dislikes = null; + const data = { + videoId: id, + source: dailymotion.name, + imageUrl: imageUrl, + title: title, + channelTitle: channelTitle.charAt(0).toUpperCase() + channelTitle.slice(1), + channelUrl: dailymotion.baseChannelUrl+channelTitle, + description: description, + embedUrl: embedUrl, + interest: interest, + views: views, + likes: likes, + dislikes: dislikes, + publishedAt: publishedAt + }; + return sendMessage(res, 32, data); + } else{ + return sendError(res, 404, -1, jsonBody.error.message); + } + } + }); + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`); + } + } else{ + return sendError(res, 500, -1, `Wrong source name`); + } + } else{ + return sendError(res, 500, -1, `No source or/and id given`); + } +}; + +// Create a new Video +exports.create = (req, res) => { + const token = checkLogin(req, res); + if(token && + typeof req.body.source !== 'undefined' && + typeof req.body.interest !== 'undefined' && + typeof req.params.id !== 'undefined'){ + const id = req.params.id; + Video.exists({userId: token.id, videoId: id, source: req.body.source, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Video already exists.", token); + } else{ + if(docs === null) { + let video; + + video = new Video({ + userId: token.id, + videoId: id, + source: req.body.source, + interest: req.body.interest, + watchedDates: [new Date()] + }); + + // Save Video in the database + video + .save(video) + .then(data => { + return sendMessage(res, 33, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Video.", token); + }); + } else{ + const id = docs._id.toString(); + Video.findByIdAndUpdate(id, {$push: {watchedDates: [new Date()]}}, {useFindAndModify: false, new: true}) + .then(data => { + if(data) { + return sendMessage(res, 33, data, token); + } else { + return sendError(res, 404, 105, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while updating the Video with id=${id}`, token); + }); + } + } + }); + } else { + return sendError(res, 500, -1, `No source or interest or id given`, token); + } +}; + +// Retrieve all Videos +exports.findAll = (req, res) => { + const token = checkLogin(req, res); + if(token){ + let query = {}; + let condition; + + const userId = req.query.userId; + condition = userId ? userId : undefined; + query.userId = condition; + + const videoId = req.query.videoId; + condition = videoId ? videoId : undefined; + query.videoId = condition; + + const source = req.query.source; + condition = source ? source : undefined; + query.source = condition; + + const interests = req.query.interests; + condition = interests ? {$in: interests} : undefined; + query["interests.interest"] = condition + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {videoId: 1}; + break; + case 'desc': + condition = {videoId: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {createdAt: -1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Video.find(query, {}, query_sort) + .then(data => { + return sendMessage(res, 34, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Videos.", token); + }); + } +}; + +// Find single Video with id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Video.findById(id, {}) + .then(data => { + if(data){ + return sendMessage(res, 35, data, token); + } else { + return sendError(res,404,105,`Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update Video with id +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + let update = {}; + let condition; + + const watchedDate = req.body.watchedDate; + if(typeof watchedDate !== 'undefined'){ + if(watchedDate){ + condition = {watchedDates: [new Date()]} + } else { + condition = undefined; + } + } else{ + condition = undefined; + } + update.$push = condition; + + const watchedDates = req.body.watchedDates; + condition = watchedDates ? watchedDates : undefined; + update.watchedDates = condition; + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + if(id && ObjectId.isValid(id)){ + Video.updateOne({_id: id, userId: token.id, isActive: true}, update) + .then(data => { + if(data) { + return sendMessage(res, 36, update, token); + } else { + return sendError(res, 404, -1, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete Video with id +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Video.updateOne({_id: id, userId: token.id, isActive: true}, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data.modifiedCount > 0) { + return sendMessage(res, 37, {message: `Video ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Videos +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res); + if(token) { + Video.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 38, { + message: `${data.modifiedCount} Videos were deleted successfully.`, + }); + }) + .catch(err => { + return sendError(res, 500, 100, err.message || "Some error occurred while removing all Videos."); + }); + } +}; diff --git a/jwtRS256.key.pub b/jwtRS256.key.pub new file mode 100644 index 0000000..6b52055 --- /dev/null +++ b/jwtRS256.key.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtW31Xj62sjbJVBxnn0G2 +Habc22q7/pFIBdfn8+OsajdNVnmtfRNOsSXZP8sNhXt1QLPSgxZ/wogG0fLXIX2+ +ewzPgqrwTnr+quJ1DZ6RqOY3G1PGOibgk25aHkIXJ/gTPk1yTT6pjUmKiaGKM8pt +M2wGwugCdEH5Wndgby8Jej30v/PPzyPxTSXrIWDeaSMX+jQyFZTGgEdgL7JvjkTj +qLtfWKIAcEeO4PzOlRXVvbzBoYphBiZqkbzEeuOjSLPxgy4cQdbqVMlJ/lZt0SBO +MLiIUBTufLcJS3ApesiZWWfUCq+pFFdhEABc9qrtVumzhmzWAv2rKVrHRXbguxc/ +eHKlRjAE4qmnNnTP2fsAuQIPkXVHOPWdXM1IBwnhXVB+XhxEHSANx/2oeKS6fO/e +1oNJCiVkHin9gC8vkU9seEN73lNKZ5wPXMqTYUGA65dCY+8li+n/1pveJOJozFk7 +amkmOAPTi44lBJmxRm88XBHC3TXz6tFqX3phMqFDcQs2D9s3/2UylK0dSH5MSLnb +9x24/ykO4RlPRVCC90vwlxzbnb0rfQVlT4dKcE6OIyXw3UsqIqFnXWmm+hnGu4QH +Ysr+i1VIhPOs9YdZwlqhzcPTuNcdxmxy9ZfZ8KlLIWbAMbSH+obwm4w+HYTZjspe +2MwrKGgzpl4YW7ct/ViqeQMCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/jwtRS256.sh b/jwtRS256.sh new file mode 100644 index 0000000..64ecaff --- /dev/null +++ b/jwtRS256.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key -q -N "" +openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub +#rm .env +echo "JWTRS256_PRIVATE_KEY='`cat ./jwtRS256.key | base64 -w 0`'" >> .env +echo "JWTRS256_PUBLIC_KEY='`cat ./jwtRS256.key.pub | base64 -w 0`'" >> .env +source .env +rm jwtRS256.key diff --git a/models/database/ads.model.js b/models/database/ads.model.js new file mode 100644 index 0000000..2389c44 --- /dev/null +++ b/models/database/ads.model.js @@ -0,0 +1,44 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + title: String, + images: { + type: Array, + default: [] + }, + url: { + type: String, + default: null + }, + interests: { + type: Array, + default: [] + }, + comment: { + type: String, + default: null + }, + views: { + type: Array, + default: [] + }, + isVisible: { + type: Boolean, + default: true + }, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("ads", schema); +}; diff --git a/models/database/playlists.model.js b/models/database/playlists.model.js new file mode 100644 index 0000000..ad065a5 --- /dev/null +++ b/models/database/playlists.model.js @@ -0,0 +1,24 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + videoIds: { + type: Array, + default: [] + }, + name: String, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("playlists", schema); +}; diff --git a/models/database/users.model.js b/models/database/users.model.js new file mode 100644 index 0000000..9467b3c --- /dev/null +++ b/models/database/users.model.js @@ -0,0 +1,48 @@ +const roles = require("../objects/role.model"); + +module.exports = mongoose => { + let schema = mongoose.Schema({ + email: String, + hashPass: String, // WARNING: We don't want to send back the hashPass + login: String, + role: { + type: Object, + default: roles.User + }, + company: String, + profileImageUrl: { + type: String, + default: "https://www.handiclubnimois.fr/wp-content/uploads/2020/10/blank-profile-picture-973460_1280.png" + }, + dateOfBirth: { + type: Date, + default: null + }, + gender: { + type: String, + default: null + }, + interests: { + type: Array, + default: null + }, + isActive: { + type: Boolean, + default: true + }, + lastConnexion: { + type: Date, + default: null + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("users", schema); +}; diff --git a/models/database/videos.model.js b/models/database/videos.model.js new file mode 100644 index 0000000..4d322da --- /dev/null +++ b/models/database/videos.model.js @@ -0,0 +1,29 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + videoId: String, + source: String, + interest: { + type: String, + default: null + }, + watchedDates: { + type: Array, + default: null + }, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("videos", schema); +}; diff --git a/models/mongodb.model.js b/models/mongodb.model.js new file mode 100644 index 0000000..9ddb7cd --- /dev/null +++ b/models/mongodb.model.js @@ -0,0 +1,19 @@ +const dbConfig = require("../config/mongodb.config"); +const mongoose = require("mongoose"); +mongoose.Promise = global.Promise; + +const db = {}; +db.mongoose = mongoose; + +if(typeof process.env.NODE_ENV !== 'undefined' && process.env.NODE_ENV === 'production'){ + db.url = dbConfig.prodUrl; +} else { + db.url = dbConfig.devUrl; +} + +db.users = require("./database/users.model")(mongoose); +db.playlists = require("./database/playlists.model")(mongoose); +db.videos = require("./database/videos.model")(mongoose); +db.ads = require("./database/ads.model")(mongoose); + +module.exports = db; diff --git a/models/objects/image.model.js b/models/objects/image.model.js new file mode 100644 index 0000000..72ec783 --- /dev/null +++ b/models/objects/image.model.js @@ -0,0 +1,10 @@ +class Image { + constructor(base64, url, description, type){ + this.base64 = base64; + this.url = url; + this.description = description; + this.type = type; + } +} + +module.exports = Image; diff --git a/models/objects/role.model.js b/models/objects/role.model.js new file mode 100644 index 0000000..2e1703c --- /dev/null +++ b/models/objects/role.model.js @@ -0,0 +1,22 @@ +module.exports = { + User: { + name: "user", + permission: 0, + isAccepted: true + }, + Advertiser: { + name: "advertiser", + permission: 5, + isAccepted: false + }, + Admin: { + name: "admin", + permission: 10, + isAccepted: false + }, + SuperAdmin: { + name: "superAdmin", + permission: 1000, + isAccepted: true + } +}; diff --git a/models/objects/video.categories.model.js b/models/objects/video.categories.model.js new file mode 100644 index 0000000..6eaab33 --- /dev/null +++ b/models/objects/video.categories.model.js @@ -0,0 +1,157 @@ +const {youtube, dailymotion} = require('../../config/host.config'); +module.exports = [ + { + id: 0, + interest: "Actualités", + categories: [ + {id: "news", name: "News", source: dailymotion.name}, + {id: "25", name: "News & Politics", source: youtube.name}, + ] + }, + { + id: 1, + interest: "Animaux", + categories: [ + {id: "animals", name: "animaux", source: dailymotion.name}, + {id: "15", name: "Pets & Animals", source: youtube.name}, + ] + }, + { + id: 2, + interest: "Arts", + categories: [ + {id: "creation", name: "Art", source: dailymotion.name}, + {id: "", name: "", source: youtube.name}, + ] + }, + { + id: 3, + interest: "Autos", + categories: [ + {id: "auto", name: "Auto-Moto", source: dailymotion.name}, + {id: "2", name: "Autos & Vehicles", source: youtube.name}, + ] + }, + { + id: 4, + interest: "Divertissements", + categories: [ + {id: "tv", name: "TV", source: dailymotion.name}, + {id: "fun", name: "Humour & Divertissement", source: dailymotion.name}, + {id: "webcam", name: "Webcam", source: dailymotion.name}, + {id: "23", name: "Comedy", source: youtube.name}, + {id: "24", name: "Entertainment", source: youtube.name}, + {id: "43", name: "Shows", source: youtube.name} + ] + }, + { + id: 5, + interest: "Éducation", + categories: [ + {id: "school", name: "Éducation", source: dailymotion.name}, + {id: "27", name: "Education", source: youtube.name} + ] + }, + { + id: 6, + interest: "Événements", + categories: [ + {id: "", name: "", source: dailymotion.name}, + {id: "19", name: "Travel & Events", source: youtube.name}, + ] + }, + { + id: 7, + interest: "Films", + categories: [ + {id: "shortfilms", name: "Cinéma", source: dailymotion.name}, + {id: "1", name: "Film & Animation", source: youtube.name}, + {id: "18", name: "Short Movies", source: youtube.name}, + {id: "30", name: "Movies", source: youtube.name}, + {id: "31", name: "Anime/Animation", source: youtube.name}, + {id: "32", name: "Action/Adventure", source: youtube.name}, + {id: "33", name: "Comedy", source: youtube.name}, + {id: "35", name: "Documentary", source: youtube.name}, + {id: "36", name: "Drama", source: youtube.name}, + {id: "39", name: "Horror", source: youtube.name}, + {id: "40", name: "Sci-Fi/Fantasy", source: youtube.name}, + {id: "41", name: "Thriller", source: youtube.name}, + {id: "42", name: "Shorts", source: youtube.name}, + {id: "44", name: "Trailers", source: youtube.name} + ] + }, + { + id: 8, + interest: "Jeux vidéo", + categories: [ + {id: "videogames", name: "Jeux vidéo", source: dailymotion.name}, + {id: "20", name: "Gaming", source: youtube.name}, + ] + }, + { + id: 9, + interest: "Kids", + categories: [ + {id: "kids", name: "Kids", source: dailymotion.name}, + {id: "", name: "", source: youtube.name}, + ] + }, + { + id: 10, + interest: "Modes de vie", + categories: [ + {id: "lifestyle", name: "Lifestyle & Tutoriels", source: dailymotion.name}, + {id: "26", name: "Howto & Style", source: youtube.name}, + ] + }, + { + id: 11, + interest: "Musiques", + categories: [ + {id: "music", name: "Musique", source: dailymotion.name}, + {id: "10", name: "Music", source: youtube.name}, + ] + }, + { + id: 12, + interest: "People", + categories: [ + {id: "people", name: "Amis & Famille", source: dailymotion.name}, + {id: "21", name: "Videoblogging", source: youtube.name}, + {id: "22", name: "People & Blogs", source: youtube.name}, + {id: "37", name: "Family", source: youtube.name}, + ] + }, + { + id: 13, + interest: "Science et Technologie", + categories: [ + {id: "tech", name: "Tech", source: dailymotion.name}, + {id: "28", name: "Science & Technology", source: youtube.name}, + ] + }, + { + id: 14, + interest: "Sports", + categories: [ + {id: "sport", name: "Sport", source: dailymotion.name}, + {id: "17", name: "Sports", source: youtube.name}, + ] + }, + { + id: 15, + interest: "Voyages", + categories: [ + {id: "travel", name: "Voyages", source: dailymotion.name}, + {id: "38", name: "Foreign", source: youtube.name}, + ] + }, + { + id: 16, + interest: "Autres", + categories: [ + {id: "29", name: "Nonprofits & Activism", source: youtube.name}, + {id: "33", name: "Classics", source: youtube.name} + ] + } +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a6e4f3 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "backend", + "version": "1.0.0", + "scripts": { + "ng": "ng", + "start": "node server.js", + "dev-win": "set NODE_ENV=development && node server.js", + "dev-nix": "export NODE_ENV=development && node server.js", + "prod-win": "set NODE_ENV=production && node server.js", + "prod-nix": "export NODE_ENV=production && node server.js" + }, + "private": true, + "dependencies": { + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "dotenv": "^10.0.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "mongoose": "^6.0.12", + "request": "^2.88.2", + "rxjs": "~6.6.0", + "tslib": "^2.0.0", + "typescript": "~4.3.5", + "zone.js": "~0.11.3" + }, + "devDependencies": { + } +} diff --git a/routes/ad.routes.js b/routes/ad.routes.js new file mode 100644 index 0000000..738892b --- /dev/null +++ b/routes/ad.routes.js @@ -0,0 +1,24 @@ +const ads = require("../controllers/ad.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Create a new Ad + router.post("/ad/create", ads.create); + + // Retrieve all Ad from id if admin or session id + router.get("/ad/findAll", ads.findAll); + + // Find single Ad from id if admin or session id + router.get("/ad/findOne/:id", ads.findOne); + + // Update a Ad with ad id + router.put("/ad/update/:id", ads.update); + + // Delete a Ad with ad id + router.delete("/ad/delete/:id", ads.delete); + + // Delete all Ad from id if admin or session id + router.delete("/ad/deleteAll", ads.deleteAll); + + app.use('/api', router); +}; diff --git a/routes/misc.routes.js b/routes/misc.routes.js new file mode 100644 index 0000000..7aa5be6 --- /dev/null +++ b/routes/misc.routes.js @@ -0,0 +1,9 @@ +const misc = require("../controllers/misc.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Get all interests available + router.get("/misc/getInterests", misc.getInterests); + + app.use('/api', router); +}; diff --git a/routes/playlist.routes.js b/routes/playlist.routes.js new file mode 100644 index 0000000..3b9beeb --- /dev/null +++ b/routes/playlist.routes.js @@ -0,0 +1,24 @@ +const playlists = require("../controllers/playlist.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Create a new Playlist + router.post("/playlist/create", playlists.create); + + // Retrieve all Playlist from id if admin or session id + router.get("/playlist/findAll", playlists.findAll); + + // Find single Playlist from id if admin or session id + router.get("/playlist/findOne/:id", playlists.findOne); + + // Update a Playlist with playlist id + router.put("/playlist/update/:id", playlists.update); + + // Delete a Playlist with playlist id + router.delete("/playlist/delete/:id", playlists.delete); + + // Delete all Playlists from id if admin or session id + router.delete("/playlist/deleteAll", playlists.deleteAll); + + app.use('/api', router); +}; diff --git a/routes/user.routes.js b/routes/user.routes.js new file mode 100644 index 0000000..a7a9c38 --- /dev/null +++ b/routes/user.routes.js @@ -0,0 +1,43 @@ +const users = require("../controllers/user.controller"); +//const {cors, corsOptions} = require("../config/cors.config"); +module.exports = app => { + let router = require("express").Router(); + + // Authenticate a User + router.post("/user/auth", users.auth); + + // Logout a User + router.delete("/user/logout", users.logout); + + // Request password reset with email + router.post("/user/resetPass", users.resetPass); + + // Create and Save a new User + router.post("/user/create", users.create); + + // Retrieve all Users if admin + router.get("/user/findAll", users.findAll); + + // Find single User from id if admin or session id + router.get("/user/findOne/:id", users.findOne); + + // Update a User from id if admin or session id + router.put("/user/update/:id", users.update); + + // Delete a User from id if admin or session id + router.delete("/user/delete/:id", users.delete); + + // Delete all Users if superAdmin + router.delete("/user/deleteAll", users.deleteAll); + + // Get all Roles depending on the User session id + router.get("/user/roles", users.roles); + + // Get 1 or multiple ad adapted to the User session id + router.get("/user/ad", users.ad); + + // Get History + router.get("/user/history", users.history); + + app.use('/api', router); +}; diff --git a/routes/video.routes.js b/routes/video.routes.js new file mode 100644 index 0000000..a07de28 --- /dev/null +++ b/routes/video.routes.js @@ -0,0 +1,30 @@ +const videos = require("../controllers/video.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Search Videos + router.get("/video/search", videos.search); + + // Get Video with id of source + router.get("/video/get/:id", videos.get); + + // Create a new Video + router.post("/video/create/:id", videos.create); + + // Retrieve all Videos + router.get("/video/findAll", videos.findAll); + + // Find single Video with id + router.get("/video/findOne/:id", videos.findOne); + + // Update Video with id + router.put("/video/update/:id", videos.update); + + // Delete Video with id + router.delete("/video/delete/:id", videos.delete); + + // Delete all Videos + router.delete("/video/deleteAll", videos.deleteAll); + + app.use('/api', router); +}; diff --git a/server.js b/server.js new file mode 100644 index 0000000..fad2e85 --- /dev/null +++ b/server.js @@ -0,0 +1,96 @@ +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +const cors = require('cors'); +const whitelist = [ + 'http://127.0.0.1:4200', + 'http://127.0.0.1:4201', + 'https://admin-polynotfound.herokuapp.com', + 'https://polynotfound.herokuapp.com' +]; +const corsOptionsDelegate = (req, callback) => { + let corsOptions; + + if (whitelist.indexOf(req.header('Origin')) !== -1) { + corsOptions = { + origin: true, + credentials: true + } + } else { + corsOptions = { + origin: false, + credentials: true + } + } + callback(null, corsOptions) +} +app.use(cors(corsOptionsDelegate)); + +const cookieParser = require('cookie-parser'); +app.use(cookieParser()); + +const bodyParser = require('body-parser'); +app.use(bodyParser.urlencoded({extended:true})); +app.use(bodyParser.json()); + +const db = require("./models/mongodb.model"); +console.log("Db Url: ",db.url); +db.mongoose + .connect(db.url, { + useNewUrlParser: true, + useUnifiedTopology: true + }, function (err){ + const admin = new db.mongoose.mongo.Admin(db.mongoose.connection.db); + admin.buildInfo(function (err, info) { + console.log("MongoDB Version: "+info.version); + }); + if(err){ + console.log("Cannot connect to the database!", err); + process.exit(); + } else{ + console.log("Connected to the database!", db.url); + } + }); + +require("./routes/user.routes")(app); +require("./routes/playlist.routes")(app); +require("./routes/video.routes")(app); +require("./routes/ad.routes")(app); +require("./routes/misc.routes")(app); + +const roles = require("./models/objects/role.model"); +const User = db.users; +const login = 'superAdmin'; +const hashPass = 'hashPassSuperAdmin'; +const mail = 'superAdmin@email.admin'; + +User.exists({role: roles.SuperAdmin}, function (err, docs){ + if(err){ + console.log("Some error occurred while checking if superAdmin already exists."); + } else{ + if(docs === null){ + const user = new User({ + login: login, + hashPass: hashPass, + email: mail, + role: roles.SuperAdmin + }); + user + .save(user) + .then(data => { + data.hashPass = undefined; // Hiding hashPass on return + console.log(data); + }) + .catch(err => { + console.log(err.message || "Some error occurred while creating superAdmin."); + }); + } else { + console.log("superAdmin already exist !"); + } + } +}); + +app.listen(port, '0.0.0.0',() => { + console.log (`listening on port ${port}`); +});