├── .gitignore ├── public ├── images │ ├── icon.png │ ├── link.png │ ├── post.png │ ├── default.png │ └── image.png ├── css │ ├── profile.css │ ├── post.css │ ├── submit.css │ ├── auth.css │ └── style.css └── js │ ├── sort.js │ ├── media.js │ ├── settings.js │ ├── submit.js │ ├── index.js │ ├── load.js │ ├── post.js │ └── comment.js ├── views ├── partials │ ├── sidebar_subreddit_info.ejs │ ├── jumbotron.ejs │ ├── sidebar_index_subreddits.ejs │ ├── sort.ejs │ ├── sidebar_index_subscribed.ejs │ ├── head.ejs │ ├── sidebar_index_actions.ejs │ ├── nav.ejs │ └── sidebar_subreddit_actions.ejs ├── error.ejs ├── settings.ejs ├── subreddit │ ├── subreddit_post.ejs │ ├── subreddit_link.ejs │ ├── subreddit.ejs │ └── subreddit_search.ejs ├── front │ ├── front_subreddit.ejs │ ├── front_post.ejs │ ├── front_link.ejs │ ├── front.ejs │ └── front_search.ejs ├── auth │ ├── auth_login.ejs │ └── auth_register.ejs ├── profile │ ├── profile_comments.ejs │ ├── profile_saved_comments.ejs │ ├── profile_posts.ejs │ └── profile_saved_posts.ejs └── post.ejs ├── configs └── database.js ├── models ├── subreddit.js ├── account.js ├── comment.js ├── postState.js ├── commentState.js ├── profile.js └── post.js ├── package.json ├── routes ├── settings.js ├── profile.js ├── front.js ├── subreddit.js ├── index.js ├── auth.js └── api.js ├── controllers ├── settings_controller.js ├── front_controller.js ├── post_controller.js ├── comment_controller.js ├── subreddit_controller.js ├── profile_controller.js └── submit_controller.js ├── app.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /public/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaychris/reddit-clone/HEAD/public/images/icon.png -------------------------------------------------------------------------------- /public/images/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaychris/reddit-clone/HEAD/public/images/link.png -------------------------------------------------------------------------------- /public/images/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaychris/reddit-clone/HEAD/public/images/post.png -------------------------------------------------------------------------------- /public/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaychris/reddit-clone/HEAD/public/images/default.png -------------------------------------------------------------------------------- /public/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaychris/reddit-clone/HEAD/public/images/image.png -------------------------------------------------------------------------------- /views/partials/sidebar_subreddit_info.ejs: -------------------------------------------------------------------------------- 1 |

Information

2 |

<%= desc %>

3 | -------------------------------------------------------------------------------- /public/css/profile.css: -------------------------------------------------------------------------------- 1 | #profile-pic { 2 | display: block; 3 | margin: auto; 4 | } 5 | 6 | #profile-info { 7 | display: block; 8 | text-align: center; 9 | } -------------------------------------------------------------------------------- /public/css/post.css: -------------------------------------------------------------------------------- 1 | .comment-wrapper { 2 | background-color: #e9ecef; 3 | margin-bottom: 15px; 4 | border-radius: 0.25rem; 5 | } 6 | 7 | .info p { 8 | margin-top: 10px; 9 | margin-bottom: 10px; 10 | } -------------------------------------------------------------------------------- /configs/database.js: -------------------------------------------------------------------------------- 1 | module.exports.config = { 2 | uri: 'mongodb+srv://admin:anime@cluster0-e7lsm.mongodb.net/test?retryWrites=true', 3 | options: { 4 | dbName: 'reddit-app', 5 | useNewUrlParser: true 6 | }, 7 | } -------------------------------------------------------------------------------- /models/subreddit.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const subredditSchema = new mongoose.Schema({ 4 | name: String, 5 | description: String 6 | }); 7 | 8 | module.exports = mongoose.model('Subreddit', subredditSchema); -------------------------------------------------------------------------------- /views/partials/jumbotron.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | <%= name %> 5 |

6 |
7 |
-------------------------------------------------------------------------------- /models/account.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const accountSchema = new mongoose.Schema({ 4 | username: { 5 | type: String, 6 | unique: true 7 | }, 8 | password: { 9 | type: String 10 | }, 11 | created: Date 12 | }); 13 | 14 | module.exports = mongoose.model('Account', accountSchema); -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const commentSchema = new mongoose.Schema({ 4 | body: String, 5 | time: { 6 | type: Date, 7 | default: Date.now() 8 | }, 9 | username: String, 10 | ref: mongoose.Schema.Types.ObjectId, 11 | votes: { 12 | type: Number, 13 | default: 0 14 | } 15 | }); 16 | 17 | module.exports = mongoose.model('Comment', commentSchema); -------------------------------------------------------------------------------- /models/postState.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const postStateSchema = new mongoose.Schema({ 4 | username: String, 5 | ref: mongoose.Schema.Types.ObjectId, 6 | vote: { 7 | type: String, 8 | default: "neutral" 9 | }, 10 | saved: { 11 | type: Boolean, 12 | default: false 13 | } 14 | }); 15 | 16 | module.exports = mongoose.model('PostState', postStateSchema, "postStates"); -------------------------------------------------------------------------------- /models/commentState.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const commentStateSchema = new mongoose.Schema({ 4 | username: String, 5 | ref: mongoose.Schema.Types.ObjectId, 6 | vote: { 7 | type: String, 8 | default: "neutral" 9 | }, 10 | saved: { 11 | type: Boolean, 12 | default: false 13 | } 14 | }); 15 | 16 | module.exports = mongoose.model('CommentState', commentStateSchema, "commentStates"); -------------------------------------------------------------------------------- /models/profile.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const profileSchema = new mongoose.Schema({ 4 | username: String, 5 | karma_post: { 6 | type: Number, 7 | default: 0 8 | }, 9 | karma_comment: { 10 | type: Number, 11 | default: 0 12 | }, 13 | saved_posts: Array, 14 | saved_comments: Array, 15 | subscribed: Array, 16 | owned: Array 17 | }); 18 | 19 | module.exports = mongoose.model('Profile', profileSchema); -------------------------------------------------------------------------------- /public/css/submit.css: -------------------------------------------------------------------------------- 1 | .submit-wrapper { 2 | background-color: #e9ecef; 3 | margin-bottom: 15px; 4 | border-radius: 0.25rem; 5 | padding: 15px; 6 | border: 1px solid #ced4da; 7 | } 8 | 9 | 10 | .post-wrapper { 11 | background-color: #e9ecef; 12 | margin-bottom: 15px; 13 | border-radius: 0.25rem; 14 | } 15 | 16 | p { 17 | padding: 10px; 18 | } 19 | 20 | .down-enabled { 21 | color: blue; 22 | } 23 | 24 | .up-enabled { 25 | color: orange; 26 | } -------------------------------------------------------------------------------- /views/partials/sidebar_index_subreddits.ejs: -------------------------------------------------------------------------------- 1 |

Subbeddits (<%=subreddits.length %>)

2 | 3 | 4 | <% if (subreddits != undefined) { 5 | subreddits.forEach((s) => { %> 6 | 7 | 8 | 9 | <% }); %> 10 | <% if(subreddits.length == 0) { %> 11 | You are not subscribed to any subbeddit 12 | <% } %> 13 | <% } %> 14 | 15 |
/r/<%= s.name %>
-------------------------------------------------------------------------------- /views/partials/sort.ejs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 5 | 10 |

-------------------------------------------------------------------------------- /public/js/sort.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | var urlParams = new URLSearchParams(window.location.search) 3 | 4 | switch (urlParams.get('sort')) { 5 | case "top": 6 | $(".sort-top").addClass('active'); 7 | break; 8 | case "new": 9 | $(".sort-new").addClass('active'); 10 | break; 11 | case "old": 12 | $(".sort-old").addClass('active'); 13 | break; 14 | default: 15 | $(".sort-top").addClass('active'); 16 | } 17 | }) -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const postSchema = new mongoose.Schema({ 4 | username: String, 5 | subreddit: String, 6 | title: String, 7 | body: String, 8 | time: { 9 | type: Date, 10 | default: Date.now() 11 | }, 12 | type: String, 13 | link: String, 14 | votes: { 15 | type: Number, 16 | default: 0 17 | }, 18 | num_of_comments: { 19 | type: Number, 20 | default: 0 21 | } 22 | }); 23 | 24 | module.exports = mongoose.model('Post', postSchema); -------------------------------------------------------------------------------- /views/partials/sidebar_index_subscribed.ejs: -------------------------------------------------------------------------------- 1 | <% if(isAuth) { %> 2 |

Subscribed (<%=subscribed.length %>)

3 | 4 | 5 | <% if (subscribed != undefined) { %> 6 | <% subscribed.forEach((s) => { %> 7 | 8 | 9 | 10 | <% }); %> 11 | <% } else { %> 12 | You are not subscribed to any subbeddit 13 | <% } %> 14 | 15 |
/r/<%= s %>
16 | <% } %> 17 | -------------------------------------------------------------------------------- /public/js/media.js: -------------------------------------------------------------------------------- 1 | $(".post-body").ready(function () { 2 | let query = $('.post-body') 3 | let url = query.text() 4 | 5 | function youtube_parser(url){ 6 | var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/; 7 | var match = url.match(regExp); 8 | return (match&&match[7].length==11)? match[7] : false; 9 | } 10 | 11 | let id = youtube_parser(url) 12 | 13 | if(id) { 14 | query.html(``); 15 | } 16 | }) -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Error 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

404 Error

15 |

Unable to find the requested webpage

16 | Homepage 17 |
18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /views/partials/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | <%=title%> 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reddit-npm", 3 | "version": "1.0.0", 4 | "description": "A reddit clone", 5 | "engines": { 6 | "node": "8.11.4" 7 | }, 8 | "main": "app.js", 9 | "scripts": { 10 | "start": "node app.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "isaychris", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bcrypt": "^3.0.0", 17 | "body-parser": "^1.18.3", 18 | "ejs": "^2.6.1", 19 | "ejs-lint": "^0.3.0", 20 | "express": "^4.16.3", 21 | "express-session": "^1.15.6", 22 | "moment": "^2.22.2", 23 | "mongoose": "^5.2.14", 24 | "passport": "^0.4.0", 25 | "validator": "^10.7.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /routes/settings.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | // CONTROLLERS 5 | let settings_controller = require("../controllers/settings_controller") 6 | 7 | // ROUTES 8 | router.get('/settings', authenticateUser(), settings_controller.view); 9 | router.put('/settings/change/password', settings_controller.change_password); 10 | router.delete('/settings/delete/account', settings_controller.delete_account); 11 | 12 | // MIDDLEWARE 13 | function authenticateUser() { 14 | return function (req, res, next) { 15 | if (req.isAuthenticated()) { 16 | return next(); 17 | } 18 | res.redirect("login"); 19 | }; 20 | } 21 | 22 | module.exports = router -------------------------------------------------------------------------------- /routes/profile.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | // CONTROLLERS 5 | let profile_controller = require("../controllers/profile_controller") 6 | 7 | // ROUTES 8 | router.get('/:user/posts', profile_controller.posts); 9 | router.get('/:user/comments', profile_controller.comments); 10 | router.get('/:user/saved/posts', profile_controller.saved_posts); 11 | router.get('/:user/saved/comments', profile_controller.saved_comments); 12 | 13 | router.get('/:user', function (req, res) { 14 | res.redirect(`/u/${req.params.user}/posts`); 15 | }); 16 | 17 | router.get('/:user/saved', function (req, res) { 18 | res.redirect(`/u/${req.params.user}/saved/posts`); 19 | }) 20 | 21 | 22 | module.exports = router -------------------------------------------------------------------------------- /routes/front.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | // CONTROLLERS 5 | let submit_controller = require("../controllers/submit_controller") 6 | let front_controller = require("../controllers/front_controller") 7 | 8 | // ROUTES 9 | router.get('/', front_controller.get_all); 10 | router.get('/submit/post', submit_controller.front_post_view); 11 | router.get('/submit/link', submit_controller.front_link_view); 12 | router.get('/submit/subreddit', submit_controller.subreddit_view); 13 | router.post('/submit/post', submit_controller.front_post); 14 | router.post('/submit/link', submit_controller.front_link); 15 | router.post('/submit/subreddit', submit_controller.subreddit); 16 | router.post('/search', submit_controller.front_search); 17 | 18 | module.exports = router; -------------------------------------------------------------------------------- /views/partials/sidebar_index_actions.ejs: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 |
12 | New link 13 | New post 14 |
15 |

16 | 17 | Create subbeddit
-------------------------------------------------------------------------------- /routes/subreddit.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | // CONTROLLERS 5 | let subreddit_controller = require("../controllers/subreddit_controller") 6 | let comment_controller = require("../controllers/comment_controller") 7 | let submit_controller = require("../controllers/submit_controller") 8 | 9 | // ROUTES 10 | router.get('/:subreddit', subreddit_controller.get_all); 11 | router.get('/:subreddit/:id/comments', subreddit_controller.get_post); 12 | router.get('/:subreddit/submit/post', submit_controller.subreddit_post_view); 13 | router.get('/:subreddit/submit/link', submit_controller.subreddit_link_view); 14 | 15 | router.post('/:subreddit/submit/post', submit_controller.subreddit_post); 16 | router.post('/:subreddit/:id/comments', comment_controller.comment); 17 | router.post('/:subreddit/submit/link', submit_controller.subreddit_link); 18 | router.post('/:subreddit/search', submit_controller.subreddit_search); 19 | 20 | router.get('/:subreddit/:id', function (req, res) { 21 | res.redirect(`/r/${req.params.subreddit}/${req.params.id}/comments`) 22 | }); 23 | 24 | module.exports = router -------------------------------------------------------------------------------- /views/partials/nav.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/js/settings.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | $("#change-password").click(function () { 3 | let password = prompt("Enter a new password:") 4 | 5 | if (password) { 6 | $.ajax({ 7 | type: "put", 8 | url: `/settings/change/password`, 9 | data: { 10 | password: `${password}` 11 | } 12 | }).done(function () { 13 | alert("Your password has been changed.") 14 | }).catch(function () { 15 | alert("Unable to change password.") 16 | }) 17 | } 18 | }) 19 | 20 | 21 | $("#delete-account").click(function () { 22 | let input = confirm("Are you sure you want to delete your account?") 23 | 24 | if (input) { 25 | $.ajax({ 26 | type: "delete", 27 | url: `/settings/delete/account` 28 | }).done(function (res) { 29 | alert("Your account has been deleted.") 30 | location.href = "/logout" 31 | }).catch(function (res) { 32 | alert("Unable to delete account.") 33 | }) 34 | } 35 | }) 36 | 37 | 38 | }) -------------------------------------------------------------------------------- /views/partials/sidebar_subreddit_actions.ejs: -------------------------------------------------------------------------------- 1 | 9 |
10 | New link 11 | New post 12 |
13 |

14 | 15 | <% if(locals.isauth) { %> 16 | <% if(state == false) { %> 17 | Subscribe 18 | <% } else if (state == true) { %> 19 | Unsubscribe 20 | <% } %> 21 | <% } else {%> 22 | Subscribe 23 | <% } %> 24 |
-------------------------------------------------------------------------------- /public/js/submit.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | 3 | // onsubmit validation handler for when user submits a subreddit 4 | // it checks if the subreddit is a valid one by making a query to the database via the server 5 | $("#form-subreddit").submit(function (e) { 6 | e.preventDefault() 7 | 8 | $.ajax({ 9 | type: "get", 10 | url: `/submit/check/${$("#subreddit_form").val()}` 11 | }).done(function (isvalid) { 12 | if (isvalid == true) { 13 | $('#subreddit_form').addClass('is-invalid') 14 | } else { 15 | $('form').unbind('submit').submit() 16 | } 17 | }); 18 | }); 19 | 20 | // onsubmit validation handler for when user submits a post or link 21 | // it checks if the subreddit is a valid one by making a query to the database via the server 22 | $("#form-post-or-link").submit(function (e) { 23 | e.preventDefault() 24 | 25 | $.ajax({ 26 | type: "get", 27 | url: `/submit/check/${$("#subreddit_form").val()}` 28 | }).done(function (isvalid) { 29 | if (isvalid == false) { 30 | $('#subreddit_form').addClass('is-invalid') 31 | } else { 32 | $('form').unbind('submit').submit() 33 | } 34 | }); 35 | }); 36 | }); -------------------------------------------------------------------------------- /views/settings.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Beddit - Settings 6 | 7 | 8 | 9 | 10 | 11 | <%- include('partials/nav', {user: locals.user, karma}) %> 12 | 13 | 14 |
15 |
16 |
17 |
Settings
18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const router = express.Router(); 4 | 5 | // CONTROLLERS 6 | let post_controller = require('../controllers/post_controller') 7 | let comment_controller = require('../controllers/comment_controller') 8 | let subreddit_controller = require('../controllers/subreddit_controller') 9 | 10 | // POSTS ROUTES 11 | router.put('/edit/post/:id', post_controller.edit); 12 | router.delete('/delete/post/:id', post_controller.delete); 13 | router.put('/save/post/:id', post_controller.save); 14 | router.put('/unsave/post/:id', post_controller.unsave); 15 | router.put('/vote/post/:id', post_controller.vote); 16 | router.get('/check/states/posts', post_controller.check); 17 | 18 | // COMMENTS ROUTES 19 | router.put('/edit/comment/:id', comment_controller.edit); 20 | router.delete('/delete/comment/:id', comment_controller.delete); 21 | router.put('/save/comment/:id', comment_controller.save); 22 | router.put('/unsave/comment/:id', comment_controller.unsave); 23 | router.put('/vote/comment/:id', comment_controller.vote); 24 | router.get('/check/states/comments', comment_controller.check); 25 | 26 | // SUBBREDDIT ROUTES 27 | router.get('/submit/check/:subreddit', subreddit_controller.check_subreddit); 28 | router.put('/subscribe/:subreddit', subreddit_controller.subscribe); 29 | router.put('/unsubscribe/:subreddit', subreddit_controller.unsubscribe); 30 | 31 | module.exports = router; -------------------------------------------------------------------------------- /controllers/settings_controller.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | let Profile = require("../models/profile"); 3 | let Account = require("../models/account"); 4 | 5 | exports.view = function (req, res) { 6 | if (req.isAuthenticated()) { 7 | Profile.find({ 8 | username: req.session.user 9 | }, function (err, result) { 10 | if (err) throw err; 11 | 12 | if (result.length) { 13 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 14 | res.render('./settings', { 15 | karma: karma 16 | }) 17 | } 18 | }) 19 | } else { 20 | res.render('./login') 21 | } 22 | } 23 | exports.change_password = function (req, res) { 24 | bcrypt.hash(req.body.password, 10, function (err, hash) { 25 | if (err) throw err 26 | 27 | Account.update({ 28 | username: req.session.user 29 | }, { 30 | password: hash 31 | }, function (err, result) { 32 | if (err) throw err; 33 | 34 | if (result) { 35 | console.log(`[${req.session.user}] password changed!`) 36 | res.send("OK") 37 | } 38 | }); 39 | }); 40 | } 41 | 42 | exports.delete_account = function (req, res) { 43 | Account.find({ 44 | username: req.session.user 45 | }).remove(function (err, result) { 46 | if (err) throw err; 47 | 48 | if (result) { 49 | console.log(`[${req.session.user}] account deleted!`) 50 | res.send("OK") 51 | } 52 | }); 53 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const mongoose = require("mongoose"); 3 | const bodyParser = require("body-parser"); 4 | const passport = require("passport") 5 | const db = require("./configs/database"); 6 | const app = express(); 7 | 8 | app.set('view engine', 'ejs'); 9 | 10 | // making the connection to mongo database 11 | mongoose.connect(db.config.uri, db.config.options); 12 | 13 | // middlewares for express routes 14 | app.use(express.static("public")); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ 17 | extended: true 18 | })); 19 | app.use(require('express-session')({ 20 | secret: 'keyboard cat', 21 | resave: true, 22 | saveUninitialized: true 23 | })); 24 | 25 | app.use(passport.initialize()); 26 | app.use(passport.session()); 27 | 28 | app.use(function (req, res, next) { 29 | res.locals.user = req.session.user; 30 | res.locals.isauth = req.isAuthenticated(); 31 | next(); 32 | }); 33 | 34 | // express routes that exist 35 | app.use('/', require('./routes/auth')); 36 | app.use('/', require('./routes/front')); 37 | app.use('/', require('./routes/index')); 38 | app.use('/', require('./routes/settings')); 39 | app.use('/r/', require('./routes/subreddit')); 40 | app.use('/u/', require('./routes/profile')); 41 | app.use('/api', require('./routes/api')); 42 | 43 | 44 | app.get('*', function (req, res) { 45 | res.status(404); 46 | res.render("./error") 47 | }); 48 | 49 | // functions for persistant sessions 50 | passport.serializeUser(function (user_id, done) { 51 | done(null, user_id); 52 | }); 53 | passport.deserializeUser(function (user_id, done) { 54 | done(null, user_id); 55 | }); 56 | 57 | app.listen(process.env.PORT || 5000, function () { 58 | console.log("listening on port 5000!"); 59 | }); -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | 3 | // function that converts all time dates on a page to relative time 4 | $("time").each(function (index) { 5 | $(this).text(moment($(this).attr('title')).fromNow()) 6 | }); 7 | 8 | // event handler that replaces broken thumbnails with a default one. 9 | $(".thumbnail").on('error', function () { 10 | $(this).attr('src', '../images/image.png'); 11 | }); 12 | 13 | // event handler that checks if the user is authenticated when they click an auth-only action. 14 | $(".auth-req").click(function (e) { 15 | if ($("#auth").text() == "false") { 16 | alert("You must be logged in to do that.") 17 | e.stopImmediatePropagation(); 18 | return false; 19 | } 20 | }); 21 | 22 | // event handler for when user subscribes to subreddit 23 | $("#subscribe").click(function () { 24 | let sub = $("#subreddit-name").text(); 25 | let that = $(this) 26 | 27 | // if current text is subscribe, update subscription in database 28 | if ($(this).text() == "Subscribe") { 29 | $.ajax({ 30 | type: "put", 31 | url: `/subscribe/${sub}` 32 | }).done(function (res) { 33 | that.text('Unsubscribe') 34 | alert(`You are now subscribed to ${sub}`) 35 | }) 36 | 37 | // if current text is unsubscribe, update subscription in database 38 | } else if ($(this).text() == "Unsubscribe") { 39 | alert('unsubscribe text') 40 | $.ajax({ 41 | type: "put", 42 | url: `/unsubscribe/${sub}`, 43 | }).done(function (res) { 44 | that.text('Subscribe') 45 | alert(`You are now unsbscribed from ${sub}`) 46 | }) 47 | } 48 | return false; 49 | }) 50 | 51 | }); -------------------------------------------------------------------------------- /views/subreddit/subreddit_post.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: info.name + " - new post"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: info.name}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 16 | 17 |
18 |

new post

19 |
20 | <% if(!locals.user) { %> 21 | You must be logged in to submit a post / link / subbeddit. 22 | <% } else { %> 23 |
24 |
25 |
26 | 27 |
28 | <% } %> 29 | 30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /views/subreddit/subreddit_link.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: info.name + " - new link"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: info.name}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 16 | 17 | 18 |
19 |

new link

20 |
21 | <% if(!locals.user) { %> 22 | You must be logged in to submit a post / link / subbeddit. 23 | <% } else { %> 24 |
25 |
26 |
27 | 28 |
29 | <% } %> 30 | 31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beddit 2 | A reddit clone written using node.js / express.js / mongodb / passport.js 3 | 4 | ### Website: 5 | https://seiya-beddit.herokuapp.com/ 6 | 7 | ![Image](https://i.imgur.com/HbpGIZP.png) 8 | 9 | 10 | ### Features: 11 | * Frontpage 12 | * Subreddits 13 | * Submitting comments / posts / links 14 | * Voting on posts / comments 15 | * Saving posts / comments 16 | * Editing posts / comments 17 | * Deleting posts / comments 18 | * Subscribing 19 | * Searching 20 | * Sorting 21 | * Profile pages 22 | * Karma system 23 | * Relative time 24 | * Validation 25 | * Login / Register 26 | * Hash / salted passwords 27 | * Change password / delete account 28 | * API 29 | 30 | # API: 31 | URL | Method | Details | Body 32 | ---- | ---- | ---- | ---- 33 | /api/frontpage | GET | Retrieves all posts from frontpage 34 | /api/r/```subreddit``` | GET | Retrieves all posts from ```subreddit``` 35 | /api/post/```id``` | GET | Retrieves post by ```id``` 36 | /api/post/```id```/comments | GET | Retrieves all comments for post by ```id``` 37 | /api/u/```user``` | GET | Retrieves profile information about ```user``` 38 | /api/u/```user```/posts | GET | Retrieves all posts by ```user``` 39 | /api/u/```user```/comments | GET | Retrieves all comments by ```user``` 40 | /api/register | POST | Registers an account | ```username```, ```password``` 41 | 42 | # Screenshots: 43 | ![Image](https://i.imgur.com/QWmcJG7.png) 44 | ![Image](https://i.imgur.com/Cf1kpy7.png) 45 | ![Image](https://i.imgur.com/vwjY3hI.png) 46 | ![Image](https://i.imgur.com/f0cJpfS.png) 47 | ![Image](https://i.imgur.com/fOl9v5E.png) 48 | ![Image](https://i.imgur.com/RSZ1ruw.png) 49 | ![Image](https://i.imgur.com/4aHHz4W.png) 50 | ![Image](https://i.imgur.com/g1sjo8w.png) 51 | ![Image](https://i.imgur.com/BVlLpbB.png) 52 | ![Image](https://i.imgur.com/YfNOatP.png) 53 | ![Image](https://i.imgur.com/c9r0FlE.png) 54 | ![Image](https://i.imgur.com/Hny7gIj.png) 55 | ![Image](https://i.imgur.com/G5TlBe2.png) 56 | ![Image](https://i.imgur.com/EQNKpbN.png) 57 | ![Image](https://i.imgur.com/s8jfuap.png) 58 | -------------------------------------------------------------------------------- /public/js/load.js: -------------------------------------------------------------------------------- 1 | $("article").ready(function () { 2 | 3 | // if the user is authenticated, load the users comment states and post states 4 | if ($("#auth").text() == "true") { 5 | //load the users posts states which include vote state and save state 6 | $.ajax({ 7 | type: "get", 8 | url: `/check/states/posts/` 9 | }).done(function (data) { 10 | $(".post").each(function (index) { 11 | 12 | for (let ele of data) { 13 | if (ele.ref == $(this).data("ref")) { 14 | if (ele.vote == "up") { 15 | $(this).find(".upvote-post").addClass("up-enabled") 16 | } else if (ele.vote == "down") { 17 | $(this).find(".downvote-post").addClass("down-enabled") 18 | } 19 | 20 | if (ele.saved == true) { 21 | $(this).find(".save-post").text("unsave") 22 | } 23 | } 24 | } 25 | }); 26 | }) 27 | //load the users comments states which include vote state and save state 28 | $.ajax({ 29 | type: "get", 30 | url: `/check/states/comments/` 31 | }).done(function (data) { 32 | $(".comment").each(function (index) { 33 | 34 | for (let ele of data) { 35 | if (ele.ref == $(this).data("ref")) { 36 | if (ele.vote == "up") { 37 | $(this).find(".upvote-comment").addClass("up-enabled") 38 | } else if (ele.vote == "down") { 39 | $(this).find(".downvote-comment").addClass("down-enabled") 40 | } 41 | 42 | if (ele.saved == true) { 43 | $(this).find(".save-comment").text("unsave") 44 | } 45 | } 46 | } 47 | }); 48 | }) 49 | } 50 | }) -------------------------------------------------------------------------------- /public/css/auth.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fbfbfb; 3 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23d0d0d0' fill-opacity='0.41'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 4 | } 5 | 6 | #footer { 7 | background-color: #f7f7f7; 8 | border: 1px solid rgba(0, 0, 0, 0.125); 9 | text-align: center; 10 | border-radius: calc(0.25rem - 1px); 11 | color: rgba(0, 0, 0, 0.4); 12 | line-height: 35px; 13 | } 14 | 15 | #wrapper { 16 | margin: 50px auto 50px auto; 17 | width: 350px; 18 | } 19 | 20 | #footer a { 21 | color: rgba(0, 0, 0, 0.4); 22 | } 23 | 24 | form { 25 | margin: 0px; 26 | } 27 | 28 | #lower { 29 | font-size: 13px; 30 | color: rgba(0, 0, 0, 0.4); 31 | } -------------------------------------------------------------------------------- /views/front/front_subreddit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Frontpage - new subbedit"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: "Frontpage"}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 15 | 16 |
17 |

new subbeddit

18 |
19 | <% if(!locals.user) { %> 20 | You must be logged in to submit a post / link / subbeddit. 21 | <% } else { %> 22 |
23 | 24 |
That subbeddit already exists!

25 |
26 | 27 |
28 | <% } %> 29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /views/front/front_post.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Frontpage - new link"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: "Frontpage"}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 15 | 16 |
17 |

new post

18 |
19 | <% if(!locals.user) { %> 20 | You must be logged in to submit a post / link / subbeddit. 21 | <% } else { %> 22 |
23 |
24 |
25 |
26 |
That subbeddit doesn't exist!

27 | 28 |
29 | <% } %> 30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /views/front/front_link.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Frontpage - new link"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: "Frontpage"}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 15 | 16 |
17 |

new link

18 |
19 | <% if(!locals.user) { %> 20 | You must be logged in to submit a post / link / subbeddit. 21 | <% } else { %> 22 | 23 |
24 |
25 |
26 | 27 |
That subbeddit doesn't exist!

28 | 29 |
30 | 31 | <% } %> 32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /views/auth/auth_login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Beddit - Login 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 |
20 |
21 | <% if (message != undefined) { %> 22 | 25 | <% } %> 26 |
27 |
Login
28 |
29 |

30 |
31 |
32 |
33 | 34 |

35 |
36 |
37 | 38 |

39 | 40 | Not a member? Join now 41 | 42 |
43 |
44 |

45 | 46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /views/auth/auth_register.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Beddit - Register 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 20 |
21 |
22 | <% if (message != undefined) { %> 23 | 26 | <% } %> 27 |
28 |
Register
29 |
30 |

31 |
32 |
33 |
34 | 35 |

36 |
37 |
38 | 39 |

40 | 41 | Already a member? Sign in 42 |
43 |
44 |

45 | 46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /controllers/front_controller.js: -------------------------------------------------------------------------------- 1 | let Subreddit = require("../models/subreddit"); 2 | let Post = require("../models/post"); 3 | let Profile = require("../models/profile"); 4 | let PostState = require("../models/postState") 5 | 6 | exports.get_all = function (req, res) { 7 | let subscribed = undefined; 8 | let subreddits = undefined; 9 | let posts = undefined; 10 | let karma = 0; 11 | let sort = undefined; 12 | 13 | switch (req.query.sort) { 14 | case "top": 15 | sort = { 16 | votes: -1 17 | } 18 | break; 19 | case "new": 20 | sort = { 21 | time: -1 22 | } 23 | break; 24 | case "old": 25 | sort = { 26 | time: 1 27 | } 28 | break; 29 | default: 30 | sort = { 31 | votes: -1 32 | } 33 | } 34 | 35 | Profile.find({ 36 | username: req.session.user 37 | }, function (err, result) { 38 | if (err) throw err; 39 | 40 | if (result.length) { 41 | subscribed = result[0]['subscribed']; 42 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 43 | } 44 | }).then(function () { 45 | Subreddit.find({}, function (err, doc) { 46 | if (err) throw err; 47 | 48 | if (doc.length) { 49 | subreddits = doc 50 | } 51 | }).then(function () { 52 | PostState.find({ 53 | username: req.session.user 54 | }, function (err, doc) { 55 | if (err) throw err; 56 | 57 | if (doc.length) { 58 | postStates = doc 59 | } 60 | }).then(function () { 61 | Post.find({}).sort(sort).exec(function (err, result) { 62 | if (err) throw err; 63 | if (result.length) { 64 | posts = result 65 | } 66 | 67 | console.log(`[Frontpage] fetching posts!`) 68 | res.render("./front/front", { 69 | posts: posts, 70 | subreddits: subreddits, 71 | subscribed: subscribed, 72 | karma: karma, 73 | isAuth: req.isAuthenticated() 74 | }) 75 | }); 76 | }); 77 | }); 78 | }); 79 | } -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | #auth { 2 | display: none; 3 | } 4 | 5 | 6 | .grid-container { 7 | margin-left: 15px; 8 | margin-right: 15px; 9 | display: grid; 10 | height: 100%; 11 | grid-template-columns: 1fr 320px; 12 | grid-template-rows: 1fr; 13 | grid-gap: 15px 15px; 14 | grid-template-areas: "main sidebar"; 15 | } 16 | 17 | .sidebar { 18 | grid-area: sidebar; 19 | } 20 | 21 | .main { 22 | grid-area: main; 23 | } 24 | 25 | .module { 26 | border: 1px solid #ced4da; 27 | border-radius: 0.25rem; 28 | } 29 | 30 | .main article { 31 | background-color: #e9ecef; 32 | margin-bottom: 15px; 33 | border-radius: 0.25rem; 34 | border: 1px solid #ced4da; 35 | } 36 | 37 | .main ul { 38 | list-style-type: none; 39 | margin: 0; 40 | padding: 0; 41 | } 42 | 43 | .main li { 44 | display: inline; 45 | } 46 | 47 | .main header { 48 | font-weight: bold; 49 | } 50 | 51 | .arrows { 52 | position: relative; 53 | float: left; 54 | padding-left: 5px; 55 | padding-right: 5px; 56 | margin-top: 5px; 57 | } 58 | 59 | 60 | .upvote, 61 | .downvote, 62 | .votes { 63 | display: block; 64 | margin: auto; 65 | text-align: center; 66 | } 67 | 68 | .info { 69 | margin-left: 35px; 70 | } 71 | 72 | .sidebar ul { 73 | list-style-type: none; 74 | margin: 0px; 75 | padding: 0px; 76 | } 77 | 78 | .actions button { 79 | display: block; 80 | width: 100%; 81 | } 82 | 83 | #user-info { 84 | float: right; 85 | color: white; 86 | } 87 | 88 | article { 89 | padding: 10px; 90 | } 91 | 92 | form { 93 | margin: 0; 94 | } 95 | 96 | tr { 97 | line-height: 5px !important; 98 | } 99 | 100 | .jumbotron-fluid { 101 | border-bottom: 1px solid #ced4da; 102 | padding: 32px; 103 | margin-bottom: 15px; 104 | } 105 | 106 | .border { 107 | border-radius: 0.25em; 108 | } 109 | 110 | .hide-options { 111 | display: none; 112 | } 113 | 114 | .upvote-post, 115 | .downvote-post, 116 | .upvote-comment, 117 | .downvote-comment { 118 | cursor: pointer; 119 | } 120 | 121 | .thumbnail { 122 | float: left; 123 | margin-top: 5px; 124 | margin-left: 10px; 125 | margin-right: 10px; 126 | border-radius: 0.50em; 127 | } 128 | 129 | .post-options li, 130 | .comment-options li { 131 | margin-right: 10px; 132 | } 133 | 134 | .post-body, 135 | .comment-body { 136 | white-space: pre-wrap; 137 | } -------------------------------------------------------------------------------- /views/front/front.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Beddit - Frontpage"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: "Frontpage"}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 16 | 17 |
18 | <%- include('../partials/sort') %> 19 | 20 | 21 | <% if (posts != undefined) { 22 | posts.forEach((post) => { %> 23 |
24 |
25 |
26 | <%= post.votes %> 27 |
28 |
29 | 30 | <% if(post.type == "img") { %> 31 | 32 | <% } else if (post.type == "link") { %> 33 | 34 | <% } else { %> 35 | 36 | <% } %> 37 | 38 |
39 |
40 | <% if(post.type == "post") { %> 41 | <%= post.title %> 42 | <% } else { %> 43 | <%= post.title %> 44 | <% } %> 45 | 46 |
47 |
48 | submitted 49 | by <%= post.username %> 50 | from /r/<%= post.subreddit %> 51 |
52 | 60 |
61 |
62 | <% });%> 63 | <% } else { %> 64 | There are no posts. 65 | <% } %> 66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /views/subreddit/subreddit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Beddit - " + info.name}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: info.name}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 15 | 16 |
17 | <%- include('../partials/sort') %> 18 | 19 | <% if (posts != undefined) { 20 | posts.forEach((post) => { %> 21 |
22 |
23 |
24 | <%= post.votes %> 25 |
26 |
27 | 28 | <% if(post.type == "img") { %> 29 | 30 | <% } else if (post.type == "link") { %> 31 | 32 | <% } else { %> 33 | 34 | <% } %> 35 | 36 |
37 |
38 | <% if(post.type == "post") { %> 39 | <%= post.title %> 40 | <% } else { %> 41 | <%= post.title %> 42 | <% } %> 43 | 44 |
45 |
46 | submitted 47 | by <%= post.username %> 48 | from /r/<%= post.subreddit %> 49 |
50 | 58 |
59 |
60 | <% });%> 61 | <% } else { %> 62 | There are no posts. 63 | <% } %> 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bcrypt = require("bcrypt"); 3 | const validator = require("validator"); 4 | const router = express.Router(); 5 | 6 | let Account = require("../models/account"); 7 | let Profile = require("../models/profile"); 8 | 9 | // route for when user logs out, session is destroyed and user redirected to login 10 | router.get("/logout", function (req, res) { 11 | console.log(`[Auth] ${req.session.user} has logged out`) 12 | 13 | req.session.destroy() 14 | res.redirect("/login") 15 | }); 16 | 17 | // route for when user views register page 18 | router.get("/register", function (req, res) { 19 | if (req.isAuthenticated()) { 20 | res.redirect("../") 21 | } else { 22 | res.render("./auth/auth_register", { 23 | message: undefined 24 | }); 25 | } 26 | }); 27 | 28 | 29 | // route for when user submits register details 30 | router.post("/register", validateRegister(), function (req, res) { 31 | // hash the password 32 | bcrypt.hash(req.body.password, 10, function (err, hash) { 33 | if (err) throw error 34 | 35 | // save account details with hash password in database 36 | Account({ 37 | username: req.body.username, 38 | password: hash, 39 | created: Date.now() 40 | }).save(function (err, doc) { 41 | // if user already exists, register page is rendered with error message 42 | if (err) { 43 | res.render("./auth/auth_register", { 44 | message: "Username already exists." 45 | }); 46 | 47 | // if not, user is redirected to index. 48 | } else { 49 | Profile({ 50 | username: req.body.username 51 | }).save(function (err, doc) { 52 | if (err) throw err 53 | }); 54 | 55 | // create session using passport js 56 | req.login(doc._id, function (err) { 57 | if (err) throw err 58 | 59 | req.session.user = req.body.username 60 | console.log(`[Auth] ${req.session.user} has registered`) 61 | res.redirect("../") 62 | }) 63 | } 64 | }) 65 | }) 66 | }) 67 | 68 | // route for when user views login page 69 | router.get("/login", function (req, res) { 70 | if (req.isAuthenticated()) { 71 | res.redirect("../") 72 | } else { 73 | res.render("./auth/auth_login", { 74 | message: undefined 75 | }) 76 | } 77 | }) 78 | 79 | // route for when user submits login details 80 | router.post("/login", function (req, res) { 81 | // make input not case sensitive 82 | req.body.username = req.body.username.toLowerCase() 83 | req.body.password = req.body.password.toLowerCase() 84 | 85 | // look up username in database 86 | Account.find({ 87 | username: req.body.username 88 | }, function (err, doc) { 89 | if (err) throw err 90 | 91 | // if nothing is returned, render login page with error message 92 | if (!doc.length) { 93 | res.render("./auth/auth_login", { 94 | message: "Username or password is incorrect." 95 | }) 96 | } else { 97 | // compare password with hashed password 98 | bcrypt.compare(req.body.password, doc[0].password, function (err, result) { 99 | if (err) throw err 100 | 101 | //if they match, redirect to index. 102 | if (result == true) { 103 | 104 | // create session using passport js 105 | req.login(doc[0]._id, function (err) { 106 | if (err) throw err; 107 | req.session.user = req.body.username 108 | 109 | console.log(`[Auth] ${req.session.user} has logged in`) 110 | res.redirect("../") 111 | }) 112 | 113 | //if not, redirect back to login. 114 | } else { 115 | console.log(`${req.session.user} failed to login`) 116 | res.render("./auth/auth_login", { 117 | message: "Username or password is incorrect." 118 | }) 119 | } 120 | }) 121 | } 122 | }) 123 | }) 124 | 125 | // MIDDLEWARE 126 | function validateRegister() { 127 | return function (req, res, next) { 128 | // make input not case sensitive 129 | req.body.username = req.body.username.toLowerCase(); 130 | req.body.password = req.body.password.toLowerCase(); 131 | 132 | if ( 133 | validator.isAlphanumeric(req.body.username) 134 | ) { 135 | console.log("authentication = " + req.isAuthenticated()); 136 | return next(); 137 | } 138 | res.render("./auth/auth_register", { 139 | message: "Invalid input. Try again." 140 | }) 141 | } 142 | } 143 | 144 | module.exports = router; -------------------------------------------------------------------------------- /controllers/post_controller.js: -------------------------------------------------------------------------------- 1 | let Post = require("../models/post"); 2 | let Profile = require("../models/profile"); 3 | let PostState = require("../models/postState") 4 | 5 | exports.check = function (req, res) { 6 | PostState.find({ 7 | username: req.session.user 8 | }, function (err, doc) { 9 | if (err) throw err 10 | 11 | if (doc.length) { 12 | res.send(doc) 13 | } 14 | }) 15 | } 16 | 17 | exports.edit = function (req, res) { 18 | Post.update({ 19 | _id: req.params.id 20 | }, { 21 | body: req.body.text 22 | }, function (err, result) { 23 | if (err) throw err; 24 | 25 | console.log(`[${req.params.id}] post edited!`) 26 | res.send("success") 27 | }) 28 | } 29 | 30 | exports.delete = function (req, res) { 31 | Post.find({ 32 | _id: req.params.id 33 | }) 34 | .remove(function (err, doc) { 35 | if (err) throw err; 36 | 37 | console.log(`[${req.params.id}] post deleted!`) 38 | res.send(doc); 39 | }); 40 | } 41 | 42 | exports.save = function (req, res) { 43 | Profile.update({ 44 | username: req.session.user 45 | }, { 46 | $push: { 47 | saved_posts: req.params.id 48 | } 49 | }, function (err, doc) { 50 | if (err) throw err; 51 | }); 52 | 53 | let query = { 54 | username: req.session.user, 55 | ref: req.params.id 56 | }; 57 | let update = { 58 | saved: true 59 | }; 60 | let options = { 61 | upsert: true, 62 | setDefaultsOnInsert: true 63 | }; 64 | 65 | PostState.findOneAndUpdate(query, update, options, function (error, doc) { 66 | if (error) throw error; 67 | 68 | if (doc) { 69 | console.log(`[${req.params.id}] post saved!`) 70 | } 71 | res.send("success") 72 | 73 | }) 74 | } 75 | 76 | exports.unsave = function (req, res) { 77 | let query = { 78 | username: req.session.user, 79 | ref: req.params.id 80 | }; 81 | let update = { 82 | saved: false 83 | }; 84 | let options = { 85 | upsert: true, 86 | setDefaultsOnInsert: true 87 | }; 88 | 89 | Profile.update({ 90 | username: req.session.user 91 | }, { 92 | $pull: { 93 | saved_posts: req.params.id 94 | } 95 | }, function (err, doc) { 96 | if (err) throw err; 97 | }); 98 | 99 | PostState.findOneAndUpdate(query, update, options, function (error, doc) { 100 | if (error) throw error; 101 | 102 | if (doc) { 103 | console.log(`[${req.params.id}] post unsaved!`) 104 | } 105 | res.send("success") 106 | 107 | }) 108 | } 109 | 110 | exports.vote = function (req, res) { 111 | console.log(req.params.id) 112 | 113 | 114 | if (req.body.action == "increment") { 115 | console.log("increment") 116 | Profile.update({ 117 | username: req.body.user 118 | }, { 119 | $inc: { 120 | karma_post: 1 121 | } 122 | }, function (err, result) { 123 | if (err) throw err; 124 | 125 | if (result) { 126 | console.log(`[${req.session.user}] post karma increased!`) 127 | } 128 | }); 129 | } else if (req.body.action == "decrement") { 130 | console.log("decrement") 131 | 132 | Profile.update({ 133 | username: req.body.user 134 | }, { 135 | $inc: { 136 | karma_post: -1 137 | } 138 | }, function (err, result) { 139 | if (err) throw err; 140 | 141 | if (result) { 142 | console.log(`[${req.session.user}] post karma decreased!`) 143 | } 144 | }); 145 | } 146 | 147 | let query = { 148 | username: req.session.user, 149 | ref: req.params.id 150 | }; 151 | let update = { 152 | vote: req.body.state 153 | }; 154 | let options = { 155 | upsert: true, 156 | setDefaultsOnInsert: true 157 | }; 158 | 159 | Post.update({ 160 | _id: req.params.id 161 | }, { 162 | votes: req.body.vote 163 | }, function (err, result) { 164 | if (err) throw err; 165 | 166 | if (result) { 167 | console.log(`[${req.params.id}] post vote count changed!`) 168 | } 169 | }) 170 | 171 | 172 | PostState.findOneAndUpdate(query, update, options, function (err, result) { 173 | if (err) throw err; 174 | 175 | if (result) { 176 | console.log(`[${req.params.id}] post vote count changed!`) 177 | res.send("OK") 178 | } 179 | }) 180 | } -------------------------------------------------------------------------------- /views/front/front_search.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "Frontpage - search"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: "Frontpage"}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 30 | 31 |
32 | <% if (posts != undefined) { %> 33 |

Results (<%= posts.length %>)

34 | <% } %> 35 | 36 | <% if (posts != undefined) { 37 | posts.forEach((post) => { %> 38 |
39 |
40 |
41 | <%= post.votes %> 42 |
43 |
44 | 45 | <% if(post.type == "img") { %> 46 | 47 | <% } else if (post.type == "link") { %> 48 | 49 | <% } else { %> 50 | 51 | <% } %> 52 | 53 |
54 |
55 | <% if(post.type == "post") { %> 56 | <%= post.title %> 57 | <% } else { %> 58 | <%= post.title %> 59 | <% } %> 60 | 61 |
62 |
63 | submitted 64 | by <%= post.username %> 65 | from /r/<%= post.subreddit %> 66 |
67 |
    68 |
  • comments
  • 69 |
  • save
  • 70 | <% if(post.username == locals.user) { %> 71 |
  • delete
  • 72 | <% } %> 73 | 74 |
75 |
76 |
77 | <% });%> 78 | <% } else { %> 79 |

Results (0)

80 | There are no posts. 81 | <% } %> 82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /views/profile/profile_comments.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "/u/" + profile_user + " - comments" }) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: profile_user}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 46 | 47 |
48 | <%- include('../partials/sort') %> 49 | 50 | <% if (comments != undefined) { 51 | comments.forEach((comment) => { %> 52 | 53 |
54 | 85 |
86 | <% });%> 87 | <% if(comments.length == 0) { %> 88 | There are no comments. 89 | <% } %> 90 | <% } else { %> 91 | There are no comments. 92 | <% } %> 93 | 94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /views/profile/profile_saved_comments.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "/u/" + profile_user + " - saved comments" }) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: profile_user}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 46 | 47 |
48 | <%- include('../partials/sort') %> 49 | 50 | <% if (comments != undefined) { 51 | comments.forEach((comment) => { %> 52 | 53 |
54 | 85 |
86 | <% });%> 87 | <% if(comments.length == 0) { %> 88 | There are no saved comments. 89 | <% } %> 90 | <% } else { %> 91 | They are no saved comments. 92 | <% } %> 93 | 94 | 95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bcrypt = require("bcrypt"); 3 | const validator = require("validator"); 4 | const router = express.Router(); 5 | 6 | let Post = require("../models/post"); 7 | let Comment = require("../models/comment"); 8 | let Profile = require("../models/profile"); 9 | let Account = require("../models/account"); 10 | 11 | 12 | router.get('/r/:subreddit', function (req, res) { 13 | Post.find({ 14 | subreddit: req.params.subreddit 15 | }) 16 | .sort({ 17 | votes: '-1' 18 | }).exec(function (err, doc) { 19 | if (err) throw err; 20 | 21 | if (doc.length) { 22 | res.json(doc) 23 | } else { 24 | res.status(404); 25 | res.json({ 26 | error: `Unable to find posts from /r/${req.params.subreddit}` 27 | }) 28 | } 29 | }) 30 | }); 31 | 32 | router.get('/frontpage', function (req, res) { 33 | Post.find({}).sort({ 34 | votes: '-1' 35 | }).exec(function (err, doc) { 36 | if (err) throw err; 37 | 38 | if (doc.length) { 39 | res.json(doc) 40 | } else { 41 | res.status(404); 42 | res.json({ 43 | error: `Unable to find posts.` 44 | }) 45 | } 46 | }) 47 | }) 48 | 49 | router.get('/comment/:id', function (req, res) { 50 | Comment.find({ 51 | _id: req.params.id 52 | }, function (err, doc) { 53 | if (err) throw err; 54 | 55 | if (doc.length) { 56 | res.json(doc[0]) 57 | } else { 58 | res.status(404); 59 | res.json({ 60 | error: `Unable to find comment.` 61 | }) 62 | } 63 | }) 64 | }) 65 | 66 | router.get('/post/:id', function (req, res) { 67 | Post.find({ 68 | _id: req.params.id 69 | }, function (err, doc) { 70 | if (err) throw err; 71 | 72 | if (doc.length) { 73 | res.json(doc[0]) 74 | } else { 75 | res.status(404); 76 | res.json({ 77 | error: `Unable to find post.` 78 | }) 79 | } 80 | }) 81 | }) 82 | 83 | router.get('/post/:id/comments', function (req, res) { 84 | Comment.find({ 85 | ref: req.params.id 86 | }, function (err, doc) { 87 | if (err) throw err; 88 | 89 | if (doc.length) { 90 | res.json(doc) 91 | } else { 92 | res.status(404); 93 | res.json({ 94 | error: `Unable to find any comments.` 95 | }) 96 | } 97 | }) 98 | }) 99 | 100 | router.get('/u/:profile', function (req, res) { 101 | Profile.find({ 102 | username: req.params.profile 103 | }, function (err, doc) { 104 | if (err) throw err; 105 | 106 | if (doc.length) { 107 | res.json(doc[0]) 108 | } else { 109 | res.status(404); 110 | res.json({ 111 | error: `Unable to find info for /u/${req.params.profile}.` 112 | }) 113 | } 114 | }) 115 | }); 116 | 117 | router.get('/u/:profile/posts', function (req, res) { 118 | Post.find({ 119 | username: req.params.profile 120 | }, function (err, doc) { 121 | if (err) throw err; 122 | 123 | if (doc.length) { 124 | res.json(doc) 125 | } else { 126 | res.status(404); 127 | res.json({ 128 | error: `Unable to find posts for /u/${req.params.profile}.` 129 | }) 130 | } 131 | }) 132 | }); 133 | 134 | router.get('/u/:profile/comments', function (req, res) { 135 | Comment.find({ 136 | username: req.params.profile 137 | }, function (err, doc) { 138 | if (err) throw err; 139 | 140 | if (doc.length) { 141 | res.json(doc) 142 | } else { 143 | res.status(404); 144 | res.json({ 145 | error: `Unable to find post.` 146 | }) 147 | } 148 | }) 149 | }); 150 | 151 | router.post('/register', function (req, res) { 152 | if (req.body.username && req.body.password) { 153 | req.body.username = req.body.username.toLowerCase(); 154 | 155 | if (validator.isAlphanumeric(req.body.username)) { 156 | bcrypt.hash(req.body.password, 10, function (err, hash) { 157 | if (err) throw err 158 | 159 | Account({ 160 | username: req.body.username, 161 | password: hash, 162 | created: Date.now() 163 | }).save(function (err, doc) { 164 | if (err) { 165 | console.log(err) 166 | res.status(409); 167 | res.json({ 168 | error: `Username '${req.body.username}' already exists.` 169 | }) 170 | } else { 171 | Profile({ 172 | username: req.body.username 173 | }).save(function (err, doc) { 174 | if (err) throw err 175 | 176 | res.json({ 177 | success: `Username '${req.body.username}' was registered.` 178 | }) 179 | }) 180 | } 181 | }) 182 | }) 183 | } else { 184 | res.status(400) 185 | res.json({ 186 | error: `Username must only include alphanumeric characters.` 187 | }) 188 | } 189 | } else { 190 | res.status(400) 191 | res.json({ 192 | error: `Username and password is required.` 193 | }) 194 | } 195 | }) 196 | 197 | module.exports = router; -------------------------------------------------------------------------------- /views/profile/profile_posts.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "/u/" + profile_user + " - posts" }) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: profile_user}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 48 | 49 |
50 | <%- include('../partials/sort') %> 51 | 52 | <% if (posts != undefined) { 53 | posts.forEach((post) => { %> 54 |
55 |
56 |
57 | <%= post.votes %> 58 |
59 |
60 | 61 | <% if(post.type == "img") { %> 62 | 63 | <% } else if (post.type == "link") { %> 64 | 65 | <% } else { %> 66 | 67 | <% } %> 68 | 69 |
70 |
71 | <% if(post.type == "post") { %> 72 | <%= post.title %> 73 | <% } else { %> 74 | <%= post.title %> 75 | <% } %> 76 | 77 |
78 |
79 | submitted 80 | by <%= post.username %> 81 | from /r/<%= post.subreddit %> 82 |
83 |
    84 |
  • comments
  • 85 |
  • save
  • 86 | <% if(post.username == locals.user) { %> 87 |
  • delete
  • 88 | <% } %> 89 | 90 |
91 |
92 |
93 | <% });%> 94 | <% if(posts.length == 0) { %> 95 | There are no posts. 96 | <% } %> 97 | 98 | <% } else { %> 99 | There are no posts. 100 | <% } %> 101 | 102 |
103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /views/profile/profile_saved_posts.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: "/u/" + profile_user + " - saved posts" }) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: profile_user}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 48 | 49 |
50 | <%- include('../partials/sort') %> 51 | 52 | <% if (posts != undefined) { 53 | posts.forEach((post) => { %> 54 |
55 |
56 |
57 | <%= post.votes %> 58 |
59 |
60 | 61 | <% if(post.type == "img") { %> 62 | 63 | <% } else if (post.type == "link") { %> 64 | 65 | <% } else { %> 66 | 67 | <% } %> 68 | 69 |
70 |
71 | <% if(post.type == "post") { %> 72 | <%= post.title %> 73 | <% } else { %> 74 | <%= post.title %> 75 | <% } %> 76 | 77 |
78 |
79 | submitted 80 | by <%= post.username %> 81 | from /r/<%= post.subreddit %> 82 |
83 |
    84 |
  • comments
  • 85 |
  • save
  • 86 | <% if(post.username == locals.user) { %> 87 |
  • delete
  • 88 | <% } %> 89 | 90 |
91 |
92 |
93 | <% });%> 94 | <% if(posts.length == 0) { %> 95 | There are no saved posts. 96 | <% } %> 97 | <% } else { %> 98 | There are no saved posts. 99 | <% } %> 100 | 101 |
102 |
103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /views/subreddit/subreddit_search.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('../partials/head', {title: info.name + " - search"}) %> 4 | 5 | 6 | <%- include('../partials/nav', {user: locals.user, karma}) %> 7 | <%- include('../partials/jumbotron', {name: info.name}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 39 | 40 |
41 | <% if (posts != undefined) { %> 42 |

Results (<%= posts.length %>)

43 | <% } %> 44 | 45 | <% if (posts != undefined) { 46 | posts.forEach((post) => { %> 47 |
48 |
49 |
50 | <%= post.votes %> 51 |
52 |
53 | 54 | <% if(post.type == "img") { %> 55 | 56 | <% } else if (post.type == "link") { %> 57 | 58 | <% } else { %> 59 | 60 | <% } %> 61 | 62 |
63 |
64 | <% if(post.type == "post") { %> 65 | <%= post.title %> 66 | <% } else { %> 67 | <%= post.title %> 68 | <% } %> 69 | 70 |
71 |
72 | submitted 73 | by <%= post.username %> 74 | from /r/<%= post.subreddit %> 75 |
76 |
    77 |
  • comments
  • 78 |
  • save
  • 79 | <% if(post.username == locals.user) { %> 80 |
  • delete
  • 81 | <% } %> 82 | 83 |
84 |
85 |
86 | <% });%> 87 | <% } else { %> 88 |

Results (0)

89 | There are no posts. 90 | <% } %> 91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /controllers/comment_controller.js: -------------------------------------------------------------------------------- 1 | let Post = require("../models/post"); 2 | let Comment = require("../models/comment"); 3 | let Profile = require("../models/profile"); 4 | let CommentState = require("../models/commentState") 5 | 6 | 7 | exports.check = function (req, res) { 8 | CommentState.find({ 9 | username: req.session.user 10 | }, function (err, doc) { 11 | if (err) throw err 12 | 13 | if (doc.length) { 14 | res.send(doc) 15 | } 16 | }) 17 | } 18 | exports.comment = function (req, res) { 19 | Post.update({ 20 | _id: req.params.id 21 | }, { 22 | $inc: { 23 | num_of_comments: 1 24 | } 25 | }, function (err, result) { 26 | if (err) throw err; 27 | 28 | if (!result.length) { 29 | console.log("something went wrong") 30 | } 31 | if (result.length) { 32 | console.log(`[${req.params.subreddit}] number of comment updated!`) 33 | } 34 | }) 35 | 36 | Comment({ 37 | body: req.body.comment, 38 | username: req.session.user, 39 | ref: req.params.id, 40 | }).save(function (err, doc) { 41 | if (err) throw err 42 | 43 | console.log(`[${req.params.subreddit}] comment posted!`) 44 | res.redirect(`/r/${req.params.subreddit}/${req.params.id}/comments`) 45 | }) 46 | } 47 | 48 | exports.edit = function (req, res) { 49 | Comment.update({ 50 | _id: req.params.id 51 | }, { 52 | body: req.body.text 53 | }, function (err, result) { 54 | if (err) throw err; 55 | 56 | console.log(`[${req.params.id}] comment edited!`) 57 | res.send("success") 58 | }) 59 | } 60 | 61 | exports.delete = function (req, res) { 62 | Comment.find({ 63 | _id: req.params.id 64 | }).exec().then((result) => { 65 | Post.update({ 66 | _id: result[0]['ref'] 67 | }, { 68 | $inc: { 69 | num_of_comments: -1 70 | } 71 | }, function (err, result) { 72 | if (err) throw err; 73 | 74 | if (result.length) { 75 | console.log(`[${req.params.subreddit}] number of comment updated!`) 76 | } 77 | }) 78 | }).catch((err) => { 79 | console.log(err) 80 | }) 81 | 82 | Comment.find({ 83 | _id: req.params.id 84 | }) 85 | .remove(function (err, doc) { 86 | if (err) throw err; 87 | 88 | console.log(`[${req.params.id}] comment deleted!`) 89 | res.send("OK"); 90 | }); 91 | } 92 | 93 | exports.save = function (req, res) { 94 | let query = { 95 | username: req.session.user, 96 | ref: req.params.id 97 | }; 98 | let update = { 99 | saved: true 100 | }; 101 | let options = { 102 | upsert: true, 103 | setDefaultsOnInsert: true 104 | }; 105 | 106 | Profile.update({ 107 | username: req.session.user 108 | }, { 109 | $push: { 110 | saved_comments: req.params.id 111 | } 112 | }, function (err, doc) { 113 | if (err) throw err; 114 | }); 115 | 116 | CommentState.findOneAndUpdate(query, update, options, function (error, doc) { 117 | if (error) throw error; 118 | 119 | if (doc) { 120 | console.log(`[${req.params.id}] comment saved!`) 121 | res.send("success") 122 | } 123 | }) 124 | } 125 | 126 | exports.unsave = function (req, res) { 127 | let query = { 128 | username: req.session.user, 129 | ref: req.params.id 130 | }; 131 | let update = { 132 | saved: false 133 | }; 134 | let options = { 135 | upsert: true, 136 | setDefaultsOnInsert: true 137 | }; 138 | 139 | Profile.update({ 140 | username: req.session.user 141 | }, { 142 | $pull: { 143 | saved_comments: req.params.id 144 | } 145 | }, function (err, doc) { 146 | if (err) throw err; 147 | }); 148 | 149 | CommentState.findOneAndUpdate(query, update, options, function (error, doc) { 150 | if (error) throw error; 151 | 152 | if (doc) { 153 | console.log(`[${req.params.id}] comment unsaved!`) 154 | res.send("success") 155 | } 156 | }) 157 | } 158 | 159 | exports.vote = function (req, res) { 160 | if (req.body.action == "increment") { 161 | console.log("increment") 162 | Profile.update({ 163 | username: req.body.user 164 | }, { 165 | $inc: { 166 | karma_comment: 1 167 | } 168 | }, function (err, result) { 169 | if (err) throw err; 170 | 171 | if (result) { 172 | console.log(`[${req.session.user}] comment karma increased!`) 173 | } 174 | }); 175 | } else if (req.body.action == "decrement") { 176 | console.log("decrement") 177 | 178 | Profile.update({ 179 | username: req.body.user 180 | }, { 181 | $inc: { 182 | karma_comment: -1 183 | } 184 | }, function (err, result) { 185 | if (err) throw err; 186 | 187 | if (result) { 188 | console.log(`[${req.session.user}] comment karma decreased!`) 189 | } 190 | }); 191 | } 192 | 193 | Comment.update({ 194 | _id: req.params.id 195 | }, { 196 | votes: req.body.vote 197 | }, function (err, result) { 198 | if (err) throw err; 199 | 200 | if (result) { 201 | console.log(`[${req.params.id}] comment vote count changed!`) 202 | } 203 | }); 204 | 205 | let query = { 206 | username: req.session.user, 207 | ref: req.params.id 208 | }; 209 | let update = { 210 | vote: req.body.state 211 | }; 212 | let options = { 213 | upsert: true, 214 | setDefaultsOnInsert: true 215 | }; 216 | 217 | CommentState.findOneAndUpdate(query, update, options, function (err, result) { 218 | if (err) throw err; 219 | 220 | if (result) { 221 | console.log(`[${req.session.user}] comment state set!`) 222 | res.send("OK") 223 | } 224 | }) 225 | } -------------------------------------------------------------------------------- /controllers/subreddit_controller.js: -------------------------------------------------------------------------------- 1 | let Subreddit = require("../models/subreddit"); 2 | let Post = require("../models/post"); 3 | let Comment = require("../models/comment"); 4 | let Profile = require("../models/profile"); 5 | 6 | exports.get_all = function (req, res) { 7 | let subreddit = undefined; 8 | let posts = undefined; 9 | let subscribed = false; 10 | let karma = 0 11 | 12 | let sort = undefined; 13 | 14 | switch (req.query.sort) { 15 | case "top": 16 | sort = { 17 | votes: -1 18 | } 19 | break; 20 | case "new": 21 | sort = { 22 | time: -1 23 | } 24 | break; 25 | case "old": 26 | sort = { 27 | time: 1 28 | } 29 | break; 30 | default: 31 | sort = { 32 | votes: -1 33 | } 34 | } 35 | 36 | Profile.find({ 37 | username: req.session.user 38 | }, function (err, result) { 39 | if (err) throw err; 40 | 41 | if (result.length) { 42 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 43 | } 44 | }); 45 | 46 | Subreddit.find({ 47 | name: req.params.subreddit 48 | }, function (err, doc) { 49 | if (err) throw err; 50 | 51 | if (doc.length) { 52 | subreddit = doc[0] 53 | } else { 54 | res.render("./error") 55 | } 56 | }).then(function () { 57 | Profile.find({ 58 | username: req.session.user, 59 | subscribed: req.params.subreddit, 60 | }, function (err, doc) { 61 | if (err) throw err; 62 | 63 | if (!doc.length) { 64 | // res.send("Unable to find subreddit state") 65 | return; 66 | } else { 67 | subscribed = true 68 | } 69 | }).then(function () { 70 | Post.find({ 71 | subreddit: req.params.subreddit 72 | }).sort(sort).exec(function (err, result) { 73 | if (err) throw err; 74 | if (result.length) { 75 | posts = result 76 | } 77 | 78 | console.log(`[${req.params.subreddit}] fetching posts!`) 79 | res.render("./subreddit/subreddit", { 80 | info: subreddit, 81 | posts: posts, 82 | karma: karma, 83 | state: subscribed, 84 | isAuth: req.isAuthenticated() 85 | }) 86 | }); 87 | }); 88 | }); 89 | } 90 | 91 | exports.get_post = function (req, res) { 92 | let info = undefined 93 | let post = undefined 94 | let comments = undefined 95 | let subscribed = false; 96 | let karma = 0 97 | 98 | let sort = undefined; 99 | 100 | switch (req.query.sort) { 101 | case "top": 102 | sort = { 103 | votes: -1 104 | } 105 | break; 106 | case "new": 107 | sort = { 108 | time: -1 109 | } 110 | break; 111 | case "old": 112 | sort = { 113 | time: 1 114 | } 115 | break; 116 | default: 117 | sort = { 118 | votes: -1 119 | } 120 | } 121 | 122 | Profile.find({ 123 | username: req.session.user 124 | }, function (err, result) { 125 | if (err) throw err; 126 | 127 | if (result.length) { 128 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 129 | } 130 | }); 131 | 132 | Subreddit.find({ 133 | name: req.params.subreddit 134 | }, function (err, doc) { 135 | if (err) throw err 136 | 137 | if (doc.length) { 138 | info = doc[0] 139 | } 140 | }).then(function () { 141 | Profile.find({ 142 | username: req.session.user, 143 | subscribed: req.params.subreddit, 144 | }, function (err, doc) { 145 | if (err) throw err; 146 | 147 | if (!doc.length) { 148 | // res.send("Unable to find subreddit state") 149 | return; 150 | } else { 151 | subscribed = true 152 | } 153 | }).then(function () { 154 | Post.find({ 155 | _id: req.params.id 156 | }, function (err, doc) { 157 | if (err) { 158 | res.render('./error') 159 | } else { 160 | if (doc.length) { 161 | post = doc[0] 162 | } 163 | } 164 | }).then(function () { 165 | 166 | Comment.find({ 167 | ref: req.params.id 168 | }).sort(sort).exec(function (err, result) { 169 | if (err) throw err; 170 | if (result.length) { 171 | comments = result 172 | } 173 | 174 | res.render('./post', { 175 | info: info, 176 | post: post, 177 | karma: karma, 178 | comments: comments, 179 | state: subscribed, 180 | isAuth: req.isAuthenticated() 181 | }) 182 | }) 183 | }) 184 | }) 185 | }) 186 | } 187 | 188 | // CHECKING SUBREDDIT 189 | exports.check_subreddit = function (req, res) { 190 | Subreddit.find({ 191 | name: req.params.subreddit 192 | }, function (err, doc) { 193 | if (err) throw err; 194 | 195 | if (!doc.length) { 196 | res.send(false); 197 | return; 198 | } 199 | console.log(`[${req.params.subreddit}] checked!`) 200 | res.send(true); 201 | }); 202 | } 203 | 204 | // SUBSCRIBING TO SUBREDDIT 205 | exports.subscribe = function (req, res) { 206 | Profile.update({ 207 | username: req.session.user 208 | }, { 209 | $push: { 210 | subscribed: req.params.subreddit 211 | } 212 | }, function (err, doc) { 213 | if (err) throw err; 214 | 215 | console.log(`[${req.params.subreddit}] subscription added!`) 216 | res.send('success!') 217 | }) 218 | } 219 | 220 | // UNSUBSCRIBE FROM SUBREDDIT 221 | exports.unsubscribe = function (req, res) { 222 | Profile.update({ 223 | username: req.session.user 224 | }, { 225 | $pull: { 226 | subscribed: req.params.subreddit 227 | } 228 | }, function (err, doc) { 229 | if (err) throw err; 230 | 231 | console.log(`[${req.params.subeddit}] subscription removed!`) 232 | res.send('success!') 233 | }) 234 | } -------------------------------------------------------------------------------- /views/post.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('partials/head', {title: post.title}) %> 4 | 5 | 6 | <%- include('partials/nav', {user: locals.user}) %> 7 | <%- include('partials/jumbotron', {name: info.name}) %> 8 | <%= isAuth %> 9 | 10 |
11 | 20 | 21 |
22 |
23 |
24 |
25 |
26 | <%= post.votes %> 27 |
28 |
29 | 30 |
31 |
32 | <% if(post.type == "post") { %> 33 | <%= post.title %> 34 | <% } else { %> 35 | <%= post.title %> 36 | <% } %> 37 |
38 | 39 |
40 | submitted 41 | by <%= post.username %> 42 | from /r/<%= post.subreddit %> 43 |
44 | 45 |

<% if(post.type == "post") { %><%= post.body %><% } else { %><%= post.link %><% } %>

46 |
    47 | <% if(locals.isauth) { %> 48 |
  • save
  • 49 | 50 | <% if(post.username == locals.user) { %> 51 |
  • edit
  • 52 |
  • delete
  • 53 | <% } %> 54 | <% } %> 55 |
56 | 57 |
58 |
59 |
60 |
61 | <% if(!isAuth) { %> 62 | You must be logged in to leave a comment 63 | <% } else { %> 64 |
65 |
67 | 68 |
69 | <% }; %> 70 |
71 |
72 | 73 |
74 |

Comments

76 | 81 |

82 | <% if (comments != undefined) { 83 | comments.forEach((comment) => { %> 84 | 85 |
86 |
87 | 92 |
<%= comment.body %>
93 |
    94 |
  • 95 | 96 | 97 |
  • 98 |
  • reply
  • 99 | 100 | <% if(locals.isauth) { %> 101 |
  • save
  • 102 | 103 | <% if(comment.username == locals.user) { %> 104 |
  • edit
  • 105 |
  • delete
  • 106 | <% } %> 107 | <% } %> 108 |
109 |
110 |
111 | <% });%> 112 | <% } else { %> 113 | There are no comments. 114 | <% } %> 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /public/js/post.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | autosize($('.comment-text')) 3 | 4 | $(".edit-post").click(function () { 5 | let query = $(this).closest('article') 6 | let ref = query.data('ref') 7 | 8 | let body = query.find('.post-body').text() 9 | let options = query.find('.post-options') 10 | query.find('.post-body').html(``) 11 | query.find('.post-body').append("
"); 12 | autosize(query.find('.post-text')) 13 | 14 | options.hide(); 15 | 16 | $("button.edit_cancel").click(function () { 17 | let text = body; 18 | 19 | query.find(".post-text").remove(); 20 | query.find(".post-body").text(text) 21 | 22 | options.show(); 23 | }) 24 | 25 | $("button.edit_submit").click(function () { 26 | let new_text = query.find('.post-text').val(); 27 | 28 | $.ajax({ 29 | type: "put", 30 | url: `/edit/post/${ref}`, 31 | data: { 32 | text: new_text 33 | } 34 | }).done(function (res) { 35 | query.find(".post-text").remove(); 36 | query.find(".post-body").text(new_text) 37 | options.show(); 38 | }) 39 | }) 40 | return false; 41 | }) 42 | 43 | $(".delete-post").click(function () { 44 | let query = $(this).closest('article') 45 | let ref = query.data('ref') 46 | 47 | if (confirm("Are you sure you want to delete?")) { 48 | $.ajax({ 49 | type: "delete", 50 | url: `/delete/post/${ref}`, 51 | }).done(function (res) { 52 | query.remove(); 53 | }) 54 | } 55 | return false; 56 | }) 57 | 58 | $(".save-post").click(function () { 59 | let query = $(this).closest('article') 60 | let ref = query.data('ref') 61 | let that = $(this) 62 | 63 | if ($(this).text() == "save") { 64 | $.ajax({ 65 | type: "put", 66 | url: `/save/post/${ref}` 67 | }).done(function (res) { 68 | if (res == "success") { 69 | that.text('unsave'); 70 | return false; 71 | } 72 | }) 73 | } else if ($(this).text() == "unsave") { 74 | $.ajax({ 75 | type: "put", 76 | url: `/unsave/post/${ref}`, 77 | }).done(function (res) { 78 | if (res == "success") { 79 | that.text('save'); 80 | return false; 81 | } 82 | }) 83 | } 84 | return false; 85 | }) 86 | 87 | $(".upvote-post").click(function () { 88 | let query = $(this).closest('article') 89 | let ref = query.data('ref') 90 | 91 | let votes = query.find('.post-votes') 92 | let down_arrow = query.find(".downvote-post") 93 | let post_user = query.find('.post-user').text() 94 | let counter; 95 | 96 | // if upvote is already toggled and user presses it again, 97 | // toggle off the upvote button and decrement vote. 98 | if ($(this).hasClass("up-enabled")) { 99 | counter = votes.text(); 100 | votes.text(--counter); 101 | $(this).removeClass("up-enabled"); 102 | 103 | $.ajax({ 104 | type: "put", 105 | url: `/vote/post/${ref}`, 106 | data: { 107 | vote: counter, 108 | state: "neutral", 109 | action: "decrement", 110 | user: post_user 111 | }, 112 | success: function (res) {} 113 | }); 114 | return false; 115 | } 116 | 117 | // if downvote is already toggled while upvote is pressed 118 | // toggle off downvote and increment vote 119 | if (down_arrow.hasClass('down-enabled')) { 120 | down_arrow.removeClass("down-enabled"); 121 | counter = votes.text(); 122 | votes.text(++counter); 123 | 124 | $.ajax({ 125 | type: "put", 126 | data: { 127 | vote: counter, 128 | state: "neutral", 129 | action: "increment", 130 | user: post_user 131 | }, 132 | url: `/vote/post/${ref}`, 133 | success: function (res) {} 134 | }); 135 | } 136 | 137 | // if upvote isnt toggled while upvote is pressed, 138 | // toggle upvote and increment vote. 139 | else if (!$(this).hasClass("up-enabled")) { 140 | counter = votes.text(); 141 | votes.text(++counter); 142 | $(this).addClass("up-enabled"); 143 | 144 | $.ajax({ 145 | type: "put", 146 | data: { 147 | vote: counter, 148 | state: "up", 149 | action: "increment", 150 | user: post_user 151 | }, 152 | url: `/vote/post/${ref}`, 153 | success: function (res) {} 154 | }); 155 | } 156 | return false; 157 | }); 158 | 159 | $(".downvote-post").click(function () { 160 | let query = $(this).closest('article') 161 | let ref = query.data('ref') 162 | 163 | let votes = query.find('.post-votes') 164 | let up_arrow = query.find(".upvote-post") 165 | let post_user = query.find('.post-user').text() 166 | let counter; 167 | 168 | // if downvote is already toggled and user presses it again, 169 | // toggle off the downvote button and increment vote. 170 | if ($(this).hasClass("down-enabled")) { 171 | counter = votes.text(); 172 | votes.text(++counter); 173 | $(this).removeClass("down-enabled"); 174 | 175 | $.ajax({ 176 | type: "put", 177 | data: { 178 | vote: counter, 179 | state: "neutral", 180 | action: "increment", 181 | user: post_user 182 | }, 183 | url: `/vote/post/${ref}`, 184 | success: function (res) {} 185 | }); 186 | return false; 187 | } 188 | 189 | // if upvote is already toggled while downvote is pressed 190 | // toggle off upvote and decrement vote 191 | if (up_arrow.hasClass('up-enabled')) { 192 | up_arrow.removeClass("up-enabled"); 193 | counter = votes.text(); 194 | votes.text(--counter); 195 | 196 | $.ajax({ 197 | type: "put", 198 | data: { 199 | vote: counter, 200 | state: "neutral", 201 | action: "decrement", 202 | user: post_user 203 | }, 204 | url: `/vote/post/${ref}`, 205 | success: function (res) {} 206 | }); 207 | 208 | // if downvote isnt toggled while downvote is pressed, 209 | // toggle downvote and decrement vote. 210 | } else if (!$(this).hasClass("down-enabled")) { 211 | counter = votes.text(); 212 | votes.text(--counter); 213 | $(this).addClass("down-enabled"); 214 | 215 | $.ajax({ 216 | type: "put", 217 | data: { 218 | vote: counter, 219 | state: "down", 220 | action: "decrement", 221 | user: post_user 222 | }, 223 | url: `/vote/post/${ref}`, 224 | success: function (res) {} 225 | }); 226 | } 227 | return false; 228 | }); 229 | }); -------------------------------------------------------------------------------- /public/js/comment.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function () { 2 | 3 | // event handler for editing a comment 4 | $(".edit-comment").click(function () { 5 | let query = $(this).closest('article') 6 | let ref = query.data('ref') 7 | let body = query.find(".comment-body").text() 8 | let options = query.find(".comment-options") 9 | 10 | // display a text area with the comment body and hide comment options 11 | query.find(".comment-body").html(``) 12 | query.find(".comment-body").append(`
`); 13 | autosize(query.find('.comment-text')) 14 | 15 | options.hide(); 16 | 17 | 18 | // when user clicks on cancel, remove textarea and show comment options 19 | $(`button.edit-comment-cancel[data-ref="${ref}"]`).click(function () { 20 | let text = body; 21 | 22 | query.find(".comment-text").remove(); 23 | query.find(".comment-body").text(text) 24 | 25 | options.show(); 26 | }) 27 | 28 | 29 | // when user clicks on submit, upate comment in database, remove textarea and show comment options 30 | $(`button.edit-comment-submit[data-ref="${ref}"]`).click(function () { 31 | let new_text = query.find('.comment-text').val(); 32 | 33 | $.ajax({ 34 | type: "put", 35 | url: `/edit/comment/${ref}`, 36 | data: { 37 | text: new_text 38 | } 39 | }).done(function (res) { 40 | query.find(".comment-text").remove(); 41 | query.find(".comment-body").text(new_text) 42 | options.show(); 43 | }) 44 | }) 45 | return false; 46 | }) 47 | 48 | 49 | // event handler for deleting a comment 50 | $(".delete-comment").click(function () { 51 | let query = $(this).closest('article') 52 | let ref = query.data('ref') 53 | 54 | // only when user clicks on okay, remove comment from database,then remove from page 55 | if (confirm("Are you sure you want to delete?")) { 56 | $.ajax({ 57 | type: "delete", 58 | url: `/delete/comment/${ref}`, 59 | }).done(function (res) { 60 | query.remove(); 61 | }) 62 | } 63 | return false; 64 | }) 65 | 66 | 67 | // event handler for saving a comment 68 | $(".save-comment").click(function () { 69 | let query = $(this).closest('article') 70 | let ref = query.data('ref') 71 | let that = $(this) 72 | 73 | if ($(this).text() == "save") { 74 | alert('saved') 75 | $.ajax({ 76 | type: "put", 77 | url: `/save/comment/${ref}` 78 | }).done(function (res) { 79 | if (res == "success") { 80 | that.text('unsave'); 81 | return false; 82 | } 83 | }) 84 | } else if ($(this).text() == "unsave") { 85 | alert('unsaved') 86 | $.ajax({ 87 | type: "put", 88 | url: `/unsave/comment/${ref}`, 89 | }).done(function (res) { 90 | if (res == "success") { 91 | that.text('save'); 92 | return false; 93 | } 94 | }) 95 | } 96 | return false; 97 | }) 98 | 99 | // event handler for upvoting a comment 100 | $(".upvote-comment").click(function () { 101 | let down_arrow = $(this).parent().find(".downvote-comment") 102 | let query = $(this).closest('article') 103 | 104 | let ref = query.data('ref') 105 | let votes = query.find('.comment-votes') 106 | let comment_user = query.find('.comment-user').text() 107 | let counter; 108 | 109 | // if upvote is already toggled and user presses it again, 110 | // toggle off the upvote button and decrement vote. 111 | if ($(this).hasClass("up-enabled")) { 112 | counter = votes.text(); 113 | votes.text(--counter); 114 | $(this).removeClass("up-enabled"); 115 | 116 | $.ajax({ 117 | type: "put", 118 | url: `/vote/comment/${ref}`, 119 | data: { 120 | vote: counter, 121 | state: "neutral", 122 | action: "decrement", 123 | user: comment_user 124 | } 125 | }); 126 | return false; 127 | } 128 | 129 | // if downvote is already toggled while upvote is pressed 130 | // toggle off downvote and increment vote 131 | if (down_arrow.hasClass('down-enabled')) { 132 | down_arrow.removeClass("down-enabled"); 133 | counter = votes.text(); 134 | votes.text(++counter); 135 | 136 | $.ajax({ 137 | type: "put", 138 | url: `/vote/comment/${ref}`, 139 | data: { 140 | vote: counter, 141 | state: "neutral", 142 | action: "increment", 143 | user: comment_user 144 | } 145 | }); 146 | } 147 | 148 | // if upvote isnt toggled while upvote is pressed, 149 | // toggle upvote and increment vote. 150 | else if (!$(this).hasClass("up-enabled")) { 151 | counter = votes.text(); 152 | votes.text(++counter); 153 | $(this).addClass("up-enabled"); 154 | 155 | $.ajax({ 156 | type: "put", 157 | url: `/vote/comment/${ref}`, 158 | data: { 159 | vote: counter, 160 | state: "up", 161 | action: "increment", 162 | user: comment_user 163 | } 164 | }); 165 | } 166 | return false; 167 | }) 168 | 169 | // event handler for downvoting a comment 170 | $(".downvote-comment").click(function () { 171 | let up_arrow = $(this).parent().find(".upvote-comment") 172 | let query = $(this).closest('article') 173 | 174 | let ref = query.data('ref') 175 | let votes = query.find('.comment-votes') 176 | let comment_user = query.find('.comment-user').text() 177 | let counter; 178 | 179 | // if downvote is already toggled and user presses it again, 180 | // toggle off the downvote button and increment vote. 181 | if ($(this).hasClass("down-enabled")) { 182 | counter = votes.text(); 183 | votes.text(++counter); 184 | $(this).removeClass("down-enabled"); 185 | 186 | $.ajax({ 187 | type: "put", 188 | url: `/vote/comment/${ref}`, 189 | data: { 190 | vote: counter, 191 | state: "neutral", 192 | action: "increment", 193 | user: comment_user 194 | } 195 | }); 196 | return false; 197 | } 198 | 199 | // if upvote is already toggled while downvote is pressed 200 | // toggle off upvote and decrement vote 201 | if (up_arrow.hasClass('up-enabled')) { 202 | up_arrow.removeClass("up-enabled"); 203 | counter = votes.text(); 204 | votes.text(--counter); 205 | 206 | $.ajax({ 207 | type: "put", 208 | url: `/vote/comment/${ref}`, 209 | data: { 210 | vote: counter, 211 | state: "neutral", 212 | action: "decrement", 213 | user: comment_user 214 | } 215 | }); 216 | 217 | // if downvote isnt toggled while downvote is pressed, 218 | // toggle downvote and decrement vote. 219 | } else if (!$(this).hasClass("down-enabled")) { 220 | counter = votes.text(); 221 | votes.text(--counter); 222 | $(this).addClass("down-enabled"); 223 | 224 | $.ajax({ 225 | type: "put", 226 | url: `/vote/comment/${ref}`, 227 | data: { 228 | vote: counter, 229 | state: "down", 230 | action: "decrement", 231 | user: comment_user 232 | } 233 | }); 234 | } 235 | return false; 236 | }); 237 | }); -------------------------------------------------------------------------------- /controllers/profile_controller.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | mongoose.Promise = global.Promise; 3 | 4 | let Post = require("../models/post"); 5 | let Comment = require("../models/comment"); 6 | let Profile = require("../models/profile"); 7 | let Account = require("../models/account") 8 | 9 | exports.posts = function (req, res) { 10 | let subscribed = undefined; 11 | let posts = undefined; 12 | let created = undefined; 13 | let karma = 0 14 | 15 | let sort = undefined; 16 | 17 | switch (req.query.sort) { 18 | case "top": 19 | sort = { 20 | votes: -1 21 | } 22 | break; 23 | case "new": 24 | sort = { 25 | time: -1 26 | } 27 | break; 28 | case "old": 29 | sort = { 30 | time: 1 31 | } 32 | break; 33 | default: 34 | sort = { 35 | votes: -1 36 | } 37 | } 38 | 39 | Profile.find({ 40 | username: req.params.user 41 | }, function (err, result) { 42 | if (err) throw err; 43 | 44 | if (result.length) { 45 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 46 | } 47 | }) 48 | 49 | Account.find({ 50 | username: req.params.user 51 | }, function (err, result) { 52 | if (err) throw err; 53 | 54 | if (result.length) { 55 | var d = new Date(result[0]['created']) 56 | created = d.toLocaleDateString().replace(/\//g, '-') 57 | } else { 58 | res.render("./error") 59 | } 60 | }).then(function () { 61 | Profile.find({ 62 | username: req.params.user 63 | }, function (err, result) { 64 | if (err) throw err; 65 | 66 | if (result.length) { 67 | subscribed = result[0]['subscribed']; 68 | } 69 | }).then(function () { 70 | Post.find({ 71 | username: req.params.user 72 | }) 73 | .sort(sort).exec(function (err, result) { 74 | if (err) throw err; 75 | 76 | if (result.length) { 77 | posts = result 78 | } 79 | console.log(`[Profile] fetching posts from ${req.params.user} !`) 80 | res.render("./profile/profile_posts", { 81 | profile_user: req.params.user, 82 | posts: posts, 83 | karma: karma, 84 | subscribed: subscribed, 85 | created: created, 86 | isAuth: req.isAuthenticated() 87 | }) 88 | }) 89 | }) 90 | }) 91 | } 92 | 93 | exports.comments = function (req, res) { 94 | let subscribed = undefined; 95 | let comments = undefined; 96 | let created = undefined; 97 | let karma = 0 98 | 99 | let sort = undefined; 100 | 101 | switch (req.query.sort) { 102 | case "top": 103 | sort = { 104 | votes: -1 105 | } 106 | break; 107 | case "new": 108 | sort = { 109 | time: -1 110 | } 111 | break; 112 | case "old": 113 | sort = { 114 | time: 1 115 | } 116 | break; 117 | default: 118 | sort = { 119 | votes: -1 120 | } 121 | } 122 | 123 | Profile.find({ 124 | username: req.params.user 125 | }, function (err, result) { 126 | if (err) throw err; 127 | 128 | if (result.length) { 129 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 130 | } 131 | }) 132 | 133 | Account.find({ 134 | username: req.params.user 135 | }, function (err, result) { 136 | if (err) throw err; 137 | 138 | if (result.length) { 139 | var d = new Date(result[0]['created']) 140 | created = d.toLocaleDateString().replace(/\//g, '-') 141 | } 142 | }).then(function () { 143 | Profile.find({ 144 | username: req.params.user 145 | }, function (err, result) { 146 | if (err) throw err; 147 | 148 | if (result.length) { 149 | subscribed = result[0]['subscribed']; 150 | } 151 | }).then(function () { 152 | Comment.aggregate([{ 153 | $match: { 154 | username: req.params.user 155 | } 156 | }, 157 | { 158 | $sort: sort 159 | }, 160 | { 161 | $lookup: { 162 | from: "posts", 163 | localField: "ref", // field in the orders collection 164 | foreignField: "_id", // field in the items collection 165 | as: "parent" 166 | } 167 | } 168 | ]).exec(function (err, result) { 169 | if (err) throw err; 170 | 171 | if (result.length) { 172 | comments = result 173 | } 174 | console.log(`[Profile] fetching comments from ${req.params.user} !`) 175 | res.render("./profile/profile_comments", { 176 | profile_user: req.params.user, 177 | comments: comments, 178 | karma: karma, 179 | created: created, 180 | subscribed: subscribed, 181 | isAuth: req.isAuthenticated() 182 | }) 183 | }); 184 | }); 185 | }); 186 | } 187 | 188 | exports.saved_posts = function (req, res) { 189 | let created = undefined 190 | let subscribed = undefined 191 | let karma = 0 192 | 193 | let sort = undefined; 194 | 195 | switch (req.query.sort) { 196 | case "top": 197 | sort = { 198 | votes: -1 199 | } 200 | break; 201 | case "new": 202 | sort = { 203 | time: -1 204 | } 205 | break; 206 | case "old": 207 | sort = { 208 | time: 1 209 | } 210 | break; 211 | default: 212 | sort = { 213 | votes: -1 214 | } 215 | } 216 | 217 | Profile.find({ 218 | username: req.params.user 219 | }, function (err, result) { 220 | if (err) throw err; 221 | 222 | if (result.length) { 223 | subscribed = result[0]['subscribed'] 224 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 225 | } 226 | }) 227 | 228 | Account.find({ 229 | username: req.params.user 230 | }).exec().then((result) => { 231 | created = new Date(result[0]['created']).toLocaleDateString().replace(/\//g, '-') 232 | 233 | return Profile.find({ 234 | username: req.params.user 235 | }) 236 | }).then((result) => { 237 | console.log(result) 238 | return Post.find({ 239 | _id: { 240 | $in: result[0].saved_posts 241 | } 242 | }).sort(sort) 243 | }).then((result) => { 244 | res.render("./profile/profile_saved_posts", { 245 | profile_user: req.params.user, 246 | posts: result, 247 | karma: karma, 248 | created: created, 249 | subscribed: subscribed, 250 | isAuth: req.isAuthenticated() 251 | }) 252 | }).catch((err) => { 253 | console.log(err) 254 | }) 255 | } 256 | 257 | exports.saved_comments = function (req, res) { 258 | let created = undefined 259 | let subscribed = undefined 260 | let karma = 0 261 | 262 | let sort = undefined; 263 | 264 | switch (req.query.sort) { 265 | case "top": 266 | sort = { 267 | votes: -1 268 | } 269 | break; 270 | case "new": 271 | sort = { 272 | time: -1 273 | } 274 | break; 275 | case "old": 276 | sort = { 277 | time: 1 278 | } 279 | break; 280 | default: 281 | sort = { 282 | votes: -1 283 | } 284 | } 285 | 286 | Profile.find({ 287 | username: req.params.user 288 | }, function (err, result) { 289 | if (err) throw err; 290 | 291 | if (result.length) { 292 | subscribed = result[0]['subscribed'] 293 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 294 | } 295 | }) 296 | 297 | Account.find({ 298 | username: req.params.user 299 | }).exec().then((result) => { 300 | created = new Date(result[0]['created']).toLocaleDateString().replace(/\//g, '-') 301 | 302 | return Profile.find({ 303 | username: req.params.user 304 | }) 305 | }).then((result) => { 306 | let casted_saved_comments = result[0].saved_comments.map(function (el) { 307 | return mongoose.Types.ObjectId(el) 308 | }) 309 | return Comment.aggregate([{ 310 | $match: { 311 | _id: { 312 | $in: casted_saved_comments 313 | } 314 | } 315 | }, 316 | { 317 | $sort: sort 318 | }, 319 | { 320 | $lookup: { 321 | from: "posts", 322 | localField: "ref", // field in the orders collection 323 | foreignField: "_id", // field in the items collection 324 | as: "parent" 325 | } 326 | } 327 | ]) 328 | }).then((result) => { 329 | res.render("./profile/profile_saved_comments", { 330 | profile_user: req.params.user, 331 | comments: result, 332 | karma: karma, 333 | created: created, 334 | subscribed: subscribed, 335 | isAuth: req.isAuthenticated() 336 | }) 337 | }).catch((err) => { 338 | console.log(err) 339 | }) 340 | 341 | } -------------------------------------------------------------------------------- /controllers/submit_controller.js: -------------------------------------------------------------------------------- 1 | let Subreddit = require("../models/subreddit"); 2 | let Post = require("../models/post"); 3 | let Profile = require("../models/profile"); 4 | 5 | exports.subreddit_post_view = function (req, res) { 6 | let subscribed = false 7 | let karma = 0 8 | 9 | Profile.find({ 10 | username: req.session.user 11 | }, function (err, result) { 12 | if (err) throw err; 13 | 14 | if (result.length) { 15 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 16 | } 17 | }); 18 | 19 | Profile.find({ 20 | username: req.session.user, 21 | subscribed: req.params.subreddit, 22 | }, function (err, doc) { 23 | if (err) throw err; 24 | 25 | if (!doc.length) { 26 | // res.send("Unable to find subreddit state") 27 | return; 28 | } else { 29 | subscribed = true 30 | } 31 | }).then(function () { 32 | Subreddit.find({ 33 | name: req.params.subreddit 34 | }, function (err, doc) { 35 | if (err) throw err 36 | 37 | if (doc.length) { 38 | res.render('./subreddit/subreddit_post', { 39 | info: doc[0], 40 | karma: karma, 41 | state: subscribed, 42 | isAuth: req.isAuthenticated(), 43 | }) 44 | } 45 | }) 46 | }) 47 | } 48 | exports.subreddit_post = function (req, res) { 49 | Post({ 50 | title: req.body.title, 51 | body: req.body.body, 52 | username: req.session.user, 53 | type: "post", 54 | subreddit: req.params.subreddit, 55 | }).save(function (err, doc) { 56 | if (err) throw err; 57 | 58 | console.log(`[${req.params.subreddit}] post submitted!`) 59 | res.redirect(`/r/${req.params.subreddit}`) 60 | }) 61 | } 62 | exports.subreddit_link_view = function (req, res) { 63 | let subscribed = false; 64 | let karma = 0 65 | 66 | Profile.find({ 67 | username: req.session.user 68 | }, function (err, result) { 69 | if (err) throw err; 70 | 71 | if (result.length) { 72 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 73 | } 74 | }); 75 | 76 | 77 | Profile.find({ 78 | username: req.session.user, 79 | subscribed: req.params.subreddit, 80 | }, function (err, doc) { 81 | if (err) throw err; 82 | 83 | if (!doc.length) { 84 | // res.send("Unable to find subreddit state") 85 | return; 86 | } else { 87 | subscribed = true 88 | } 89 | }).then(function () { 90 | Subreddit.find({ 91 | name: req.params.subreddit 92 | }, function (err, doc) { 93 | if (err) throw err 94 | 95 | if (doc.length) { 96 | res.render('./subreddit/subreddit_link', { 97 | info: doc[0], 98 | karma: karma, 99 | state: subscribed, 100 | isAuth: req.isAuthenticated(), 101 | }) 102 | } 103 | }) 104 | }) 105 | } 106 | exports.subreddit_link = function (req, res) { 107 | let type = "link" 108 | 109 | function checkURL(url) { 110 | return (url.match(/\.(jpeg|jpg|gif|png)$/) != null); 111 | } 112 | 113 | if (checkURL(req.body.link)) { 114 | type = "img" 115 | } 116 | 117 | Post({ 118 | title: req.body.title, 119 | body: req.body.body, 120 | username: req.session.user, 121 | type: type, 122 | link: req.body.link, 123 | subreddit: req.params.subreddit, 124 | }).save(function (err, doc) { 125 | if (err) throw error; 126 | 127 | console.log(`[${req.params.subreddit}] link submitted!`) 128 | res.redirect(`/r/${req.params.subreddit}`) 129 | }) 130 | } 131 | 132 | exports.subreddit_search = function (req, res) { 133 | let subreddit = undefined 134 | let posts = undefined 135 | let subscribed = false 136 | let karma = 0 137 | 138 | Profile.find({ 139 | username: req.session.user 140 | }, function (err, result) { 141 | if (err) throw err; 142 | 143 | if (result.length) { 144 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 145 | } 146 | }); 147 | 148 | Subreddit.find({ 149 | name: req.params.subreddit 150 | }, function (err, doc) { 151 | if (err) throw err 152 | 153 | if (doc.length) { 154 | subreddit = doc[0] 155 | } 156 | }).then(function () { 157 | Profile.find({ 158 | username: req.session.user, 159 | subscribed: req.params.subreddit, 160 | }, function (err, doc) { 161 | if (err) throw err; 162 | 163 | if (!doc.length) { 164 | // res.send("Unable to find subreddit state") 165 | return; 166 | } else { 167 | subscribed = true 168 | } 169 | }).then(function () { 170 | Post.find({ 171 | $and: [{ 172 | subreddit: req.params.subreddit 173 | }, 174 | { 175 | title: { 176 | $regex: '.*' + req.body.query + '.*', 177 | $options: 'i' 178 | } 179 | } 180 | ] 181 | }).sort({ 182 | votes: '-1' 183 | }).exec(function (err, result) { 184 | if (err) throw err; 185 | if (result.length) { 186 | posts = result 187 | } 188 | 189 | console.log(`[${req.params.subreddit}] searching for posts which contain '{${req.body.query}}'`) 190 | res.render("./subreddit/subreddit_search", { 191 | info: subreddit, 192 | posts: result, 193 | karma: karma, 194 | state: subscribed, 195 | query: req.body.query, 196 | isAuth: req.isAuthenticated(), 197 | }) 198 | }) 199 | }) 200 | }) 201 | } 202 | 203 | 204 | // SUBMITING A POST 205 | exports.front_post = function (req, res) { 206 | Post({ 207 | title: req.body.title, 208 | body: req.body.text, 209 | username: req.session.user, 210 | type: "post", 211 | subreddit: req.body.subreddit, 212 | }).save(function (err, doc) { 213 | if (err) throw err; 214 | 215 | console.log(`[Frontpage] post submitted to [${req.body.subreddit}]`) 216 | res.redirect(`/r/${req.body.subreddit}/${doc._id}/comments`); 217 | }); 218 | } 219 | 220 | // SUBMITING A LINK 221 | exports.front_link = function (req, res) { 222 | let type = "link" 223 | 224 | function checkURL(url) { 225 | return (url.match(/\.(jpeg|jpg|gif|png)$/) != null); 226 | } 227 | 228 | if (checkURL(req.body.link)) { 229 | type = "img" 230 | } 231 | 232 | Post({ 233 | title: req.body.title, 234 | link: req.body.link, 235 | username: req.session.user, 236 | type: type, 237 | subreddit: req.body.subreddit, 238 | }).save(function (err, doc) { 239 | if (err) throw err; 240 | 241 | console.log(`[Frontpage] link submitted to [${req.body.subreddit}]`) 242 | res.redirect(`/r/${req.body.subreddit}/${doc._id}/comments`); 243 | }); 244 | } 245 | 246 | 247 | // SUBMITING A SUBREDDIT 248 | exports.subreddit = function (req, res) { 249 | Profile.update({ 250 | username: req.session.user 251 | }, { 252 | $push: { 253 | owned: req.body.subreddit 254 | } 255 | }, 256 | function (err, doc) { 257 | if (err) throw err; 258 | 259 | }).then(function () { 260 | Subreddit({ 261 | name: req.body.subreddit, 262 | description: req.body.description 263 | }).save(function (err, doc) { 264 | if (err) throw err 265 | 266 | console.log(`[Frontpage] ${req.body.subreddit} subreddit created`) 267 | res.redirect(`/r/${req.body.subreddit}`); 268 | }); 269 | }); 270 | } 271 | 272 | // SEARCHING FOR A POST 273 | exports.front_search = function (req, res) { 274 | let subscribed = undefined; 275 | let subreddits = undefined; 276 | let posts = undefined; 277 | let karma = 0; 278 | 279 | Profile.find({ 280 | username: req.session.user 281 | }, function (err, result) { 282 | if (err) throw err; 283 | if (result.length) { 284 | subscribed = result[0]['subscribed']; 285 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 286 | } 287 | }) 288 | .then(function () { 289 | Subreddit.find({}, function (err, doc) { 290 | if (err) throw err; 291 | 292 | if (doc.length) { 293 | subreddits = doc 294 | } 295 | }) 296 | .then(function () { 297 | Post.find({ 298 | title: { 299 | $regex: '.*' + req.body.query + '.*', 300 | $options: 'i' 301 | } 302 | }) 303 | .sort({ 304 | votes: '-1' 305 | }) 306 | .exec(function (err, result) { 307 | if (err) throw err; 308 | if (result.length) { 309 | posts = result 310 | } 311 | 312 | console.log(`[Frontpage] searching for posts which contain '{${req.body.query}}'`) 313 | res.render("./front/front_search", { 314 | posts: result, 315 | subreddits: subreddits, 316 | subscribed: subscribed, 317 | karma: karma, 318 | query: req.body.query, 319 | isAuth: req.isAuthenticated() 320 | }) 321 | }); 322 | }); 323 | }); 324 | } 325 | 326 | exports.front_post_view = function (req, res) { 327 | let subscribed = undefined; 328 | let karma = 0; 329 | 330 | Profile.find({ 331 | username: req.session.user 332 | }, function (err, result) { 333 | if (err) throw err; 334 | 335 | if (result.length) { 336 | subscribed = result[0]['subscribed'] 337 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 338 | 339 | } 340 | 341 | res.render("./front/front_post", { 342 | isAuth: req.isAuthenticated(), 343 | subscribed: subscribed, 344 | karma: karma 345 | }); 346 | }) 347 | } 348 | 349 | exports.front_post_view = function (req, res) { 350 | let subscribed = undefined; 351 | let karma = 0; 352 | 353 | Profile.find({ 354 | username: req.session.user 355 | }, function (err, result) { 356 | if (err) throw err; 357 | 358 | if (result.length) { 359 | subscribed = result[0]['subscribed'] 360 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 361 | 362 | } 363 | 364 | res.render("./front/front_post", { 365 | isAuth: req.isAuthenticated(), 366 | subscribed: subscribed, 367 | karma: karma 368 | }); 369 | }) 370 | } 371 | exports.front_link_view = function (req, res) { 372 | let subscribed = undefined; 373 | let karma = 0; 374 | 375 | Profile.find({ 376 | username: req.session.user 377 | }, function (err, result) { 378 | if (err) throw err; 379 | 380 | if (result.length) { 381 | subscribed = result[0]['subscribed'] 382 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 383 | } 384 | 385 | res.render("./front/front_link", { 386 | isAuth: req.isAuthenticated(), 387 | karma: karma, 388 | subscribed: subscribed 389 | }); 390 | }) 391 | } 392 | exports.subreddit_view = function (req, res) { 393 | let subscribed = undefined; 394 | let karma = 0; 395 | 396 | Profile.find({ 397 | username: req.session.user 398 | }, function (err, result) { 399 | if (err) throw err; 400 | 401 | if (result.length) { 402 | subscribed = result[0]['subscribed'] 403 | karma = result[0]['karma_post'] + result[0]['karma_comment'] 404 | } 405 | 406 | res.render("./front/front_subreddit", { 407 | isAuth: req.isAuthenticated(), 408 | karma: karma, 409 | subscribed: result[0]['subscribed'] 410 | }); 411 | }) 412 | } --------------------------------------------------------------------------------