diff --git a/.gitignore b/.gitignore index 309f129f..85212dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ src/**/*.css build/* npm-debug.log -TODO \ No newline at end of file +TODO +mongo/ \ No newline at end of file diff --git a/routes/follow/follow.js b/routes/follow/follow.js index 90eb9d32..e15a3f3c 100644 --- a/routes/follow/follow.js +++ b/routes/follow/follow.js @@ -7,6 +7,8 @@ router.route("/:project_id").post(function(req, res) { const { _id: user_id } = req.user; const { project_id } = req.params; + + console.log('Follow/Unfollow Request', project_id, req.user._id); // Find existing project Project.findById(project_id, function(err, project) { @@ -18,7 +20,7 @@ router.route("/:project_id").post(function(req, res) { if (userStatus.status === 'following') { // User is already following. Unfollow. userStatus.remove(); - console.log('Unfollowed', project); + console.log('Unfollowed', project.title); } } else { // User not associated to project @@ -27,7 +29,7 @@ router.route("/:project_id").post(function(req, res) { _id: user_id, status: 'following' }); - console.log('Followed', project); + console.log('Followed', project.title); } project.save(function(err, update) { if (err) throw err; diff --git a/routes/users/users.js b/routes/users/users.js index a48ec942..7168a98f 100644 --- a/routes/users/users.js +++ b/routes/users/users.js @@ -6,6 +6,8 @@ const request = require('request'); const User = require('../../model/users'); const userHelpers = require('./userHelpers.js') +const Project = require('../../model/projects'); + // USER ROUTES router.route('/') @@ -45,7 +47,10 @@ router.route('/:user_id') .get(function(req, res) { User.findById(req.params.user_id, function(err, user) { if (err) { res.send(err); } - else { + else if (!user) { + console.log("User not found!"); + res.send("User not found!"); + } else { let user_filtered = { _id: user._id, username: user.username, @@ -54,7 +59,7 @@ router.route('/:user_id') skillset: user.skillset }; // Only if user is requesting own info, share email address. - if (req.user._id === req.params.user_id) { + if (req.user && req.user._id === req.params.user_id) { user_filtered.email = user.email; } // respond with full user data only if logged in. @@ -91,15 +96,62 @@ router.route('/:user_id') //delete method for removing a user from our database .delete(function(req, res) { - User.findById(req.user._id) - .exec(function (err, user) { - if (!user._id.equals(req.user._id)) return res.json({ message: 'Users do not match'}); - //selects the user by its ID, then removes it. - User.remove({ _id: req.params.user_id }, function(err, user) { - if (err) { res.send(err); } - return res.json({ message: 'User has been deleted' }) - }) - }) + console.log('Delete User request', req.params.user_id); + User.findById(req.params.user_id) + .exec(function (err, user) { + if (err) return res.send(err); + if (String(user._id) == String(req.user._id)) { + // user to be deleted is owned by the logged in user + // Find and remove owned projects + Project.deleteMany({ + users: { + $elemMatch: { + _id: user._id, + status: "owner" + } + } + }) + .exec(function (err, res) { + if (err) throw err; + console.log("Projects deleted", res.deletedCount); + }); + // also find followed projects and remove status + Project.find({ + users: { + $elemMatch: { + _id: user._id, + status: "following" + } + } + }) + .exec(function (err, projects) { + if (err) throw err; + if (projects) { + projects.forEach(function(project) { + //unfollow all followed projects + let status = project.users.id(user._id); + status.remove(); + project.save(function(err, update) { + if (err) throw err; + console.log("Unfollowed projects", update.title); + }); + }); + + } + }); + // Finally remove user account + console.log('User to be deleted', user.username); + user.remove(); + user.save(function(err, update) { + if (err) throw err; + }); + return res.redirect('/'); + } else { + // user to be deleted is not owned by the logged in user + console.log('logged in user ID does not match user to be deleted'); + return res.send('Cannot delete account not owned by the user'); + } + }) }); router.route('/contact/:user_id') diff --git a/src/components/atoms/Button.js b/src/components/atoms/Button.js index a27148e4..053b916a 100644 --- a/src/components/atoms/Button.js +++ b/src/components/atoms/Button.js @@ -9,7 +9,6 @@ import { withRouter } from 'react-router-dom'; class Button extends Component { handleClick = (e) => { if (this.props.onClick) { - console.log('button click', this.props.label); this.props.onClick(e); } if (this.props.redirect) { @@ -18,8 +17,6 @@ class Button extends Component { } render(){ const style = this.props.style || null; - - console.log("Button style: ", style); return( ) diff --git a/src/components/molecules/Nav.js b/src/components/molecules/Nav.js index f88df30c..07d47a8d 100644 --- a/src/components/molecules/Nav.js +++ b/src/components/molecules/Nav.js @@ -56,7 +56,6 @@ class Nav extends Component { } }; render() { - console.log("Nav", this.props); return
{(this.props.user && user._id === this.props.user._id) ? - ( +
+ ) : ( + + + + ); diff --git a/src/js/apiCalls.js b/src/js/apiCalls.js index bfa2d532..297373d4 100644 --- a/src/js/apiCalls.js +++ b/src/js/apiCalls.js @@ -10,6 +10,7 @@ const apiCall = { // Retrieve all projects from server getAllProjects(next) { // get projects from api + console.log('get all projects url', apiUrl + '/api/projects'); axios.get(apiUrl + '/api/projects') .then(res => { next({data: res.data}); @@ -115,6 +116,19 @@ const apiCall = { next({error: err}); throw err; }); + }, + + // delete user + deleteUser(id, next) { + console.log('deleteUser'); + axios.delete(`${apiUrl}/api/users/${id}`) + .then(res => { + next({data: res.data}); + }) + .catch(err => { + next({error: err}); + throw err; + }); } }; diff --git a/src/js/fakeApi.js b/src/js/fakeApi.js new file mode 100644 index 00000000..4537cd35 --- /dev/null +++ b/src/js/fakeApi.js @@ -0,0 +1,57 @@ +const fakeData = { + user: { + _id: "12345", + username: "fakeUser", + skillset: ["One", "Two", "Three"], + avatar: "https://picsum.photos/50" + }, + projects: [{"_id":"5a65fd8a3d77f50004decf51","repoUrl":"http://www.ideaswatch.com/startup-idea/fake-at-party-app","status":"I have the idea, but I haven't started coding it yet.","description":"This app creates background noises (e.g. traffic, loud music, people in a party chatting) when you are calling. This is useful when you're ex is calling, so you can pretend you're busy having fun!","owner":"5a6247110173f1000448b79d","title":"Noisr","__v":8,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"}],"img":["NA"],"stack":["Java","Swift"],"categories":["Mobile App"]},{"_id":"5a66116211a5c72408a19281","repoUrl":"NA","status":"It's just an idea","description":"Find coworkers in your area to commute with.","owner":"5a6247110173f1000448b79d","title":"CommuteWithMe","__v":12,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"},{"_id":"5a7b8c2b128e880004cb2dbf","status":"following"}],"img":[],"stack":["React Native"],"categories":["Mobile App"]},{"_id":"5a7b8d82128e880004cb2dc0","repoUrl":"https://github.com/chingu-coders/Voyage2-Turtles-02","status":"Currently complete but we are currently working on V2 which would include a faster DevTab and more features. (Created by Dan Nguyen, Jeff Bothe, Tyler Del Rosario)\nDevTab is a Chingu Project","description":"An awesome new-tab extension designed for Developers","owner":"","title":"DevTab","__v":10,"users":[{"_id":"5a7b8c2b128e880004cb2dbf","status":"owner"}],"img":[],"stack":["HTML","CSS","JS","jQuery","jQuery UI"],"categories":["New-Tab Extension"]},{"_id":"5a7b8eb9128e880004cb2dc1","repoUrl":"https://github.com/chingu-voyage3/bears-06","status":"BearBnB is a completed Chingu Project :D(Created by Vannya, Stephan, Lee)\n ","description":"An open-source clone of AirBnB.","title":"BearBnB","__v":11,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a6055120f25ffaa290471fd","status":"following"}],"img":[],"stack":["MERN Stack","Mongo","Express","ReactJS","Node"],"categories":["App Clone"]},{"_id":"5ad5b3a8cdc05a3a50119f28","repoUrl":"http://","status":"Stuck","description":"The most generic project ever","title":"Yet another project","__v":1,"users":[{"_id":"5a9a9139ab060929303bded4","status":"owner"},{"_id":"5af4874738973100045687d3","status":"following"}],"img":[],"stack":["Mern"],"categories":["Web app"]},{"_id":"5af4879838973100045687d4","repoUrl":"http","status":"begin","description":"ever project best","title":"bookface","__v":0,"users":[{"_id":"5af4874738973100045687d3","status":"owner"}],"img":[],"stack":[],"categories":["Web app"]},{"_id":"5a660fbb11a5c72408a1927f","repoUrl":"http://www.ideaswatch.com/startup-idea/rifflord","status":"Idea found on IdeasWatch - never started","description":"Find and connect with musicians around you and form your band. On RiffLord you can upload a sample of your music and browse through musicians living around your area.","title":"RiffLord","__v":2,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"},{"_id":"5b074a9f2a1ccc0004510e38","status":"following"}],"img":[],"stack":["TBD"],"categories":["Social Network"]},{"_id":"5a663627d1a0785a64e8b6e3","repoUrl":"http://github.com","status":"Just an idea. Looking for devs to build it!","description":"This is a social network for unsociable people. It will text you when someone you DON'T want to meet is nearby.","title":"UnSocial Network","__v":1,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"}],"img":["https://cdn.images.express.co.uk/img/dynamic/80/590x/Grumpy-Cat-on-bed-544409.jpg"],"stack":["HTML","CSS","JavaScript"],"categories":["Web App"]},{"_id":"5a660f1f11a5c72408a1927e","repoUrl":"https://bitbucket.org/AlexGherardelli/evergreen","status":"I got stuck because I could not figure out how to post things on behalf of users with Flask and the Twitter API. I would love to have a more expert developer helping me with this project!","description":"Evergreen is a social media management app that allows you to reschedule your best and most effective posts over and over again, to maximize engagement and exposure. For example, you can schedule a \"Happy New Year\" post and never worry ever again to post it again. Or you can resend the invitation to an event multiple times at different times, so that people in different time zones can see your post.","title":"Evergreen","__v":4,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"},{"_id":"5a6055120f25ffaa290471fd","status":"following"}],"img":[],"stack":["Flask (Python)","HTML","SCSS/SASS","JavaScript"],"categories":["Social media management"]},{"_id":"5a66109911a5c72408a19280","repoUrl":"https://bitbucket.org/AlexGherardelli/dickens","status":"I have the boilerplate, but I would like a team mate to develop it.","description":"I have read online that with the time people spend on social media, they could read 200 books a year. With this scary stat in mind, I thought it would be nice to build a browser extension that every time you connect to a social media like Facebook, it redirects you toward a short story that can be read in a few minutes. A more productive break indeed!","owner":"5a6247110173f1000448b79d","title":"Dickens","__v":14,"users":[{"_id":"5a6247110173f1000448b79d","status":"owner"},{"_id":"5a9bfa90b2631921458268d7","status":"following"},{"_id":"5acadeeffcadd708d6d029d6","status":"following"}],"img":["https://i2-prod.mirror.co.uk/incoming/article5418983.ece/ALTERNATES/s615/Charles-Dickens.jpg"],"stack":[],"categories":["Browser Extension"]}] +}; + +const fakeApi = { + // Retrieve all projects from server + getAllProjects(next) { + // get projects from api + console.log('FAKE get all projects url'); + next({data: fakeData.projects}); + }, + // Retrieve one project by ID + getProjectById(projects, projectId, next) { + console.log('FAKE get project by ID'); + next({data: fakeData.projects[0]}); + }, + // Get logged in user data from api and assign it to state + getCurrentUser(next) { + console.log('FAKE get currentUser'); + next({data: fakeData.user}); + }, + // get user data from api + getUserById(id, next) { + console.log('FAKE get user by ID'); + next({data: fakeData.user}); + }, + + // Updates user data + postUser(data, next) { + console.log('FAKE postUser'); + next({data: "FAKE postUser"}); + }, + + // creates new project or updates a project + postProject(data, next) { + console.log('FAKE postProject'); + next({data: "FAKE postProject"}); + }, + // delete project + deleteProject(data, next) { + console.log('FAKE deleteProject'); + next({data: "FAKE deleteProject"}); + }, + // delete user + deleteUser(id, next) { + console.log('FAKE deleteUser'); + next({data: "FAKE deleteUser Success"}); + } +}; + +export default fakeApi; \ No newline at end of file diff --git a/src/routes.js b/src/routes.js index 53dcc065..6008279e 100644 --- a/src/routes.js +++ b/src/routes.js @@ -9,6 +9,7 @@ import axios from 'axios'; // Import Javascript functions import getCookie from './js/getCookie'; import apiCall from './js/apiCalls'; +import fakeApi from './js/fakeApi'; // Actions import { setUser, logoutUser } from './actions/users.js'; @@ -36,9 +37,16 @@ import Dashboard from './components/molecules/Dashboard'; // Loads environment variables with dotenv require('dotenv').load(); +// Code below triggers fake auth mode for development +let api = apiCall; +if (process.env.REACT_APP_FAKE) { + console.log("***FAKE MODE***"); + api = fakeApi; +} + // Declare App component class App extends Component { - constructor(props) { + /*constructor(props) { super(props); // url is REACT_APP_APPURL if set, otherwise it's window.location.origin @@ -50,7 +58,7 @@ class App extends Component { this.state = { apiUrl: url } - } + }*/ // Once the app is mounted componentDidMount() { // load all projects @@ -74,9 +82,8 @@ class App extends Component { // fetch all projcets allProjects = () => { - apiCall.getAllProjects(res => { + api.getAllProjects(res => { if (res.error) {console.error(res.error)} - console.log('res.data', res.data); if (res.data) { this.setState({projects: res.data}); // update state to response data this.props.setProjects(res.data); // redux store @@ -86,8 +93,8 @@ class App extends Component { // get one project by ID getOneProject = (projectId, next) => { // apiCall expects a "null" if projects are not loaded yet - const projects = (this.state.projects.length > 0) ? this.state.projects : null; - apiCall.getProjectById(projects, projectId, res => { + const projects = (this.props.projects.length > 0) ? this.props.projects : null; + api.getProjectById(projects, projectId, res => { if (res.error) {console.error(res.error)} next(res.data); }); @@ -101,23 +108,23 @@ class App extends Component { // update project updateProject = (data) => { console.log('updateProject', data); - apiCall.postProject(data, this.allProjects()); + api.postProject(data, this.allProjects()); } // delete project deleteProject = (data) => { - apiCall.deleteProject(data, this.allProjects()); + api.deleteProject(data, this.allProjects()); } // get one user profile by ID getOneUser = (id, next) => { - apiCall.getUserById(id, res => { + api.getUserById(id, res => { if (res.error) {console.error(res.error)} next(res.data); }); } // updates user data postUser = (data) => { - apiCall.postUser(data, () => { + api.postUser(data, () => { this.allProjects(); this.setUser(); }); @@ -125,7 +132,7 @@ class App extends Component { // get user data from api and assign it to state setUser = () => { console.log('set user'); - apiCall.getCurrentUser(res => { + api.getCurrentUser(res => { console.log('set user response', res); if (res.error) {console.error(res.error)} if (res.data) { @@ -137,6 +144,19 @@ class App extends Component { } }); } + // delete user + deleteUser = (id) => { + console.log(`delete user ${id}`); + api.deleteUser(id, res => { + if (res.error) { + console.error(res.error); + window.location = '/'; + } else { + console.log(res.data); + window.location = '/'; + } + }) + } // logout the user by setting the app state.user as null logoutUser = () => { // logout user axios.get('/auth/logout').then(()=> { @@ -148,9 +168,6 @@ class App extends Component { window.location = '/'; // and redirects to the homepage }); } - - - // once project is unfollowed or followed, matches the db value without calling db updateUserProjects = (project_id) => { // copy state @@ -236,7 +253,8 @@ class App extends Component { {...{ user: this.props.user, projects: this.props.projects, - getOneUser: this.getOneUser + getOneUser: this.getOneUser, + onUserDelete: this.deleteUser }} /> } }/>