diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index 7513987..1a69fc5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # assignment_thoreddit A social news web application for Viking thunder Gods + +by Dan and Stephanie \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..7c2a1ad --- /dev/null +++ b/app.js @@ -0,0 +1,48 @@ +const express = require("express"); +const app = express(); +const bodyParser = require("body-parser"); +const exphbs = require("express-handlebars"); +const mongoose = require("mongoose"); +const methodOverride = require("method-override"); +const getPostSupport = require("express-method-override-get-post-support"); + +app.use(bodyParser.urlencoded({ extended: true })); +app.use(methodOverride(getPostSupport.callback, getPostSupport.options)); + +const hbs = exphbs.create({ + partialsDir: "views/partials", + defaultLayout: "main" +}); +app.engine("handlebars", hbs.engine); +app.set("view engine", "handlebars"); + +var port = process.env.PORT || process.argv[2] || 3000; +var host = "localhost"; + +var args; +process.env.NODE_ENV === "production" ? (args = [port]) : (args = [port, host]); +var sessionsRouter = require("./routers/sessions")(app); + +app.use((req, res, next) => { + if (mongoose.connection.readyState) { + next(); + } else { + require("./mongo")().then(() => next()); + } +}); + +app.use("/", sessionsRouter); + +var usersRouter = require("./routers/users"); +app.use("/users", usersRouter); +var postsRouter = require("./routers/posts"); +app.use("/posts", postsRouter); +args.push(() => { + console.log(`Listening: http://${host}:${port}`); +}); + +app.get("/", (req, res) => { + res.redirect("/users"); +}); + +app.listen.apply(app, args); diff --git a/config/mongo.json b/config/mongo.json new file mode 100644 index 0000000..36c68bc --- /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" + } +} diff --git a/data_structure.txt b/data_structure.txt new file mode 100644 index 0000000..0f12f75 --- /dev/null +++ b/data_structure.txt @@ -0,0 +1,12 @@ +Users + username (string) + email (string) + posts (array) + +Posts + title (string) + author (string) + body (text) + votes (integer) + topLevel (boolean) + comments (array) diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..757645a --- /dev/null +++ b/models/index.js @@ -0,0 +1,15 @@ +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.Post = require("./post"); +//... more models + +module.exports = models; diff --git a/models/post.js b/models/post.js new file mode 100644 index 0000000..8deef47 --- /dev/null +++ b/models/post.js @@ -0,0 +1,15 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; + +var PostSchema = { + title: String, + author: { type: Schema.Types.ObjectId, ref: "User" }, + body: String, + votes: Number, + topLevel: Boolean, + subPosts: [{ type: Schema.Types.ObjectId, ref: "Post" }] +}; + +var Post = mongoose.model("Post", PostSchema); + +module.exports = Post; diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..53eb7c7 --- /dev/null +++ b/models/user.js @@ -0,0 +1,12 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; + +var UserSchema = { + username: String, + email: String, + posts: [{ type: Schema.Types.ObjectId, ref: "Post" }] +}; +//[{ type: Schema.Types.ObjectId, ref: "Post" }] +var User = mongoose.model("User", UserSchema); + +module.exports = User; diff --git a/mongo.js b/mongo.js new file mode 100644 index 0000000..1f7a93c --- /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..4b43c15 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "assignment_thoreddit", + "version": "1.0.0", + "description": "A definitely-not-Reddit application for Vikings", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Avonyel/assignment_thoreddit.git" + }, + "author": "Dan and Stephanie", + "license": "ISC", + "bugs": { + "url": "https://github.com/Avonyel/assignment_thoreddit/issues" + }, + "homepage": "https://github.com/Avonyel/assignment_thoreddit#readme", + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "express": "^4.15.4", + "express-handlebars": "^3.0.0", + "express-method-override-get-post-support": "0.0.7", + "method-override": "^2.3.9", + "mongoose": "^4.11.6", + "mongooseeder": "^2.0.5" + } +} diff --git a/queries.js b/queries.js new file mode 100644 index 0000000..ef8890c --- /dev/null +++ b/queries.js @@ -0,0 +1,209 @@ +const mongoose = require("mongoose"); +var models = require("./models"); +var cp = require("child_process"); + +Object.keys(models).forEach(modelName => { + global[modelName] = mongoose.model(modelName); +}); + +// ---------------------------------------- +// Mongoose Queries +// ---------------------------------------- + +var _num = 0; + +require("./mongo")() + // ---------------------------------------- + // Seed + // ---------------------------------------- + + // ---------------------------------------- + // Find + // ---------------------------------------- + .then(() => { + return User.find().then(_lg("User.find")); + }) + // .then(() => { + // return User.findOne().then(_lg("User.findOne")); + // }) + // .then(user => { + // return User.findById(user.id).then(_lg("User.findById")); + // }) + // .then(user => { + // return User.findByIdAndUpdate(user.id, { + // fname: "Updated!", + // lname: "Updated!" + // }).then(_lg("User.findByIdAndUpdate")); + // }) + // .then(user => { + // return User.findByIdAndRemove(user.id).then(_lg("User.findByIdAndRemove")); + // }) + // .then(user => { + // return User.findOne({}, { _id: 0, fname: 1, lname: 1 }).then( + // _lg("User.findOne Projection") + // ); + // }) + // // ---------------------------------------- + // // Filtering Documents + // // ---------------------------------------- + // .then(() => { + // return Rating.find({ + // value: { $gte: 2 } + // }) + // .limit(10) + // .then(_lg("Rating.find")); + // }) + // // ---------------------------------------- + // // $where + // // ---------------------------------------- + // .then(() => { + // return Rating.$where("this.value >= 2") + // .limit(10) + // .then(_lg("Rating.$where")); + // }) + // // ---------------------------------------- + // // where + // // ---------------------------------------- + // .then(() => { + // return Rating.where("value").gte(2).limit(10).then(_lg("Rating.where")); + // }) + // // ---------------------------------------- + // // Limiting and Offsetting + // // ---------------------------------------- + // .then(() => { + // return Rating.find( + // {}, + // {}, + // { + // limit: 10, + // skip: 10 + // } + // ).then(_lg("Rating.skip.limit")); + // }) + // .then(() => { + // return Rating.find().limit(10).skip(10).then(_lg("Rating.skip.limit")); + // }) + // // ---------------------------------------- + // // Create + // // ---------------------------------------- + // .then(() => { + // return User.create({ + // fname: "Just", + // lname: "Created", + // username: "justcreated", + // email: "just@created.com" + // }).then(_lg("User.create")); + // }) + // // ---------------------------------------- + // // Update + // // ---------------------------------------- + // .then(() => { + // return User.update({ + // fname: "Update", + // lname: "Again!" + // }).then(_lg("User.update")); + // }) + // .then(() => { + // return User.updateMany({ + // fname: "All", + // lname: "Same" + // }).then(_lg("User.updateMany")); + // }) + // // ---------------------------------------- + // // Destroy + // // ---------------------------------------- + // .then(() => { + // return User.remove({ + // username: "justcreated" + // }) + // .then(r => (r ? 1 : 0)) + // .then(_lg("User.remove")); + // }) + // // ---------------------------------------- + // // Sorting + // // ---------------------------------------- + // .then(() => { + // return Rating.find( + // {}, + // { + // _id: 0, + // value: 1 + // }, + // { + // sort: { value: -1 } + // } + // ).then(_lg("Rating.find.sort")); + // }) + // .then(() => { + // return Rating.find( + // {}, + // { + // _id: 0, + // value: 1 + // } + // ) + // .sort({ value: -1 }) + // .then(_lg("Rating.find.sort")); + // }) + // // ---------------------------------------- + // // Instances + // // ---------------------------------------- + // .then(() => { + // return Motel.findOne().then(_lg("Motel.findOne")); + // }) + // .then(motel => { + // motel.name = "Bates Motel"; + // return motel.save().then(_lg("motel.save")); + // }) + // // ---------------------------------------- + // // Populate + // // ---------------------------------------- + // .then(() => { + // return Rating.find() + // .limit(10) + // .populate("user") + // .then(_lg("Rating.find.populate")); + // }) + // .then(() => { + // return Rating.find() + // .limit(10) + // .populate({ + // path: "user", + // select: ["fname", "lname"] + // }) + // .then(_lg("Rating.find.populate({ path, select })")); + // }) + // // ---------------------------------------- + // // Map Reduce + // // ---------------------------------------- + // .then(() => { + // return Rating.mapReduce({ + // map: function() { + // emit(this.ratable, this.value); + // }, + // reduce: function(key, values) { + // if (values.length) { + // var total = values.reduce((sum, value) => { + // return (sum += value); + // }, 0); + // return total / values.length; + // } + // + // return 0; + // } + // }).then(_lg("Rating.mapReduce")); + // }) + // // ---------------------------------------- + // // Aggregate Pipeline + // // ---------------------------------------- + // .then(() => { + // return Rating.aggregate([ + // { $group: { _id: "$ratable", rating: { $avg: "$value" } } }, + // { $sort: { rating: -1 } } + // ]).then(_lg("Rating.aggregate")); + // }) + // // ---------------------------------------- + // // Finish + // ---------------------------------------- + .catch(e => console.error(e.stack)) + .then(() => mongoose.disconnect()); diff --git a/routers/posts.js b/routers/posts.js new file mode 100644 index 0000000..a80a472 --- /dev/null +++ b/routers/posts.js @@ -0,0 +1,171 @@ +var express = require("express"); +var router = express.Router(); +const mongoose = require("mongoose"); +var models = require("./../models"); +var Post = mongoose.model("Post"); +var User = mongoose.model("User"); + +// ---------------------------------------- +// Index +// ---------------------------------------- +router.get("/", (req, res) => { + Post.find({ topLevel: true }) + .populate("author") + .then(posts => { + res.render("posts/index", { posts }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// New +// ---------------------------------------- +router.get("/new", (req, res) => { + res.render("posts/new"); +}); +router.get("/:id/new", (req, res) => { + Post.findById(req.params.id) + .then(post => { + res.render("posts/newSubPost", { post }); + }) + .catch(e => res.status(500).send(e.stack)); +}); +// ---------------------------------------- +// Edit +// ---------------------------------------- +router.get("/:id/edit", (req, res) => { + Post.findById(req.params.id) + .then(post => { + res.render("posts/edit", { post }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// Show +// ---------------------------------------- +router.get("/:id", (req, res) => { + Post.findById(req.params.id) + .populate([ + { + path: "subPosts", + populate: [ + { + path: "subPosts", + model: "Post", + populate: [ + { + path: "subPosts", + model: "Post" + }, + { + path: "author", + model: "User" + } + ] + }, + { + path: "author", + model: "User" + } + ] + }, + { + path: "author", + model: "User" + } + ]) + .then(post => { + console.log(post); + res.render("posts/show", { post }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// Create +// ---------------------------------------- +router.post("/", (req, res) => { + let rnd = Math.floor(Math.random() * 10 + 1); + User.findOne().then(user => { + var post = new Post({ + title: req.body.post.title, + author: user, + body: req.body.post.body, + votes: rnd, + topLevel: true, + subPosts: [] + }); + + post + .save() + .then(post => { + res.redirect(`/posts/${post.id}`); + }) + .catch(e => res.status(500).send(e.stack)); + }); +}); +// +router.post("/:id", (req, res) => { + let rnd = Math.floor(Math.random() * 10 + 1); + User.findOne().then(user => { + var post = new Post({ + title: req.body.post.title, + author: user, + body: req.body.post.body, + votes: rnd, + topLevel: false, + subPosts: [] + }); + + post + .save() + .then(post => { + Post.findByIdAndUpdate(req.params.id, { + $push: { subPosts: post } + }).then(() => { + res.redirect(`/posts/${req.params.id}`); + }); + }) + .catch(e => res.status(500).send(e.stack)); + }); +}); +// ---------------------------------------- +// Update +// ---------------------------------------- +// router.put("/:id", (req, res) => { +// var postParams = { +// title: req.body.post.title, +// author: req.body.post.author, +// body: req.body.post.body, +// votes: req.body.post.votes, +// topLevel: req.body.post.topLevel, +// subPosts: req.body.post.subPosts +// }; + +// User.findByIdAndUpdate(req.params.id, postParams) +// .then(post => { +// req.method = "GET"; +// res.redirect(`/posts/${post.id}`); +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +// ---------------------------------------- +// Destroy +// ---------------------------------------- +// router.delete("/:id", (req, res) => { +// var currentPost = req.session.currentPost; +// Post.findByIdAndRemove(req.params.id) +// .then(() => { +// req.method = "GET"; +// if (currentPost.id === req.params.id) { +// res.redirect("/logout"); +// } else { +// res.redirect("/posts"); +// } +// }) +// .catch(e => res.status(500).send(e.stack)); +// }); + +module.exports = router; diff --git a/routers/ratables.js b/routers/ratables.js new file mode 100644 index 0000000..b592c80 --- /dev/null +++ b/routers/ratables.js @@ -0,0 +1,41 @@ +var express = require('express'); +var router = express.Router(); +const mongoose = require('mongoose'); +var models = require('./../models'); +var Hotel = mongoose.model('Hotel'); +var Motel = mongoose.model('Motel'); + + +// ---------------------------------------- +// Index +// ---------------------------------------- +router.get('/', (req, res) => { + var hotels; + var motels; + Hotel.find({}).populate('ratings') + .then((results) => { + hotels = results; + return Motel.find({}).populate('ratings'); + }) + .then((results) => { + motels = results; + res.render('ratables/index', { hotels, motels }); + }) + .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..442d5c8 --- /dev/null +++ b/routers/sessions.js @@ -0,0 +1,31 @@ +var url = require("url"); +var express = require("express"); +var router = express.Router(); +const mongoose = require("mongoose"); +var models = require("./../models"); +var User = mongoose.model("User"); + +module.exports = app => { + // Auth + + // New + // var onNew = (req, res) => { + // if (req.session.currentUser) { + // res.redirect("/users"); + // } else { + // res.render("sessions/new"); + // } + // + // }; + router.get("/"); + router.get("/login"); + + // Create + + // Destroy + + router.get("/logout"); + router.delete("/logout"); + + return router; +}; diff --git a/routers/users.js b/routers/users.js new file mode 100644 index 0000000..394e9f6 --- /dev/null +++ b/routers/users.js @@ -0,0 +1,99 @@ +var express = require("express"); +var router = express.Router(); +const mongoose = require("mongoose"); +var models = require("./../models"); +var User = mongoose.model("User"); + +// ---------------------------------------- +// Index +// ---------------------------------------- +router.get("/", (req, res) => { + User.find({}) + .then(users => { + res.render("users/index", { users }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// New +// ---------------------------------------- +router.get("/new", (req, res) => { + res.render("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) + .populate("posts") + .then(user => { + res.render("users/show", { user }); + }) + .catch(e => res.status(500).send(e.stack)); +}); + +// ---------------------------------------- +// Create +// ---------------------------------------- +router.post("/", (req, res) => { + var user = new User({ + 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 = { + 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/seeders/index.js b/seeders/index.js new file mode 100644 index 0000000..ba71214 --- /dev/null +++ b/seeders/index.js @@ -0,0 +1,99 @@ +const mongoose = require("mongoose"); +const mongooseeder = require("mongooseeder"); +var env = process.env.NODE_ENV || "development"; +var config = require("./../config/mongo")[env]; +const models = require("./../models"); +const { User, Post } = models; +const mongodbUrl = + process.env.NODE_ENV === "production" + ? process.env[config.use_env_variable] + : `mongodb://${config.host}/${config.database}`; + +const seeds = () => { + console.log("Creating Users"); + var users = []; + for (let i = 1; i < 11; i++) { + var user = new User({ + username: `foobar${i}`, + email: `foobar${i}@gmail.com`, + posts: [] + }); + users.push(user); + } + + console.log("Creating Posts"); + var posts = []; + var authorId; + + for (let i = 1; i < 21; i++) { + authorId = i - 1; + + if (authorId > 9) { + authorId -= 10; + } + + var post = new Post({ + title: `Title of ${i}`, + author: users[authorId], + body: `This is a post! ${i}`, + votes: 0, + topLevel: true, + subPosts: [] + }); + users[authorId].posts.push(post); + posts.push(post); + } + + /// + var subPosts = []; + var authorId; + + for (let i = 0; i < 20; i++) { + authorId = i; + + if (authorId > 9) { + authorId -= 10; + } + + var subPost = new Post({ + title: `Title of ${i}`, + author: users[authorId], + body: `This is a subpost! ${i}`, + votes: 0, + topLevel: false, + subPosts: [] + }); + + var subSubPost = new Post({ + title: `Title of ${i}`, + author: users[authorId], + body: `This is a sub-subpost! ${i}`, + votes: 0, + topLevel: false, + subPosts: [] + }); + subPost.subPosts.push(subSubPost); + posts[authorId].subPosts.push(subPost); + users[authorId].posts.push(subPost); + users[authorId].posts.push(subSubPost); + posts.push(subPost); + posts.push(subSubPost); + } + /// + + var promises = []; + [users, posts].forEach(collection => { + collection.forEach(model => { + promises.push(model.save()); + }); + }); + return Promise.all(promises); +}; + +mongooseeder.seed({ + mongodbUrl: mongodbUrl, + models: models, + clean: true, + mongoose: mongoose, + seeds: seeds +}); diff --git a/views/index.handlebars b/views/index.handlebars new file mode 100644 index 0000000..1fe33eb --- /dev/null +++ b/views/index.handlebars @@ -0,0 +1,6 @@ +
{{ subPost.body }}
+ + + Comment + {{#if subPost.subPosts.length}} + {{>postPartial subPosts=subPost.subPosts}} + {{/if}} +{{ post.body }}
+ {{ if post.subPosts.length}} + {{>postPartial.handlebars}} + {{/if}} +{{/each }} diff --git a/views/posts/edit.handlebars b/views/posts/edit.handlebars new file mode 100644 index 0000000..0bcfec1 --- /dev/null +++ b/views/posts/edit.handlebars @@ -0,0 +1,40 @@ + + +=========================
+ + New Post +=========================
+{{ post.body }}
+ + + Comment +{{ post.body }}
+ + +{{ post.body }}
+ + + Comment + {{#if post.subPosts.length}} + {{>postPartial subPosts=post.subPosts}} + {{/if}} +Username | ++ | + | |
---|---|---|---|
+ {{ user.username }} + | ++ {{ user.email }} + | ++ Edit + | ++ Delete + | +
No users
+{{/if }} + + + + + + + + + + + diff --git a/views/users/new.handlebars b/views/users/new.handlebars new file mode 100644 index 0000000..c0c502a --- /dev/null +++ b/views/users/new.handlebars @@ -0,0 +1,49 @@ + + +Key | +Value | +
---|---|
Username | +{{ user.username }} | +
{{ user.email }} | +|
Number of Posts | +{{ user.posts.length }} | +
{{ post.body }}
+