diff --git a/client/src/hooks/getMentions.js b/client/src/hooks/getMentions.js index 79b09a9..830d5d0 100644 --- a/client/src/hooks/getMentions.js +++ b/client/src/hooks/getMentions.js @@ -3,7 +3,7 @@ import axios from "axios"; export const getMentions = async (dispatch, keyword, platforms, page, sort) => { const platformsArray = Object.keys(platforms).filter((key) => platforms[key]); - let platformsString = platformsArray.join(); + const platformsString = platformsArray.join(); try { const url = `/api/mentions?platforms=${platformsString}${ @@ -11,7 +11,6 @@ export const getMentions = async (dispatch, keyword, platforms, page, sort) => { }&page=${page}&sort=${sort}`; const res = await axios.get(url); - return res.data; } catch (err) { dispatch({ diff --git a/client/src/layout/Mention.js b/client/src/layout/Mention.js index 2ba6267..3daea2e 100644 --- a/client/src/layout/Mention.js +++ b/client/src/layout/Mention.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext } from "react"; import { Typography, Card, @@ -9,7 +9,8 @@ import { import { makeStyles } from "@material-ui/core/styles"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSmile } from "@fortawesome/free-regular-svg-icons"; -import redditLogo from "../utils/images/reddit-logo.png" +import redditLogo from "../utils/images/reddit-logo.png"; +import { UserContext } from "../context/user"; const useStyles = makeStyles((theme) => ({ root: { @@ -68,19 +69,19 @@ const useStyles = makeStyles((theme) => ({ }, })); - const image = (image) => { - if (image === "default" || image === "self") { - return redditLogo; - } else { - return image; - } - }; - +const image = (image) => { + if (image === "default" || image === "self") { + return redditLogo; + } else { + return image; + } +}; const Mention = ({ mention }) => { const classes = useStyles(); const keyword = "DolphinCorp"; - const regex = new RegExp(`${keyword}`, "i"); + const { searchTerm } = useContext(UserContext); + const regex = new RegExp(`${searchTerm}`, "i"); const indexK = mention.title.search(regex); return ( @@ -94,8 +95,8 @@ const Mention = ({ mention }) => { {/* {mention.title.substring(0, indexK)} - {keyword} - {mention.title.substring(indexK + keyword.length)} */} + {searchTerm} + {mention.title.substring(indexK + searchTerm.length)} */} {mention.title} diff --git a/client/src/pages/HomePage.js b/client/src/pages/HomePage.js index 8b72170..7a5f126 100644 --- a/client/src/pages/HomePage.js +++ b/client/src/pages/HomePage.js @@ -7,6 +7,7 @@ import SortToggle from "../layout/SortToggle"; import { makeStyles } from "@material-ui/core/styles"; import Spinner from "../layout/Spinner"; import { getMentions } from "../hooks/getMentions"; +import InfiniteScroll from "react-infinite-scroller"; const useStyles = makeStyles((theme) => ({ box: { @@ -23,6 +24,7 @@ const HomePage = () => { const [switching, setSwitching] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [sort, setSort] = useState("date"); + const { dispatch, error, searchTerm, user } = useContext(UserContext); const loadMore = async () => { @@ -60,7 +62,7 @@ const HomePage = () => { setCurrentPage(1); }) .catch((err) => alert("Cookie expired. Please log in again")); - }, [searchTerm, user.platforms]); + }, [searchTerm, user.platforms, dispatch]); if (mentionDatas === null) return ; @@ -97,6 +99,7 @@ const HomePage = () => { error={error} /> )} + diff --git a/client/src/pages/PagesWrapper.js b/client/src/pages/PagesWrapper.js index c985279..08a11b0 100644 --- a/client/src/pages/PagesWrapper.js +++ b/client/src/pages/PagesWrapper.js @@ -7,7 +7,7 @@ const PagesWrapper = ({ children }) => { useEffect(() => { authenticate(dispatch); - }, [isAuthenticated]); + }, [isAuthenticated, dispatch]); return <>{children}; }; diff --git a/client/src/pages/PrivateRoute.js b/client/src/pages/PrivateRoute.js index 69528e3..2330ef9 100644 --- a/client/src/pages/PrivateRoute.js +++ b/client/src/pages/PrivateRoute.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from "react"; +import React, { useContext } from "react"; import { Route, Redirect } from "react-router-dom"; import { UserContext } from "../context/user"; diff --git a/server/app.js b/server/app.js index 027d48a..a7b3817 100644 --- a/server/app.js +++ b/server/app.js @@ -12,6 +12,7 @@ const pingRouter = require("./routes/ping"); const mentionRouter = require("./routes/mentionRoutes"); const userRouter = require("./routes/userRoutes"); const mongoose = require("mongoose"); +const { connectRedis, handleTaskQueues } = require("./utils/jobHandler"); const connectDB = async () => { try { @@ -31,6 +32,10 @@ const connectDB = async () => { connectDB(); +connectRedis(); // if error, try start redis server in background + +handleTaskQueues(); + const { json, urlencoded } = express; var app = express(); @@ -53,37 +58,4 @@ app.use(notFound); // error handler app.use(errorHandler); -// test redis -// to start redis server in background -// run "redis-server --daemonize yes" -// to stop the server running "redis-cli shutdown" -const redis = require("redis"); -const client = redis.createClient({ - host: process.env.REDIS_HOST, - port: process.env.REDIS_PORT, -}); -client.on("error", (err) => { - console.log("Error " + err); -}); - -client.on("ready", function () { - console.log("redis is running"); -}); - -// test bull -var Queue = require("bull"); - -const myFirstQueue = new Queue("my-first-queue", { - redis: { port: process.env.REDIS_PORT, host: process.env.REDIS_HOST }, -}); - -// job producer -const job = myFirstQueue.add(); - -// job consumer -myFirstQueue.process(function (job, done) { - // call done when finished - done(console.log(`Job with id ${job.id} has been completed`)); -}); - module.exports = app; diff --git a/server/controllers/mentionController.js b/server/controllers/mentionController.js index ef906d5..56f8cc5 100644 --- a/server/controllers/mentionController.js +++ b/server/controllers/mentionController.js @@ -42,10 +42,17 @@ const getMentions = async (req, res) => { // Fetching Mentions pertaining to selected platforms // Sorting handled by MongoDB - const fetchMentions = async (array, sorting) => { const results = await Mention.find({ - $or: [...getPlatformsObject(array)], + $and: [ + { + $or: [ + { title: { $regex: req.user.name, $options: "i" } }, + { content: { $regex: req.user.name, $options: "i" } }, + ], + }, + { $or: [...getPlatformsObject(array)] }, + ], }).sort(getSortOption(sorting)); return results; diff --git a/server/controllers/userController.js b/server/controllers/userController.js index ade4ccf..c7f095a 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -32,10 +32,6 @@ const signUp = async (req, res) => { secure: false, // should be true in Production ! }); - // Added for Co-op Midterm Presentation - // To be handled later on by BullMQ - await addMentionsToDB(user.name); - res.status(201).json({ _id: user._id, name: user.name, @@ -66,10 +62,6 @@ const signIn = async (req, res) => { secure: false, // should be true in Production ! }); - // Added for Co-op Midterm Presentation - // To be handled later on by BullMQ - await addMentionsToDB(user.name); - res.status(201).json({ _id: user._id, name: user.name, diff --git a/server/dump.rdb b/server/dump.rdb new file mode 100644 index 0000000..18c3b4e Binary files /dev/null and b/server/dump.rdb differ diff --git a/server/models/mentionModel.js b/server/models/mentionModel.js index 145d328..53ca084 100644 --- a/server/models/mentionModel.js +++ b/server/models/mentionModel.js @@ -25,6 +25,10 @@ const mentionSchema = mongoose.Schema({ type: Number, default: 0, }, + url: { + type: String, + required: true, + }, }); module.exports = mongoose.model("Mention", mentionSchema); diff --git a/server/models/userModel.js b/server/models/userModel.js index efe05e0..26e50fe 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -44,4 +44,8 @@ userSchema.pre("save", async function (next) { this.password = await bcrypt.hash(this.password, salt); }); +userSchema.statics.getAllCompanies = async function () { + return await this.distinct("name"); +}; + module.exports = mongoose.model("User", userSchema); diff --git a/server/utils/jobHandler.js b/server/utils/jobHandler.js new file mode 100644 index 0000000..e16542e --- /dev/null +++ b/server/utils/jobHandler.js @@ -0,0 +1,71 @@ +const redis = require("redis"); +const Queue = require("bull"); +const User = require("../models/userModel"); +const Mention = require("../models/mentionModel"); +const { addMentionsToDB } = require("./scraper"); + +const connectRedis = () => { + // to start redis server in background + // run "redis-server --daemonize yes" + // to stop the server running "redis-cli shutdown" + const client = redis.createClient({ + host: process.env.REDIS_HOST, + port: process.env.REDIS_PORT, + }); + client.on("error", (err) => { + console.log("Error " + err); + }); + + client.on("ready", function () { + console.log("redis is running"); + }); +}; + +const handleTaskQueues = () => { + const companiesQueue = new Queue("queue-for-getting-componies", { + redis: { port: process.env.REDIS_PORT, host: process.env.REDIS_HOST }, + }); + + const scrapingQueue = new Queue("queue-for-scraping", { + redis: { port: process.env.REDIS_PORT, host: process.env.REDIS_HOST }, + }); + // job producer, repeated every 15 mins + companiesQueue.add( + {}, + { + repeat: { cron: "*/15 * * * *" }, + delay: 2000, + jobId: "repeatCompaniesUpdate", + } + ); + + // to remove existedQueue use + // companiesQueue.empty(); + + // job consumer + companiesQueue.process(async function (job, done) { + try { + User.getAllCompanies().then((companies) => + companies.forEach((company) => { + for (var platform in User.schema.obj.platforms) { + scrapingQueue.add({ company: company, platform: platform }); + } + }) + ); + done(new Error("error adding companies to queue")); + } catch (err) { + console.log(err); + } + }); + + scrapingQueue.process(async function (job, done) { + try { + await addMentionsToDB(job.data.company, job.data.platform); + done(new Error(`${job.data.company} and ${job.data.platform} error`)); + } catch (err) { + console.log(err); + } + }); +}; + +module.exports = { connectRedis, handleTaskQueues }; diff --git a/server/utils/reddit.js b/server/utils/reddit.js index af87826..1ed5f8a 100644 --- a/server/utils/reddit.js +++ b/server/utils/reddit.js @@ -16,16 +16,16 @@ const searchRecursive = async (term, after = "", posts = []) => { try { const res = await axios.get(url); - if (res.status === 200) { - res.data.data.children.forEach((post) => { + res.data.data.children.forEach(async (post) => { posts.push({ - content: post.data.url, + content: post.data.selftext || post.data.url, title: post.data.title, platform: "reddit", image: image(post.data.thumbnail), date: new Date(post.data.created * 1000), popularity: post.data.ups, + url: post.data.url, }); }); if (res.data.data.after) { diff --git a/server/utils/scraper.js b/server/utils/scraper.js index 20f2646..332a4a1 100644 --- a/server/utils/scraper.js +++ b/server/utils/scraper.js @@ -1,13 +1,34 @@ -const Mention = require("../models/mentionModel"); // Added for Co-op Midterm Presentation -const { searchRecursive } = require("../utils/reddit"); // Added for Co-op Midterm Presentation +const Mention = require("../models/mentionModel"); +const { searchRecursive } = require("../utils/reddit"); -// Added for Co-op Midterm Presentation -// To be handled later on by BullMQ -const addMentionsToDB = async (company) => { +// To be handled by BullMQ +const addMentionsToDB = async (company, platform) => { try { - const posts = await searchRecursive(company); - await Mention.deleteMany(); - await Mention.insertMany(posts); + switch (platform) { + case "reddit": + const posts = await searchRecursive(company); + posts.forEach(async (post) => { + const bool = await Mention.exists({ url: post.url }); + if (!bool) { + await Mention.create(post); + } + }); + break; + case "twitter": + break; + case "facebook": + break; + case "amazon": + break; + case "forbes": + break; + case "shopify": + break; + case "businessinsider": + break; + default: + console.log(`oops! ${platform} is not inside our platform`); + } } catch (error) { console.log(error.message); }