diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bc162 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..104e6bd --- /dev/null +++ b/app.js @@ -0,0 +1,136 @@ +var express = require("express"); +var app = express(); + +// ---------------------------------------- +// Body Parser +// ---------------------------------------- +var bodyParser = require("body-parser"); +app.use(bodyParser.urlencoded({ extended: true })); + +// ---------------------------------------- +// Sessions/Cookies +// ---------------------------------------- +var cookieSession = require("cookie-session"); + +app.use( + cookieSession({ + name: "session", + keys: ["asdf1234567890qwer"] + }) +); + +app.use((req, res, next) => { + res.locals.session = req.session; + res.locals.currentUser = req.session.currentUser; + next(); +}); + +// ---------------------------------------- +// Method Override +// ---------------------------------------- +app.use((req, res, next) => { + var method; + if (req.query._method) { + method = req.query._method; + delete req.query._method; + for (let key in req.query) { + req.body[key] = decodeURIComponent(req.query[key]); + } + } else if (typeof req.body === "object" && req.body._method) { + method = req.body._method; + delete req.body._method; + } + + if (method) { + method = method.toUpperCase(); + req.method = method; + } + + next(); +}); + +// ---------------------------------------- +// Referrer +// ---------------------------------------- +app.use((req, res, next) => { + req.session.backUrl = req.header("Referer") || "/"; + next(); +}); + +// ---------------------------------------- +// Public +// ---------------------------------------- +app.use(express.static(`${__dirname}/public`)); + +// ---------------------------------------- +// Logging +// ---------------------------------------- +var morgan = require("morgan"); +app.use(morgan("tiny")); +app.use((req, res, next) => { + ["query", "params", "body"].forEach(key => { + if (req[key]) { + var capKey = key[0].toUpperCase() + key.substr(1); + var value = JSON.stringify(req[key], null, 2); + console.log(`${capKey}: ${value}`); + } + }); + next(); +}); + +// ---------------------------------------- +// Mongoose +// ---------------------------------------- +var mongoose = require("mongoose"); +app.use((req, res, next) => { + if (mongoose.connection.readyState) { + next(); + } else { + require("./mongo")(req).then(() => next()); + } +}); + +// ---------------------------------------- +// Routes +// ---------------------------------------- +var sessionsRouter = require("./routers/sessions"); +app.use("/", sessionsRouter); + +var usersRouter = require("./routers/users"); +app.use("/users", usersRouter); + +var postsRouter = require("./routers/posts"); +app.use("/posts", postsRouter); + +// var ratablesRouter = require('./routers/ratables'); +// app.use('/ratables', ratablesRouter); + +// ---------------------------------------- +// Template Engine +// ---------------------------------------- +var expressHandlebars = require("express-handlebars"); + +var hbs = expressHandlebars.create({ + partialsDir: "views/", + defaultLayout: "main" +}); + +app.engine("handlebars", hbs.engine); +app.set("view engine", "handlebars"); + +// ---------------------------------------- +// Server +// ---------------------------------------- +var port = process.env.PORT || process.argv[2] || 3000; +var host = "localhost"; + +var args; +process.env.NODE_ENV === "production" ? (args = [port]) : (args = [port, host]); + +args.push(() => { + console.log(`Listening: http://${host}:${port}`); +}); + +app.listen.apply(app, args); + +module.exports = app; diff --git a/config/mongo.json b/config/mongo.json new file mode 100644 index 0000000..6231649 --- /dev/null +++ b/config/mongo.json @@ -0,0 +1,13 @@ +{ + "development": { + "database": "thoreddit_development", + "host": "localhost" + }, + "test": { + "database": "thoreddit_test", + "host": "localhost" + }, + "production": { + "use_env_variable": "MONGODB_URI" + } +} \ No newline at end of file diff --git a/models/comment.js b/models/comment.js new file mode 100644 index 0000000..b23a314 --- /dev/null +++ b/models/comment.js @@ -0,0 +1,14 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var Commentable = require("./commentable"); + +let CommentSchema = new Schema( + {}, + { + discriminatorKey: "kind" + } +); + +const Comment = Commentable.discriminator("Comment", CommentSchema); + +module.exports = Comment; diff --git a/models/commentable.js b/models/commentable.js new file mode 100644 index 0000000..8fa10d8 --- /dev/null +++ b/models/commentable.js @@ -0,0 +1,27 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; + +let CommentableSchema = new Schema( + { + body: String, + score: Number, + author: { + type: Schema.Types.ObjectId, + ref: "User" + }, + comments: [ + { + type: Schema.Types.ObjectId, + ref: "Comment" + } + ] + }, + { + timestamps: true, + discriminatorKey: "kind" + } +); + +const Commentable = mongoose.model("Commentable", CommentableSchema); + +module.exports = Commentable; diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..09e6204 --- /dev/null +++ b/models/index.js @@ -0,0 +1,18 @@ +var mongoose = require("mongoose"); +var bluebird = require("bluebird"); + +// Set bluebird as the promise +// library for mongoose +mongoose.Promise = bluebird; + +var models = {}; + +// Load models and attach to models here +models.User = require("./user"); +models.Commentable = require("./commentable"); +models.Comment = require("./comment"); +models.Post = require("./post"); + +//... more models + +module.exports = models; diff --git a/models/post.js b/models/post.js new file mode 100644 index 0000000..a93d6e9 --- /dev/null +++ b/models/post.js @@ -0,0 +1,16 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var Commentable = require("./commentable"); + +let PostSchema = new Schema( + { + title: String + }, + { + discriminatorKey: "kind" + } +); + +const Post = Commentable.discriminator("Post", PostSchema); + +module.exports = Post; diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..eee0235 --- /dev/null +++ b/models/user.js @@ -0,0 +1,37 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; + +var UserSchema = new Schema( + { + fname: String, + lname: String, + username: String, + email: String + }, + { + timestamps: true + } +); + +// UserSchema.methods.name = function() { +// return `${ this.fname } ${ this.lname }`; +// }; + +// UserSchema.statics.findByFirstName = function (fname) { +// return User.find({ fname: fname }); +// }; + +// UserSchema.virtual('fullname').set(function(name) { +// console.log('Setting the name of the user'); +// name = name.toString(); +// var splat = name.split(' '); +// var fname = splat[0] || this.fname; +// var lname = splat[1] || this.lname; +// this.fname = fname; +// this.lname = lname; +// return this.name; +// }); + +var User = mongoose.model("User", UserSchema); + +module.exports = User; diff --git a/mongo.js b/mongo.js new file mode 100644 index 0000000..65e7e96 --- /dev/null +++ b/mongo.js @@ -0,0 +1,10 @@ +var mongoose = require("mongoose"); +var env = process.env.NODE_ENV || "development"; +var config = require("./config/mongo")[env]; + +module.exports = () => { + var envUrl = process.env[config.use_env_variable]; + var localUrl = `mongodb://${config.host}/${config.database}`; + var mongoUrl = envUrl ? envUrl : localUrl; + return mongoose.connect(mongoUrl); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..dfa1a39 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "assignment_thorredit", + "version": "1.0.0", + "description": "A social news web application for Viking thunder Gods", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon app.js", + "c": "node repl.js", + "seed": "node seeds", + "seeds": "node seeds" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/eglital/assignment_thorredit.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/eglital/assignment_thorredit/issues" + }, + "homepage": "https://github.com/eglital/assignment_thorredit#readme", + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.1", + "cookie-session": "^2.0.0-beta.1", + "express": "^4.15.2", + "express-handlebars": "^3.0.0", + "faker": "^4.1.0", + "mongoose": "^4.9.5", + "morgan": "^1.8.1" + } +} diff --git a/repl.js b/repl.js new file mode 100644 index 0000000..f3e3c51 --- /dev/null +++ b/repl.js @@ -0,0 +1,13 @@ +var mongoose = require("mongoose"); +var repl = require("repl").start({}); +var models = require("./models"); + +require("./mongo")().then(() => { + repl.context.models = models; + + Object.keys(models).forEach(modelName => { + repl.context[modelName] = mongoose.model(modelName); + }); + + repl.context.lg = data => console.log(data); +}); diff --git a/routers/posts.js b/routers/posts.js new file mode 100644 index 0000000..f1603c6 --- /dev/null +++ b/routers/posts.js @@ -0,0 +1,59 @@ +var express = require("express"); +var router = express.Router(); +var mongoose = require("mongoose"); +var models = require("./../models"); +var User = mongoose.model("User"); +var Post = mongoose.model("Post"); + +// ---------------------------------------- +// Index +// ---------------------------------------- +router.get("/", (req, res) => { + if (req.session.currentUser) { + var posts; + Post.find({}) + .then(postsProm => { + posts = postsProm; + var promiseArr = []; + posts.forEach(post => { + promiseArr.push(User.findById(post.author)); + }); + return Promise.all(promiseArr); + }) + .then(users => { + posts.forEach(post => { + users.forEach(user => { + //console.log("user " + user + "\n" + "post " + post); + if (post.author == user.id) { + console.log("inside if statement ......."); + post.user = user; + } + }); + }); + res.render("posts/index", { posts }); + }) + .catch(e => res.status(500).send(e.stack)); + } else { + res.redirect("/login"); + } +}); + +router.get("/:id", (req, res) => { + Post.findOne({ _id: req.params.id }) + .populate({ + path: "comments author", + populate: { + path: "comments author", + populate: { + path: "author" + } + } + }) + .then(post => { + console.log("inside router.get ", post); + res.render("posts/show", { post }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +module.exports = router; diff --git a/routers/sessions.js b/routers/sessions.js new file mode 100644 index 0000000..790741f --- /dev/null +++ b/routers/sessions.js @@ -0,0 +1,49 @@ +var url = require("url"); +var express = require("express"); +var router = express.Router(); +var mongoose = require("mongoose"); +// var models = require('./../models'); +// var User = mongoose.model('User'); + +//var app = express(); + +router.get("/", (req, res) => { + if (req.session.currentUser) { + // to prevent unauthorized direct access to the 'logged' private page + res.redirect("/users"); + } else { + res.redirect("/login"); + } +}); + +router.get("/login", (req, res) => { + if (req.session.currentUser) { + // to prevent unauthorized direct access to the 'logged' private page + res.redirect("/users"); + } else { + res.render("sessions/login"); + } +}); + +router.post("/login", (req, res) => { + // invoked when '/login' action called on index.hbs page + //req.session.visited = true; + req.session.currentUser = { name: req.body.username, email: req.body.email }; + res.redirect("/"); +}); + +router.post("/logout", (req, res) => { + // invoked when '/logout' action called on logged.hbs page + req.session.destroy(); // to prevent unauthorized direct access to the 'logged' private page + res.redirect("/"); +}); + +// Destroy +var onDestroy = (req, res) => { + req.session.currentUser = null; + res.redirect("/login"); +}; +router.get("/logout", onDestroy); +router.delete("/logout", onDestroy); + +module.exports = router; diff --git a/routers/users.js b/routers/users.js new file mode 100644 index 0000000..d4b2eae --- /dev/null +++ b/routers/users.js @@ -0,0 +1,106 @@ +var express = require("express"); +var router = express.Router(); +var mongoose = require("mongoose"); +var models = require("./../models"); +var User = mongoose.model("User"); + +// ---------------------------------------- +// Index +// ---------------------------------------- +router.get("/", (req, res) => { + if (req.session.currentUser) { + User.find({}) // 'User' is the collection in database, find() is mongoose method + .then(users => { + res.render("users/index", { users }); + }) + .catch(e => res.status(500).send(e.stack)); + } else { + res.redirect("/login"); + } +}); + +// ---------------------------------------- +// New +// ---------------------------------------- +// router.get("/new", (req, res) => { +// res.render("users/new"); +// }); + +// ---------------------------------------- +// Edit +// ---------------------------------------- +// router.get("/:id/edit", (req, res) => { +// User.findById(req.params.id) +// .then(user => { +// res.render("users/edit", { user }); +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +//---------------------------------------- +//Show +//---------------------------------------- +router.get("/:id", (req, res) => { + User.findById(req.params.id) + .then(user => { + res.render("users/show", { user }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// Create +// ---------------------------------------- +// router.post("/", (req, res) => { +// var user = new User({ +// fname: req.body.user.fname, +// lname: req.body.user.lname, +// username: req.body.user.username, +// email: req.body.user.email +// }); +// +// user +// .save() +// .then(user => { +// res.redirect(`/users/${user.id}`); +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +// ---------------------------------------- +// Update +// ---------------------------------------- +// router.put("/:id", (req, res) => { +// var userParams = { +// fname: req.body.user.fname, +// lname: req.body.user.lname, +// username: req.body.user.username, +// email: req.body.user.email +// }; +// +// User.findByIdAndUpdate(req.params.id, userParams) +// .then(user => { +// req.method = "GET"; +// res.redirect(`/users/${user.id}`); +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +// ---------------------------------------- +// Destroy +// ---------------------------------------- +// router.delete("/:id", (req, res) => { +// var currentUser = req.session.currentUser; +// User.findByIdAndRemove(req.params.id) +// .then(() => { +// req.method = "GET"; +// if (currentUser.id === req.params.id) { +// res.redirect("/logout"); +// } else { +// res.redirect("/users"); +// } +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +module.exports = router; diff --git a/seeds/clean.js b/seeds/clean.js new file mode 100644 index 0000000..28565fb --- /dev/null +++ b/seeds/clean.js @@ -0,0 +1,16 @@ +var mongoose = require("mongoose"); + +module.exports = () => { + var collections = mongoose.connection.collections; + + var collectionKeys = Object.keys(collections); + + var promises = []; + + collectionKeys.forEach(key => { + var promise = collections[key].remove(); + promises.push(promise); + }); + + return Promise.all(promises); +}; diff --git a/seeds/index.js b/seeds/index.js new file mode 100644 index 0000000..22f19d4 --- /dev/null +++ b/seeds/index.js @@ -0,0 +1,19 @@ +var mongoose = require("mongoose"); +var models = require("./../models"); + +Object.keys(models).forEach(modelName => { + global[modelName] = mongoose.model(modelName); +}); + +require("./../mongo")() + .then(() => console.log("Cleaning Database...")) + .then(() => { + return require("./clean")(); + }) + .then(() => console.log("Seeding...")) + .then(() => { + return require("./seeds")(); + }) + .then(() => console.log("Done")) + .catch(e => console.error(e)) + .then(() => mongoose.disconnect()); diff --git a/seeds/seeds.js b/seeds/seeds.js new file mode 100644 index 0000000..6ea950f --- /dev/null +++ b/seeds/seeds.js @@ -0,0 +1,139 @@ +var faker = require("faker"); +// var voca = require('voca'); + +// const MULTIPLIER = 1; + +function randomRating() { + return Math.floor(Math.random() * 6); +} + +// function randomLodgingName(type) { +// type = voca.titleCase(type); +// var randomWord = faker.random.word(); +// randomWord = voca.titleCase(randomWord); +// var names = [ +// `The ${ randomWord } Inn`, +// `${ type } ${ randomWord }`, +// `${ randomWord } ${ type }` +// ]; +// var index = Math.floor(Math.random() * names.length); +// return names[index]; +// } + +function randomText() { + return faker.lorem.sentence(); +} +function randomWord() { + return faker.random.word(); +} + +module.exports = () => { + // ---------------------------------------- + // Create Users + // ---------------------------------------- + console.log("Creating Users"); + var users = []; + for (let i = 0; i < 6; i++) { + var user = new User({ + fname: "Foo", + lname: "Bar", + username: `foobar${i}`, + email: `foobar${i}@gmail.com` + }); + users.push(user); + } + + function randomUserIndex() { + return Math.floor(Math.random() * users.length); + } + // function randomCommentIndex() { + // return Math.floor(Math.random() * comments.length) + 2; + // } + + // -------------------------------------------- + // Comments + // -------------------------------------------- + console.log("Creating Comments"); + var comments = []; + for (let i = 0; i < 100; i++) { + var comment = new Comment({ + body: randomText(), + score: randomRating(), + author: users[randomUserIndex()], + comments: i === 0 || i === 1 ? [] : [comments[0], comments[1]] + }); + comments.push(comment); + } + + // ---------------------------------------- + // Posts + // ---------------------------------------- + console.log("Creating Posts"); + var posts = []; + for (let i = 0; i < 20; i++) { + var post = new Post({ + title: randomWord(), + body: randomText(), + score: randomRating(), + author: users[randomUserIndex()], + comments: [comments[2], comments[3]] + }); + posts.push(post); + } + + // // ---------------------------------------- + // // Motels + // // ---------------------------------------- + // console.log('Creating Motels'); + // var motels = []; + // for (let i = 0; i < MULTIPLIER * 100; i++) { + // var motel = new Motel({ + // name: randomLodgingName('motel') + // }); + // motels.push(motel); + // } + + // // ---------------------------------------- + // // Ratings + // // ---------------------------------------- + // console.log('Creating Ratings'); + // var ratings = []; + // for (let i = 0; i < MULTIPLIER * 1000; i++) { + // var hotel = hotels[i % hotels.length]; + // var motel = motels[i % motels.length]; + // var user = users[1]; + // var hotelRating = new Rating({ + // ratable: hotel, + // user: user, + // value: randomRating() + // }); + // var motelRating = new Rating({ + // ratable: motel, + // user: user, + // value: randomRating() + // }); + // hotel.ratings.push(hotelRating); + // motel.ratings.push(motelRating); + // ratings.push(hotelRating); + // ratings.push(motelRating); + // } + + // ---------------------------------------- + // Finish + // ---------------------------------------- + console.log("Saving..."); + var promises = []; + [ + users, + posts, + comments + // hotels, + // motels, + // ratings + ].forEach(collection => { + collection.forEach(model => { + promises.push(model.save()); + }); + }); + return Promise.all(promises); +}; diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars new file mode 100644 index 0000000..e559eb5 --- /dev/null +++ b/views/layouts/main.handlebars @@ -0,0 +1,39 @@ + + + + + + + + Thoreddit + {{#if title }} + | {{ title }} + {{/if }} + + + + + + + + + + + + + + + + + + + {{> shared/_nav }} +
+ {{{ body }}} +
+ + + + diff --git a/views/posts/index.handlebars b/views/posts/index.handlebars new file mode 100644 index 0000000..4e59b2f --- /dev/null +++ b/views/posts/index.handlebars @@ -0,0 +1,40 @@ + + + + + +{{#if posts.length }} + + + + + + + + + + + {{#each posts as |post| }} + + + + + + + {{/each }} + +
Posted AtTitleBodyUsername
+ {{ post.createdAt }} + + {{ post.title }} + + {{ post.body}} + + {{ post.user.username }} +
+{{else }} +

No users

+{{/if }} diff --git a/views/posts/show.handlebars b/views/posts/show.handlebars new file mode 100644 index 0000000..aaa15ef --- /dev/null +++ b/views/posts/show.handlebars @@ -0,0 +1,21 @@ + +
+ {{#each post.comments as |comment| }} + {{comment.author.username}} said: +
+ {{comment.body}} + Comment + {{#each comment.comments as |nestedComment| }} + {{nestedComment.author}} said: +
+ {{nestedComment.body}} + Comment + {{/each}} + {{/each }} +
diff --git a/views/sessions/login.handlebars b/views/sessions/login.handlebars new file mode 100644 index 0000000..1d0af1e --- /dev/null +++ b/views/sessions/login.handlebars @@ -0,0 +1,18 @@ + + + +
+
+ + +
+
+ + +
+
+ +
+
diff --git a/views/shared/_nav.handlebars b/views/shared/_nav.handlebars new file mode 100644 index 0000000..fbbebb6 --- /dev/null +++ b/views/shared/_nav.handlebars @@ -0,0 +1,32 @@ + diff --git a/views/users/index.handlebars b/views/users/index.handlebars new file mode 100644 index 0000000..504ee75 --- /dev/null +++ b/views/users/index.handlebars @@ -0,0 +1,48 @@ + + + + + +{{#if users.length }} + + + + + + + + + + + + + {{#each users as |user| }} + + + + + + + + + {{/each }} + +
First NameLast NameUsernameEmailEditDelete
+ {{ user.fname }} + + {{ user.lname }} + + {{ user.username }} + + {{ user.email }} + + Edit + + Delete +
+{{else }} +

No users

+{{/if }} diff --git a/views/users/show.handlebars b/views/users/show.handlebars new file mode 100644 index 0000000..6e92033 --- /dev/null +++ b/views/users/show.handlebars @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
First Name{{ user.fname }}
Last Name{{ user.lname }}
Username{{ user.username }}
Email{{ user.email }}
+ +