├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── README.md ├── controllers ├── auth.js ├── chat.js ├── email.js ├── index.js ├── map.js ├── potentialUser.js ├── suggestion.js ├── user.js └── utils │ └── invitations.js ├── images ├── screenshot.jpg ├── screenshot2.jpg ├── screenshot3.jpg ├── screenshot4.jpg ├── screenshot5.jpg └── screenshot6.jpg ├── models ├── Map.js ├── PotentialUser.js ├── chat.js ├── index.js ├── suggestion.js └── user.js ├── nodemailer ├── index.js ├── renderEmail.js └── txt_green.png ├── package.json ├── seeds └── seed.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | models/erd.png 5 | .vscode 6 | .prettierignore 7 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#65c89b", 4 | "activityBar.activeBorder": "#945bc4", 5 | "activityBar.background": "#65c89b", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#945bc4", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#42b883", 11 | "statusBar.foreground": "#15202b", 12 | "statusBarItem.hoverBackground": "#359268", 13 | "titleBar.activeBackground": "#42b883", 14 | "titleBar.activeForeground": "#15202b", 15 | "titleBar.inactiveBackground": "#42b88399", 16 | "titleBar.inactiveForeground": "#15202b99" 17 | }, 18 | "peacock.color": "#42b883" 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PLANiT 2 | PLANiT is a collaborative travel planning app for groups looking to make the most of their next adventure. Users can create planning boards, or maps as we call them, where an unlimited amount of guests can be invited to suggest or vote on plans within user-customized trip catergories (e.g. Resturants, Accommodation, Hikes, Entertainment etc.) The app makes group consensus clear with visual outputs for voting stats and a print itinerary function that ranks the leading suggestions at any given time. Along with a chat room unique to each map, communicating has never been more accessible and centralized. Whether you are trying coordinate plans with a group or are just having fun dreaming about your next vacation, planning a trip has never been easier or as stress free! PLANiT - connecting our world to your plan! 3 | 4 | ## Specifications 5 | PLANiT uses a React.js front end, along with MongoDB to create a robust, full MERN application. Tools and packages that were used to make this application possible include socket.io, nodemailer, react-scroll, react-animate-on-scroll, ant-design, axios, and more. The team has plans to integrate more APIs and features to make PLANiT a travel-prep must-have! 6 | 7 | ## Meet The Team 8 | Bryce Pingul 9 | * GitHub: [Brycetp11](https://github.com/Brycetp11) 10 | 11 | Derek Watson 12 | * GitHub: [derek-watson14](https://github.com/derek-watson14) 13 | 14 | Nicole Remy 15 | * GitHub: [nicoleremy95](https://github.com/nicoleremy95) 16 | 17 | Vincent Varghese 18 | * GitHub: [VinAVarghese](https://github.com/VinAVarghese) 19 | 20 | Zack Deacon 21 | * GitHub: [zackdeacon](https://github.com/zackdeacon) 22 | 23 | ## Contact 24 | * Email: [teamplanitcartographers@gmail.com](mailto:teamplanitcartographers@gmail.com) 25 | Feel free to email the team with any suggestions, questions, or comments with the subject line "RE: PLANiT". 26 | Thank you and happy travels! 27 | 28 | ## Links/Images 29 | [Click For Deployed Application](https://travelplanit.herokuapp.com/) 30 | 31 | ![Screenshot](./images/screenshot.jpg) 32 | ![Screenshot](./images/screenshot2.jpg) 33 | ![Screenshot](./images/screenshot3.jpg) 34 | ![Screenshot](./images/screenshot4.jpg) 35 | ![Screenshot](./images/screenshot5.jpg) 36 | ![Screenshot](./images/screenshot6.jpg) 37 | -------------------------------------------------------------------------------- /controllers/auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const db = require("../models"); 3 | const router = express.Router(); 4 | //password encryption 5 | const bcrypt = require("bcrypt"); 6 | 7 | //SIGN UP 8 | router.post("/signup", (req, res) => { 9 | const { username, password, email, name } = req.body; 10 | db.User.create({ 11 | username: username, 12 | password: password, 13 | email: email, 14 | name: { 15 | first: name ? name.first : "", 16 | last: name ? name.last : "", 17 | }, 18 | }) 19 | .then(async function (newUser) { 20 | req.session.user = { 21 | id: newUser._id, 22 | username: newUser.username, 23 | email: newUser.email 24 | }; 25 | const potentialUser = await db.PotentialUser.findOne({ email: newUser.email }); 26 | if (potentialUser) { 27 | newUser.invitations.push(...potentialUser.invitedMapIds); 28 | const dbOperationPromises = [ 29 | await newUser.save(), 30 | await db.PotentialUser.deleteOne({ _id: potentialUser._id }), 31 | ]; 32 | const dbOpertationResults = await Promise.all(dbOperationPromises); 33 | res.json({ newUser, dbOpertationResults }); 34 | } else { 35 | console.log(newUser); 36 | res.json(newUser); 37 | } 38 | }) 39 | .catch(function (err) { 40 | console.log(err); 41 | res.status(500).json(err); 42 | }); 43 | }); 44 | 45 | //LOGIN 46 | router.post("/login", (req, res) => { 47 | db.User.findOne({ 48 | username: req.body.username, 49 | }) 50 | .then((user) => { 51 | if (!user) { 52 | return res.status(404).send("no such user"); 53 | } else { 54 | console.log("User (auth.js)", user) 55 | if (bcrypt.compareSync(req.body.password, user.password)) { 56 | req.session.user = { 57 | id: user._id, 58 | username: user.username, 59 | email: user.email 60 | }; 61 | console.log("req.session.user (auth.js)", req.session.user); 62 | res.send(req.session); 63 | } else { 64 | res.status(401).send("wrong password"); 65 | } 66 | } 67 | }) 68 | .catch((err) => { 69 | console.log(err) 70 | res.status(500).end(); 71 | }); 72 | }); 73 | 74 | //LOG OUT 75 | router.get("/logout", (req, res) => { 76 | req.session.destroy(); 77 | res.send("You have been logged out!"); 78 | }); 79 | 80 | //READ SESSIONS 81 | router.get("/readsession", (req, res) => { 82 | res.json(req.session); 83 | }); 84 | 85 | module.exports = router; 86 | -------------------------------------------------------------------------------- /controllers/chat.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | 5 | // Get all chats in the database 6 | // Passed test call 7 | router.get("/", (req, res) => { 8 | db.Chat.find({}) 9 | .then(allChats => { 10 | res.json(allChats); 11 | }) 12 | .catch((err) => { 13 | console.log(err); 14 | res.status(500).end(); 15 | }); 16 | }); 17 | 18 | // Get all chats for a specific map 19 | // Passed test call 20 | router.get("/map/:mapId", (req, res) => { 21 | db.Chat.find({ map: req.params.mapId }) 22 | .populate("user", "username name") 23 | .then(allMapChats => { 24 | res.json(allMapChats) 25 | res.status(204).end() 26 | }).catch(err => { 27 | console.log(err) 28 | res.status(500).end() 29 | }) 30 | }) 31 | 32 | 33 | // Session Conditional >> When ready, uncomment 34 | router.post("/new", (req, res) => { 35 | if (!req.session.user) { 36 | res.status(401).send("login required") 37 | } else { 38 | db.Chat.create({ 39 | user: req.session.user.id, 40 | map: req.body.mapId, 41 | message: req.body.message, 42 | }).then(newChat => { 43 | res.json(newChat); 44 | res.status(204).end() 45 | }).catch(err => { 46 | console.log(err); 47 | res.status(500).end() 48 | }) 49 | } 50 | }) 51 | 52 | // Delete chat 53 | // Passed test call 54 | router.delete("/delete", (req, res) => { 55 | db.Chat.deleteOne({ 56 | _id: req.body.chatId, 57 | }).then(deleteData => { 58 | res.json(deleteData) 59 | res.status(204).end() 60 | }).catch(err => { 61 | console.log(err) 62 | res.status(500).end() 63 | }) 64 | }) 65 | 66 | 67 | module.exports = router; 68 | -------------------------------------------------------------------------------- /controllers/email.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | const nodemailer = require("../nodemailer"); 5 | 6 | // * Different data than a mapId and an email can be passed in, 7 | // * this is just an example 8 | // * go to nodemailer/index.js then final render to edit the email 9 | router.post("/send/map/render", async (req, res) => { 10 | const { mapId, email } = req.body; 11 | console.log(mapId, email); 12 | const map = await db.Map.findById(mapId); 13 | const suggestions = await db.Suggestion.find({ mapId: mapId }); 14 | nodemailer.sendEmail({ 15 | to: nodemailer.finalRender.email(map), 16 | subject: nodemailer.finalRender.subject(map), 17 | text: nodemailer.finalRender.text({ map, suggestions }), 18 | html: nodemailer.finalRender.html({ map, suggestions }), 19 | }).then(data => { 20 | console.log("sent!"); 21 | res.json(data) 22 | }).catch(err => { 23 | console.log(err); 24 | res.json(err); 25 | }) 26 | }) 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const userRoutes = require("./user"); 5 | const mapRoutes = require("./map"); 6 | const suggestionRoutes = require("./suggestion"); 7 | const chatRoutes = require("./chat"); 8 | const authRoutes = require("./auth"); 9 | const emailRoutes = require("./email"); 10 | 11 | router.use("/api/users", userRoutes); 12 | router.use("/api/maps", mapRoutes); 13 | router.use("/api/suggestions", suggestionRoutes); 14 | router.use("/api/chats", chatRoutes); 15 | router.use("/api/auth", authRoutes); 16 | router.use("/api/email", emailRoutes); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /controllers/map.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | const inviter = require("./utils/invitations"); 5 | const { connection } = require("mongoose"); 6 | 7 | // Get all maps in the database 8 | // Passed test call 9 | router.get("/", (req, res) => { 10 | db.Map.find({}) 11 | .then((allMaps) => { 12 | res.json(allMaps); 13 | }) 14 | .catch((err) => { 15 | console.log(err); 16 | res.status(500).end(); 17 | }); 18 | }); 19 | 20 | // Get one map by id 21 | // Passed test call 22 | router.get("/one/id/:mapId", (req, res) => { 23 | db.Map.findOne({ _id: req.params.mapId }) 24 | .populate("creatorId", "username name") 25 | .then((map) => { 26 | res.json(map); 27 | }) 28 | .catch((err) => { 29 | console.log(err); 30 | res.status(500).end(); 31 | }); 32 | }); 33 | 34 | // Create a new map 35 | // Passed test call 36 | router.post("/new", (req, res) => { 37 | if (!req.session.user) { 38 | res.status(401).send("login required") 39 | } else { 40 | const { name, guests, dates, destinations } = req.body; 41 | db.Map.create({ 42 | name: name, 43 | creator: req.session.user.username, 44 | creatorId: req.session.user.id, 45 | dates: { 46 | start: dates ? dates.start : "", 47 | end: dates ? dates.end : "", 48 | }, 49 | guests: guests, 50 | destinations: destinations, 51 | }).then(newMap => { 52 | db.User.findById(newMap.creatorId).then(user => { 53 | user.createdMaps.push(newMap._id); 54 | user.save(); 55 | }).catch(err => { 56 | console.log(err, "no user found"); 57 | res.status(500).end() 58 | }) 59 | // Invite guests 60 | const inviterInfo = { 61 | tripName: newMap.name, 62 | mapId: newMap._id, 63 | creatorId: newMap.creatorId, 64 | guestEmails: newMap.guests, 65 | }; 66 | inviter.inviteGuests(inviterInfo); 67 | res.json(newMap) 68 | res.status(204).end() 69 | }).catch(err => { 70 | console.log(err) 71 | res.status(500).end() 72 | }) 73 | } 74 | }); 75 | 76 | router.put("/categories/add", (req, res) => { 77 | const { mapId, newCategory } = req.body; 78 | db.Map.findById(mapId).then(async (map) => { 79 | const catsLower = map.suggestionCategories.map(cat => cat.toLowerCase()); 80 | if (catsLower.includes(newCategory.toLowerCase())) { 81 | res.json({ 82 | successful: false, 83 | message: "Category already exists.", 84 | categories: map.suggestionCategories, 85 | }); 86 | } else { 87 | map.suggestionCategories.push(newCategory); 88 | await map.save(); 89 | res.json({ 90 | successful: true, 91 | message: `${newCategory} added to suggestion categories.`, 92 | categories: map.suggestionCategories, 93 | }); 94 | } 95 | }) 96 | }); 97 | 98 | router.put("/categories/remove", (req, res) => { 99 | const { mapId, category } = req.body; 100 | db.Map.findById(mapId).then(async (map) => { 101 | const removeIndex = map.suggestionCategories.indexOf(category); 102 | if (removeIndex >= 0) { 103 | map.suggestionCategories.splice(removeIndex, 1); 104 | db.Suggestion.deleteMany({ 105 | mapId: mapId, 106 | category: category, 107 | }); 108 | await map.save(); 109 | res.json({ 110 | successful: true, 111 | message: "Category removed", 112 | categories: map.suggestionCategories, 113 | }); 114 | } else { 115 | res.json({ 116 | successful: false, 117 | message: "Category not found", 118 | categories: map.suggestionCategories, 119 | }); 120 | } 121 | }) 122 | }); 123 | 124 | router.put("/invite", (req, res) => { 125 | const { mapId, guestEmail } = req.body; 126 | db.Map.findById(mapId).then(async (map) => { 127 | if (map.guests.includes(guestEmail)) { 128 | res.json({ 129 | message: `${guestEmail} already invited`, 130 | successful: false, 131 | guests: map.guests, 132 | }) 133 | } else { 134 | const inviterInfo = { 135 | tripName: map.name, 136 | mapId: map._id, 137 | creatorId: map.creatorId, 138 | newGuest: guestEmail, 139 | guestEmails: [guestEmail], 140 | }; 141 | inviter.inviteGuests(inviterInfo); 142 | map.guests.push(guestEmail); 143 | await map.save(); 144 | res.json({ 145 | message: `${guestEmail} has been invited`, 146 | successful: true, 147 | newGuest: guestEmail, 148 | guests: map.guests, 149 | }) 150 | } 151 | }) 152 | }) 153 | 154 | // Delete map 155 | // Passed test call 156 | router.delete("/delete", (req, res) => { 157 | db.Map.deleteOne({ 158 | _id: req.body.id, 159 | }).then(async (mapDel) => { 160 | try { 161 | const deletePromises = [ 162 | mapDel, 163 | await db.Suggestion.deleteMany({ mapId: req.body.id }), 164 | await db.Chat.deleteMany({ mapId: req.body.id }), 165 | ]; 166 | const deleteData = await Promise.all(deletePromises); 167 | res.json({ 168 | map: deleteData[0], 169 | suggestions: deleteData[1], 170 | chats: deleteData[2], 171 | }); 172 | } catch { 173 | console.log(error); 174 | res.status(500).end(); 175 | } 176 | }).catch((err) => { 177 | console.log(err); 178 | res.status(500).end(); 179 | }); 180 | }); 181 | 182 | 183 | //upload image 184 | router.post("/images/new/:mapId",(req,res)=>{ 185 | console.log('req.body.images', req.body.images) 186 | db.Map.findOne({ _id: req.params.mapId }) 187 | .then(data=>{ 188 | console.log('data', data) 189 | data.images.push(req.body.images) 190 | console.log('data.images', data.images) 191 | data.save() 192 | res.json(data) 193 | }) 194 | .catch(err=>{ 195 | console.log('err', err) 196 | // connection.end() 197 | }) 198 | }) 199 | 200 | //all images for map 201 | router.get("/images/:mapId", (req,res)=>{ 202 | console.log(req.body) 203 | db.Map.findOne({ 204 | _id: req.params.mapId 205 | }).then(map=>{ 206 | console.log('map', map) 207 | res.json(map.images) 208 | }).catch(err=>{ 209 | console.log("err",err) 210 | }) 211 | }) 212 | 213 | module.exports = router; 214 | -------------------------------------------------------------------------------- /controllers/potentialUser.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | 5 | // Get all users in the database 6 | // Passed test call 7 | router.get("/", (req, res) => { 8 | db.PotentialUser.find({}) 9 | .then((allPotentialUsers) => { 10 | res.json(allPotentialUsers); 11 | }) 12 | .catch((err) => { 13 | console.log(err); 14 | res.status(500).end(); 15 | }); 16 | }); 17 | 18 | module.exports = router; 19 | 20 | 21 | -------------------------------------------------------------------------------- /controllers/suggestion.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | 5 | // Get all suggestions in the database 6 | // Passed test call 7 | router.get("/", (req, res) => { 8 | db.Suggestion.find({}) 9 | .then((allChats) => { 10 | res.json(allChats); 11 | }) 12 | .catch((err) => { 13 | console.log(err); 14 | res.status(500).end(); 15 | }); 16 | }); 17 | 18 | // Get all suggestions for a specific map 19 | // Passed test call 20 | router.get("/map/:mapId", (req, res) => { 21 | db.Suggestion.find({ 22 | mapId: req.params.mapId 23 | }) 24 | .populate("userId", "username name") 25 | .then(allMapSuggestions => { 26 | res.json(allMapSuggestions) 27 | res.status(204).end() 28 | }).catch(err => { 29 | console.log(err) 30 | res.status(500).end() 31 | }) 32 | }) 33 | 34 | // Add suggestion to suggestion collecton 35 | // Passed test call 36 | router.post("/new", (req, res) => { 37 | if (!req.session.user) { 38 | res.status(401).send("login required") 39 | } else { 40 | db.Suggestion.create({ 41 | userId: req.session.user.id, 42 | mapId: req.body.mapId, 43 | title: req.body.title, 44 | category: req.body.category, 45 | description: req.body.description, 46 | link: req.body.link, 47 | cost: req.body.cost, 48 | }).then((newSuggestion) => { 49 | res.json(newSuggestion); 50 | }).catch((err) => { 51 | console.log(err); 52 | res.status(500).end(); 53 | }); 54 | } 55 | }); 56 | 57 | 58 | // Delete a suggestion by id 59 | // Passed a test call 60 | router.delete("/delete", (req, res) => { 61 | db.Suggestion.deleteOne({ 62 | _id: req.body.id, 63 | }).then((data) => { 64 | res.json(data); 65 | }).catch((err) => { 66 | console.log(err); 67 | res.status(500).end(); 68 | }); 69 | }); 70 | 71 | //route for new vote 72 | router.post("/vote/:suggestionId", (req, res) => { 73 | console.log(req.body) 74 | db.Suggestion.findOne({ 75 | _id: req.params.suggestionId 76 | 77 | }) 78 | .then(data => { 79 | // console.log("this is the data",data) 80 | let voteThing = false 81 | for(i=0; i { 106 | console.log(req.body) 107 | db.Suggestion.findOne({ 108 | _id: req.params.suggestionId 109 | }) 110 | .then(data => { 111 | // console.log("this is the data",data) 112 | data.comments.push({ 113 | userId: req.session.user.id, 114 | message: req.body.message 115 | }) 116 | data.save() 117 | res.json(data) 118 | }) 119 | }) 120 | 121 | router.get("/comments/:suggestionId", (req, res) => { 122 | // console.log("this is the thing", req.body) 123 | db.Suggestion.findOne({ 124 | _id:req.params.suggestionId 125 | 126 | }).populate("userId", "username name") 127 | .then(data=>{ 128 | console.log("this is the suggestion", data) 129 | console.log("all Comments", data.comments) 130 | res.json(data.comments) 131 | }).catch(err=>{ 132 | console.log("why this is happening",err) 133 | }) 134 | }) 135 | 136 | 137 | module.exports = router; 138 | 139 | -------------------------------------------------------------------------------- /controllers/user.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const db = require("../models"); 4 | const bcrypt = require("bcrypt"); 5 | const nodemailer = require("../nodemailer"); 6 | 7 | // Get all users in the database 8 | router.get("/", (req, res) => { 9 | db.User.find({}) 10 | .then((allUsers) => { 11 | res.json(allUsers); 12 | }) 13 | .catch((err) => { 14 | console.log(err); 15 | res.status(500).end(); 16 | }); 17 | }); 18 | 19 | // Accept invitation 20 | router.put("/invitation/accept", (req, res) => { 21 | db.User.findById(req.session.user.id) 22 | .then(async (user) => { 23 | user.invitations.splice(req.body.index, 1); 24 | user.guestMaps.push(req.body.mapId); 25 | const updatedUser = await user.save(); 26 | res.json(updatedUser); 27 | }) 28 | .catch((err) => { 29 | res.status(500).end(err); 30 | }) 31 | }) 32 | 33 | // Decline invitation 34 | router.put("/invitation/decline", (req, res) => { 35 | db.User.findById(req.session.user.id) 36 | .then(async (user) => { 37 | user.invitations.splice(req.body.index, 1); 38 | const updatedUser = await user.save(); 39 | res.json(updatedUser); 40 | }) 41 | .catch((err) => { 42 | console.log(err); 43 | res.status(500).end(err); 44 | }) 45 | }) 46 | 47 | // Change user's name 48 | router.put("/change/name", (req, res) => { 49 | db.User.findById(req.session.user.id) 50 | .then(async (user) => { 51 | const newName = { first: user.name.first, last: user.name.last }; 52 | if (req.body.first) { 53 | newName.first = req.body.first; 54 | } 55 | if (req.body.last) { 56 | newName.last = req.body.last; 57 | } 58 | user.name = newName; 59 | const updatedUser = await user.save(); 60 | res.json(updatedUser); 61 | }) 62 | .catch((err) => { 63 | console.log(err); 64 | res.status(500).end(); 65 | }) 66 | }) 67 | 68 | // Reset password 69 | router.put("/reset/password", (req, res) => { 70 | console.log(req.body) 71 | db.User.findById(req.body.userId) 72 | .then(async (user) => { 73 | const { username, name, email } = user; 74 | var tempPass = Math.random().toString(36).slice(-8); 75 | user.password = tempPass; 76 | await user.save(); 77 | 78 | nodemailer.sendEmail({ 79 | to: email, 80 | subject: nodemailer.passwordReset.subject(user.username), 81 | text: nodemailer.passwordReset.text({ username, name, tempPass }), 82 | html: nodemailer.passwordReset.html({ username, name, tempPass }), 83 | }) 84 | res.json({ username, email }); 85 | }) 86 | .catch((err) => { 87 | console.log(err); 88 | res.status(500).end(); 89 | }) 90 | }) 91 | 92 | // Change user password 93 | router.put("/change/password", (req, res) => { 94 | db.User.findById(req.session.user.id) 95 | .then(async (user) => { 96 | const { oldPassword, newPassword } = req.body; 97 | let success, message; 98 | if (bcrypt.compareSync(oldPassword, user.password)) { 99 | user.password = newPassword; 100 | await user.save(); 101 | success = true; 102 | message = "Password updated successfully!"; 103 | } else { 104 | success = false; 105 | message = "Old password incorrect!"; 106 | } 107 | res.json({ username: user.username, success, message }); 108 | }) 109 | .catch((err) => { 110 | console.log(err); 111 | res.status(500).end(); 112 | }) 113 | }) 114 | 115 | // Get one user by id 116 | router.get("/one/id/:userId", (req, res) => { 117 | db.User.findOne({ _id: req.params.userId }) 118 | .populate("createdMaps") 119 | .populate("guestMaps") 120 | .populate("invitations", "name _id creator") 121 | .exec() 122 | .then((user) => { 123 | res.json(user); 124 | }) 125 | .catch((err) => { 126 | console.log(err); 127 | res.status(500).end(); 128 | }); 129 | }); 130 | 131 | // Get one user by username 132 | router.get("/one/username/:username", (req, res) => { 133 | db.User.findOne({ username: req.params.username }) 134 | .populate("createdMaps") 135 | .populate("guestMaps") 136 | .populate("invitations", "name _id creator") 137 | .exec() 138 | .then((user) => { 139 | res.json(user); 140 | }) 141 | .catch((err) => { 142 | console.log(err); 143 | res.status(500).end(); 144 | }); 145 | }); 146 | 147 | // Delete users by id 148 | router.delete("/delete/:userId", (req, res) => { 149 | db.User.deleteOne({ 150 | _id: req.params.userId, 151 | }).then(async (userDel) => { 152 | try { 153 | const deletePromises = []; 154 | const mapDel = await db.Map.deleteMany({ creatorId: req.params.userId }); 155 | const sugDel = await db.Suggestion.deleteMany({ userId: req.params.userId }); 156 | const chatDel = await db.Chat.deleteMany({ user: req.params.userId }); 157 | deletePromises.push(userDel, mapDel, sugDel, chatDel); 158 | const deleteData = await Promise.all(deletePromises); 159 | req.session.destroy(); 160 | res.json({ 161 | user: deleteData[0], 162 | maps: deleteData[1], 163 | suggestions: deleteData[2], 164 | chats: deleteData[3], 165 | }); 166 | } catch { 167 | console.log(error); 168 | res.status(500).end(); 169 | } 170 | }).catch((err) => { 171 | console.log(err); 172 | res.status(500).end(); 173 | }); 174 | }); 175 | 176 | // Upload Profile Picture 177 | router.post("/picture/:userId", (req, res) => { 178 | console.log('req.body.image', req.body.image) 179 | db.User.findOne({ _id: req.params.userId }) 180 | .then(data => { 181 | console.log('data', data) 182 | data.image.push(req.body.image) 183 | console.log('data.image', data.image) 184 | data.save() 185 | res.json(data) 186 | }) 187 | .catch(err => { 188 | console.log('err', err) 189 | // connection.end() 190 | }) 191 | }) 192 | 193 | module.exports = router; 194 | -------------------------------------------------------------------------------- /controllers/utils/invitations.js: -------------------------------------------------------------------------------- 1 | const db = require("../../models"); 2 | const nodemailer = require("../../nodemailer"); 3 | 4 | const inviter = { 5 | // Check if each guest email already has an acccount 6 | inviteGuests: async function (inviterInfo) { 7 | const { tripName, mapId, creatorId, guestEmails } = inviterInfo; 8 | 9 | const creator = await db.User.findById(creatorId); 10 | const creatorName = creator.name; 11 | 12 | for (let guestEmail of guestEmails) { 13 | const guestAccounts = await db.User.find({ email: guestEmail }); 14 | const emailInfo = { guestEmail, tripName, creatorName }; 15 | 16 | if (guestAccounts.length > 0) { 17 | emailInfo.isNewUser = false; 18 | this.sendGuestInvitation(emailInfo); 19 | guestAccounts.forEach((account) => { 20 | account.invitations.push(mapId); 21 | account.save(); 22 | }); 23 | } else { 24 | emailInfo.isNewUser = true; 25 | this.addPotentialUserMap(guestEmail, mapId); 26 | this.sendGuestInvitation(emailInfo); 27 | } 28 | } 29 | }, 30 | 31 | // Send a guest an email invitiation 32 | sendGuestInvitation: function (emailInfo) { 33 | nodemailer.sendEmail({ 34 | to: emailInfo.guestEmail, 35 | subject: nodemailer.invitation.subject(emailInfo.creatorName), 36 | text: nodemailer.invitation.text(emailInfo), 37 | html: nodemailer.invitation.html(emailInfo), 38 | }).then(info => { 39 | console.log(info); 40 | }).catch(err => { 41 | console.log(err); 42 | }); 43 | }, 44 | 45 | addPotentialUserMap: async function (email, mapId) { 46 | const potentialUser = await db.PotentialUser.findOne({ email }); 47 | if (potentialUser) { 48 | potentialUser.invitedMapIds.push(mapId); 49 | await potentialUser.save(); 50 | console.log(potentialUser); 51 | } else { 52 | const newPotentialUser = await db.PotentialUser.create({ 53 | email: email, 54 | invitedMapIds: [mapId], 55 | }); 56 | console.log(newPotentialUser); 57 | } 58 | } 59 | } 60 | 61 | module.exports = inviter; -------------------------------------------------------------------------------- /images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot.jpg -------------------------------------------------------------------------------- /images/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot2.jpg -------------------------------------------------------------------------------- /images/screenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot3.jpg -------------------------------------------------------------------------------- /images/screenshot4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot4.jpg -------------------------------------------------------------------------------- /images/screenshot5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot5.jpg -------------------------------------------------------------------------------- /images/screenshot6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/images/screenshot6.jpg -------------------------------------------------------------------------------- /models/Map.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const mapSchema = new Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | creator: { 10 | type: String 11 | }, 12 | creatorId: { 13 | type: Schema.Types.ObjectId, 14 | ref: "User", 15 | required: true, 16 | }, 17 | guests: [ 18 | { 19 | type: String 20 | }, 21 | ], 22 | dates: { 23 | start: { 24 | type: String, 25 | default: "", 26 | }, 27 | end: { 28 | type: String, 29 | default: "", 30 | }, 31 | }, 32 | destinations: [String], 33 | suggestionCategories: { 34 | type: [String], 35 | default: ["Accommodation", "Flights", "Food", "Entertainment"], 36 | }, 37 | images: [] 38 | }, { timestamps: true }); 39 | 40 | const Map = mongoose.model("Map", mapSchema); 41 | 42 | module.exports = Map; 43 | -------------------------------------------------------------------------------- /models/PotentialUser.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const potentialUserSchema = new Schema({ 5 | email: { 6 | type: String, 7 | unique: true, 8 | }, 9 | invitedMapIds: [{ 10 | type: Schema.Types.ObjectId, 11 | ref: "Map", 12 | }], 13 | }); 14 | 15 | const PotentialUser = mongoose.model("PotentialUser", potentialUserSchema); 16 | 17 | module.exports = PotentialUser; -------------------------------------------------------------------------------- /models/chat.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const chatSchema = new Schema({ 5 | user: { 6 | type: Schema.Types.ObjectId, 7 | ref: "User", 8 | required: true, 9 | }, 10 | map: { 11 | type: Schema.Types.ObjectId, 12 | ref: "Map", 13 | required: true, 14 | }, 15 | message: { 16 | type: String, 17 | required: true, 18 | }, 19 | }, { timestamps: true }); 20 | 21 | const Chat = mongoose.model("Chat", chatSchema); 22 | 23 | module.exports = Chat; 24 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | User: require("./user"), 3 | Map: require("./Map"), 4 | Suggestion: require("./suggestion"), 5 | Chat: require("./chat"), 6 | PotentialUser: require("./PotentialUser"), 7 | }; 8 | -------------------------------------------------------------------------------- /models/suggestion.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | // https://mongoosejs.com/docs/subdocs.html 5 | const voteSchema = new Schema({ 6 | userId: { 7 | type: Schema.Types.ObjectId, 8 | ref: "User", 9 | required: true, 10 | }, 11 | vote: Boolean, 12 | }, { timestamps: true }); 13 | 14 | const commentSchema = new Schema({ 15 | userId: { 16 | type: Schema.Types.ObjectId, 17 | ref: "User", 18 | required: true, 19 | }, 20 | message: String, 21 | }, { timestamps: true }) 22 | 23 | const suggestionSchema = new Schema({ 24 | userId: { 25 | type: Schema.Types.ObjectId, 26 | ref: "User", 27 | required: true, 28 | }, 29 | mapId: { 30 | type: Schema.Types.ObjectId, 31 | ref: "Map", 32 | required: true, 33 | }, 34 | title: { 35 | type: String, 36 | required: true, 37 | }, 38 | category: { 39 | type: String, 40 | required: true, 41 | }, 42 | description: { 43 | type: String, 44 | required: true, 45 | }, 46 | link: String, 47 | cost: Number, 48 | date: Date, 49 | votes: [voteSchema], 50 | comments: [commentSchema], 51 | }, { timestamps: true }); 52 | 53 | const Suggestion = mongoose.model("Suggestion", suggestionSchema); 54 | 55 | module.exports = Suggestion; 56 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const mongoose = require("mongoose"); 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema( 6 | { 7 | username: { 8 | type: String, 9 | required: true, 10 | unique: true, 11 | }, 12 | password: { 13 | type: String, 14 | required: true, 15 | }, 16 | email: { 17 | type: String, 18 | required: true, 19 | }, 20 | name: { 21 | first: { 22 | type: String, 23 | required: true, 24 | }, 25 | last: { 26 | type: String, 27 | required: true, 28 | }, 29 | }, 30 | createdMaps: [ 31 | { 32 | type: Schema.Types.ObjectId, 33 | ref: "Map", 34 | }, 35 | ], 36 | guestMaps: [ 37 | { 38 | type: Schema.Types.ObjectId, 39 | ref: "Map", 40 | }, 41 | ], 42 | invitations: [ 43 | { 44 | type: Schema.Types.ObjectId, 45 | ref: "Map", 46 | }, 47 | ], 48 | image: [] 49 | }, 50 | { timestamps: true } 51 | ); 52 | 53 | //encrypt password before adding to database 54 | userSchema.pre("save", function (next) { 55 | const user = this; 56 | if (!user.isModified("password")) return next(); 57 | bcrypt.genSalt(10, function (err, salt) { 58 | if (err) return next(err); 59 | 60 | bcrypt.hash(user.password, salt, function (err, hash) { 61 | if (err) return next(err); 62 | user.password = hash; 63 | next(); 64 | }); 65 | }); 66 | }); 67 | 68 | const User = mongoose.model("User", userSchema); 69 | 70 | module.exports = User; 71 | -------------------------------------------------------------------------------- /nodemailer/index.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | require('dotenv').config(); 3 | 4 | const TEAM_EMAIL_ADDRESS = 'teamplanitcartographers@gmail.com'; 5 | const PLANIT_URL = "https://travelplanit.herokuapp.com/#loginform"; 6 | 7 | //Create Transporter 8 | const transporter = nodemailer.createTransport({ 9 | service: 'gmail', 10 | auth: { 11 | user: 'teamplanitcartographers@gmail.com', 12 | // ! NEED THIS FOR TESTING 13 | // pass: process.env.NODEMAILER 14 | pass: "planitpassword1" 15 | } 16 | }); 17 | 18 | const mailer = { 19 | sendEmail: async (options) => { 20 | options.from = TEAM_EMAIL_ADDRESS; 21 | return await transporter.sendMail(options); 22 | }, 23 | invitation: { 24 | subject: ({ first, last }) => { 25 | return `${first} ${last} has invited you to help plan a trip with PLANiT!`; 26 | }, 27 | text: ({ tripName, creatorName, isNewUser }) => { 28 | const instructions = isNewUser 29 | ? "signing up for an account" 30 | : "logging into your account"; 31 | return ` 32 | ${tripName}\n\n 33 | Help ${creatorName.first} plan this trip by visiting ${PLANIT_URL} and ${instructions}. You will then be able to view the trip itinerary, suggest ideas, vote on suggestions and chat with other people on the trip!\n 34 | See you there! 35 | `; 36 | }, 37 | html: ({ tripName, creatorName, isNewUser }) => { 38 | const instructions = isNewUser 39 | ? "signing up for an account" 40 | : "logging into your account"; 41 | 42 | return ` 43 |

${tripName}

44 |

45 | Help ${creatorName.first} plan this trip by visiting PLANiT and ${instructions}. 46 |

47 |

Then you can:

48 | 54 | 55 |

See you there!

56 | `; 57 | } 58 | }, 59 | finalRender: { 60 | email: (data) => { 61 | // const guestList = data.guests.map(guest => guest) 62 | // console.log(guestList) 63 | return data.guests 64 | }, 65 | subject: (map) => { 66 | return `Your trip plan for ${map.name}!`; 67 | }, 68 | text: (data) => { 69 | // console.log(data); 70 | const suggestionList = data.suggestions.map(sugg => `- ${sugg.title}\n`).join(); 71 | return ` 72 | We are going to ${data.map.destinations[0]}!\n 73 | Text body goes here for email clients without HTML support.\n 74 | ${suggestionList} 75 | ` 76 | }, 77 | html: (data) => { 78 | const accomodationArr = []; 79 | const flightArr = []; 80 | const foodArr = []; 81 | const entertainmentArr = []; 82 | const otherArr = []; 83 | console.log(data.suggestions) 84 | for (let i = 0; i < data.suggestions.length; i++) { 85 | if (data.suggestions[i].category === "Accommodation") { 86 | accomodationArr.push(data.suggestions[i]) 87 | } 88 | else if (data.suggestions[i].category === "Flights") { 89 | flightArr.push(data.suggestions[i]) 90 | } 91 | else if (data.suggestions[i].category === "Food") { 92 | foodArr.push(data.suggestions[i]) 93 | } 94 | else if (data.suggestions[i].category === "Entertainment") { 95 | entertainmentArr.push(data.suggestions[i]) 96 | } 97 | else { 98 | otherArr.push(data.suggestions[i]) 99 | } 100 | 101 | } 102 | console.log("here is the array!") 103 | console.log(accomodationArr) 104 | const accomodationList = accomodationArr.map(place => `
  • ${place.title} --- $${place.cost} \n Link
  • `) 105 | const flightList = flightArr.map(flight => `
  • ${flight.title} --- $${flight.cost} \n Link
  • `) 106 | const foodList = foodArr.map(food => `
  • ${food.title} --- $${food.cost} \n Link
  • `) 107 | const entertainmentList = entertainmentArr.map(fun => `
  • ${fun.title} --- $${fun.cost} \n Link
  • `) 108 | const otherList = otherArr.map(other => `
  • ${other.title} --- $${other.cost} \n Link
  • `) 109 | 110 | return ` 111 | 112 |

    ${data.map.name}

    113 |

    114 | Here is the final itinerary for ${data.map.name}! We hope you have a fantastic trip and thanks for letting us help you Planit! 115 |

    116 | 117 |
    118 |

    Accomodations

    119 |
      120 | ${accomodationList} 121 |
    122 |
    123 |
    124 |

    Flights

    125 |
      126 | ${flightList} 127 |
    128 |
    129 |
    130 |

    Food

    131 |
      132 | ${foodList} 133 |
    134 |
    135 |
    136 |

    Entertainment

    137 |
      138 | ${entertainmentList} 139 |
    140 |
    141 |
    142 |

    Other

    143 |
      144 | ${otherList} 145 |
    146 |
    147 |
    148 | 149 |

    Make sure you say thanks to ${data.map.creator} for planning this great trip!

    150 | 151 |

    Best,

    152 |

    Team PLANiT

    153 | 154 | 155 | `; 156 | } 157 | }, 158 | passwordReset: { 159 | subject: (username) => { 160 | return `Instructions to reset PLANiT password for ${username}`; 161 | }, 162 | text: ({ username, name, tempPass }) => { 163 | return ` 164 | Hello ${name.first},\n\n 165 | We have recieved a request to reset your password on PLANiT, the collaborative travel planning application.\n 166 | Please use the following password to log into your account with the username ${username}:\n 167 | ${tempPass}\n 168 | While you can continue using this password permenantly, we suggest you immediately visit your user page, go to your settings, 169 | and change it to a personalized password. That way your account's current password is not stored in plain text.\n 170 | As always, thank you for using PLANiT!\n\n 171 | Sincerely,\n 172 | Your PLANiT Team 173 | ` 174 | }, 175 | html: ({ username, name, tempPass }) => { 176 | return ` 177 |

    Hello ${name.first},

    178 |

    We have recieved a request to reset your password on PLANiT, the collaborative travel planning application.

    179 |

    Please use the following password to log into your account with the username ${username}:

    180 |
    ${tempPass}
    181 |

    While you can continue using this password permenantly, we suggest you immediately visit your user page, go to your settings, 182 | and change it to a personalized password. That way your account's current password is not stored in plain text.

    183 |

    As always, thank you for using PLANiT!

    184 |

    Sincerely,

    185 |
    186 |
    Your PLANiT Team
    187 | ` 188 | } 189 | } 190 | } 191 | 192 | module.exports = mailer; 193 | -------------------------------------------------------------------------------- /nodemailer/renderEmail.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | 3 | const TEAM_EMAIL_ADDRESS = 'teamplanitcartographers@gmail.com'; 4 | const PLANIT_URL = "https://travelplanit.herokuapp.com"; 5 | 6 | let messageOptions= { 7 | from: TEAM_EMAIL_ADDRESS, 8 | to: "zackdeacon347@gmail.com", 9 | subject: `${data.data.name} Trip`, 10 | text: "", 11 | html: ` 12 | ${data.data.name} 14 |

    15 | Here is the final itinerary for ${data.data.name}! We hope you have a fantastic trip and thanks for letting us help you Planit! 16 |

    17 | 18 |
    19 |

    Accomodations

    20 | 21 |
    22 |
    23 |

    Flights

    24 | 25 |
    26 |
    27 |

    Food

    28 | 29 |
    30 |
    31 |

    Entertainment

    32 | 33 |
    34 | 35 |
    36 | 37 |

    Make sure you say thanks to ${data.data.creatorId.name.first} for planning this great trip!

    38 | `} 39 | 40 | let transporter = nodemailer.createTransport({ 41 | service: 'gmail', 42 | auth: { 43 | user: 'teamplanitcartographers@gmail.com', 44 | pass: 'planitpassword1' 45 | } 46 | }); 47 | transporter.sendMail(messageOptions, (err, info)=> { 48 | if (err) { 49 | return console.log(err) 50 | } else { 51 | console.log("message sent: %s", info.message) 52 | } 53 | }); 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /nodemailer/txt_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackdeacon/planit-backend/77f3ad112e255cc001eb6a65e72758eb11145cae/nodemailer/txt_green.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "planit", 3 | "version": "1.0.0", 4 | "description": "PLANiT Express", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "watch": "nodemon server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "concurrently": "^4.1.0", 14 | "nodemon": "^1.18.7" 15 | }, 16 | "dependencies": { 17 | "bcrypt": "^5.0.0", 18 | "connect-mongo": "^3.2.0", 19 | "cors": "^2.8.5", 20 | "dotenv": "^8.2.0", 21 | "express": "^4.16.3", 22 | "express-session": "^1.17.1", 23 | "if-env": "^1.0.4", 24 | "mongoose": "^5.9.27", 25 | "multer": "^1.4.2", 26 | "multer-gridfs-storage": "^4.2.0", 27 | "nodemailer": "^6.4.11", 28 | "socket.io": "^2.3.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /seeds/seed.js: -------------------------------------------------------------------------------- 1 | const models = require("../models"); 2 | 3 | // This file empties the Books collection and inserts the books below 4 | 5 | const userSeed = [ 6 | { 7 | username: "zackdeacon", 8 | password: "password", 9 | email: "zdeacon@planit.com", 10 | name: { 11 | first: "Zack", 12 | last: "Deacon", 13 | }, 14 | }, 15 | { 16 | username: "derek-watson14", 17 | password: "password", 18 | email: "dwatson@planit.com", 19 | name: { 20 | first: "Derek", 21 | last: "Watson", 22 | }, 23 | }, 24 | { 25 | username: "nicoleremy95", 26 | password: "password", 27 | email: "nremy@planit.com", 28 | name: { 29 | first: "Nicole", 30 | last: "Remy", 31 | }, 32 | }, 33 | { 34 | username: "VinAVarghese", 35 | password: "password", 36 | email: "vvarghese@planit.com", 37 | name: { 38 | first: "Vincent", 39 | last: "Varghese", 40 | }, 41 | }, 42 | { 43 | username: "Brycetp11", 44 | password: "password", 45 | email: "bpingul@planit.com", 46 | name: { 47 | first: "Bryce", 48 | last: "Pingul", 49 | }, 50 | }, 51 | ]; 52 | 53 | const mapSeed = [ 54 | { 55 | name: "Team Vancouver Trip", 56 | creatorId: undefined, 57 | dates: { 58 | start: new Date("2021-01-20"), 59 | end: new Date("2021-01-28"), 60 | }, 61 | destinations: ["Vancouver", "Whistler"], 62 | }, 63 | { 64 | name: "Visit Cuba", 65 | creatorId: undefined, 66 | dates: { 67 | start: new Date("2021-03-06"), 68 | end: new Date("2021-03-20"), 69 | }, 70 | destinations: ["Cuba"], 71 | }, 72 | ]; 73 | 74 | const suggestionSeed = [ 75 | { 76 | userId: "", 77 | mapId: "", 78 | title: "AirBnb house", 79 | category: "Accomodation", 80 | description: "Cool AirBnb with a hottub in Whistler", 81 | link: "https://www.airbnb.com/rooms/16068259?s=67&unique_share_id=5951f1de-099c-4b12-a013-54df4c947520", 82 | cost: 175, 83 | } 84 | ] 85 | 86 | const chatSeed = [ 87 | { 88 | userId: "", 89 | mapId: "", 90 | message: "Wow, live chat! This is really cool :)", 91 | }, 92 | { 93 | userId: "", 94 | mapId: "", 95 | message: "I wonder if they made this using socket.io?", 96 | }, 97 | ] 98 | 99 | const potentialUserSeed = [ 100 | { 101 | email: "derek.watson92@gmail.com", 102 | invitedMapIds: [], 103 | } 104 | ] 105 | 106 | async function addUsers() { 107 | // Insert 5 users 108 | const userPromises = userSeed.map(async user => await models.User.create(user)); 109 | const userDocs = await Promise.all(userPromises); 110 | // console.log("User docs: ", userDocs); 111 | // Get user ids 112 | const userIds = userDocs.map(doc => doc._id); 113 | 114 | return { docs: userDocs, ids: userIds }; 115 | } 116 | 117 | async function addMaps(users) { 118 | // Add created user ids to maps 119 | mapSeed[0].creatorId = users.ids[0]; 120 | mapSeed[0].creator = users.docs[0].username; 121 | mapSeed[0].guests = users.docs.filter(user => user._id !== users.ids[0]).map(user => user.email); 122 | mapSeed[1].creatorId = users.ids[1]; 123 | mapSeed[1].creator = users.docs[1].username; 124 | 125 | 126 | // Add 2 maps 127 | const mapDocs = await models.Map.insertMany(mapSeed) 128 | // console.log("Map docs: ", mapDocs); 129 | 130 | // Get map ids 131 | const mapIds = mapDocs.map(doc => doc._id); 132 | 133 | // Update sample users with new created maps 134 | users.docs[0].createdMaps.push(mapIds[0]); 135 | users.docs[1].createdMaps.push(mapIds[1]); 136 | 137 | // Update other users with guest maps 138 | users.docs.forEach(user => { 139 | if (user._id !== users.docs[0]._id) { 140 | user.guestMaps.push(mapIds[0]); 141 | } 142 | if (user._id !== users.docs[1]._id) { 143 | user.invitations.push(mapIds[1]); 144 | } 145 | }) 146 | // Save all users after pushing associated maps 147 | users.docs.forEach(user => user.save()); 148 | 149 | // return map info for outside access 150 | return { docs: mapDocs, ids: mapIds }; 151 | } 152 | 153 | async function addSuggestion(users, maps) { 154 | // Add real ids into suggestion 155 | suggestionSeed[0].userId = users.ids[0]; 156 | suggestionSeed[0].mapId = maps.ids[0]; 157 | suggestionSeed[0].votes = [ 158 | { userId: users.ids[0], vote: true }, 159 | { userId: users.ids[1], vote: false }, 160 | { userId: users.ids[2], vote: true } 161 | ]; 162 | suggestionSeed[0].comments = [ 163 | { userId: users.ids[2], message: "Looks really nice!" }, 164 | { userId: users.ids[1], message: "Probably gonna cost way more with fees added :(" } 165 | ]; 166 | 167 | // Create suggestion doc 168 | const suggestionDoc = await models.Suggestion.create(suggestionSeed[0]); 169 | suggestionDoc.votes.push({ userId: users.ids[3], vote: false }); 170 | suggestionDoc.save(); 171 | // console.log("Suggestion doc: ", suggestionDoc); 172 | 173 | return suggestionDoc; 174 | } 175 | 176 | async function addChats(users, maps) { 177 | // Add generated id details to chat objects 178 | chatSeed[0].user = users.ids[4]; 179 | chatSeed[0].map = maps.ids[0]; 180 | 181 | chatSeed[1].user = users.ids[4]; 182 | chatSeed[1].map = maps.ids[0]; 183 | 184 | const chatDocs = await models.Chat.insertMany(chatSeed); 185 | // console.log("Chat docs: ", chatDocs); 186 | 187 | return chatDocs; 188 | } 189 | 190 | async function addPotentialUsers(maps) { 191 | potentialUserSeed[0].invitedMapIds.push(maps.ids[0]); 192 | const potentialUserDocs = await models.PotentialUser.create(potentialUserSeed[0]); 193 | // console.log("Potential User docs: ", potentialUserDocs); 194 | 195 | return potentialUserDocs; 196 | } 197 | 198 | async function seed() { 199 | const users = await addUsers(); 200 | const maps = await addMaps(users); 201 | const suggestion = await addSuggestion(users, maps); 202 | const chats = await addChats(users, maps); 203 | const potentialUsers = await addPotentialUsers(maps); 204 | }; 205 | 206 | module.exports = seed; 207 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const session = require("express-session"); 3 | const MongoStore = require('connect-mongo')(session); 4 | const cors = require("cors"); 5 | const mongoose = require("mongoose"); 6 | require('dotenv').config(); 7 | 8 | const models = require("./models"); 9 | const allRoutes = require("./controllers"); 10 | const seed = require("./seeds/seed"); 11 | 12 | const app = express(); 13 | const PORT = process.env.PORT || 8080; 14 | 15 | // Defining middleware 16 | app.use(express.urlencoded({ extended: true })); 17 | app.use(express.json()); 18 | 19 | // Serving static assets 20 | if (process.env.NODE_ENV === "production") { 21 | app.use(express.static("client/build")); 22 | } 23 | 24 | // Connect to MongoDB 25 | // Change boolean to true to reseed database on server start 26 | const reseedOnConnect = false; 27 | mongoose 28 | .connect(process.env.MONGODB_URI || "mongodb://localhost/plannit", { 29 | useNewUrlParser: true, 30 | useCreateIndex: true, 31 | useFindAndModify: false, 32 | useUnifiedTopology: true, 33 | }) 34 | .then(async () => { 35 | if (reseedOnConnect) { 36 | await Promise.all([ 37 | models.User.deleteMany({}), 38 | models.Map.deleteMany({}), 39 | models.Chat.deleteMany({}), 40 | models.Suggestion.deleteMany({}), 41 | models.PotentialUser.deleteMany({}), 42 | ]); 43 | seed(); 44 | } 45 | }); 46 | 47 | // CORS 48 | app.use( 49 | cors({ 50 | origin: ["https://travelplanit.herokuapp.com","http://localhost:3000", "https://planitserver.herokuapp.com/"], 51 | credentials: true 52 | }) 53 | ); 54 | 55 | //SESSION 56 | // for heroku deploy uncomment proxy, samesite and secure 57 | app.use( 58 | session({ 59 | secret: process.env.SESSIONSECRET, 60 | resave: false, 61 | saveUninitialized: false, 62 | proxy: true, 63 | store: new MongoStore({mongooseConnection: mongoose.connection}), 64 | cookie: { 65 | maxAge: 2 * 60 * 60 * 1000, 66 | sameSite: "none", 67 | secure: true, 68 | } 69 | }) 70 | ); 71 | 72 | // API routes 73 | app.use("/", allRoutes); 74 | 75 | let server = app.listen(PORT, () => { 76 | let io = require("socket.io").listen(server); 77 | console.log("connected") 78 | io.on("connection", (socket) => { 79 | socket.emit("your id", socket.id); 80 | socket.on("new message", (e) => { 81 | io.emit("update messages"); 82 | console.log("message sent", e) 83 | }); 84 | }); 85 | 86 | console.log( 87 | `🌎 ==> API server now listening on port ${PORT}! http://localhost:${PORT}` 88 | ); 89 | }); 90 | --------------------------------------------------------------------------------