MEAN stack project
- Demo: https://sportspartner.herokuapp.com/
- The info pages: https://sportspartner.herokuapp.com/about
- Repo link: https://github.com/PascalSun/sportpartner
- Group 29
- Team members: QIANG SUN(21804416) and XUE YU (20657462)
If you want to play basketball or football, you will need some people to team up;
If you want to start go to gym or learn to swim, you will need a guide;
If you want to run everyday and you cannot insist on, you will need a partner support each other;
So people can just register on our website about what they want and what they can do;
we will match them up as a list( also can be shown on the map) based on the information they provide.
- Create a Heroku account: https://www.heroku.com/
- Connect it with Github
- Deploy it with this repo (you can fork this one first)
- Wait for the outcome
- Demo: https://sportspartner.herokuapp.com/
- install nodejs and npm :https://nodejs.org/en/download/package-manager/
- clone the repo
git clone https://github.com/PascalSun/sportpartner
cd ./sportpartner
npm install
npm start
- open your web broswer: localhost://3000
sudo apt-get update
install nvmcurl https://raw.githubusercontent.com/creationix/nvm/v0.16.1/install.sh | sh
- install nodejs:
nvm install 6.10.2
- install npm:
sudo apt-get install npm
- install git:
sudo apt-get install git
git clone https://github.com/PascalSun/sportpartner
cd ./sportpartner
npm install
npm start
- want to keep it running, use something like forever
- login link: https://sportspartner.herokuapp.com/login
- login with {username:admin@admin.com,password:admin1}
- see match link: https://sportspartner.herokuapp.com/match?map=1
There are five variables here to match:
- sex{male,female};
- age{0~99};
- sports{basketball,football...};
- sport skill degree{0,10};
- address
To match, the user need to setup profile and preference first.
Then it will show the partners who have exactly the sex and sports kind the user want as a list.
And the order of the partners will be decided by difference degree, which combined by location distance, age difference, and skill difference:
diff = skilldiff+agediff/5+(location distance)x200
The formula can be adjusted via further research.
Use MEAN stack obviously
- cloud mongodb from mLab as database
- Nodejs and Express framework
- Angular and REST api used to allow user change the way they search the matches, and give a result immediately and frequently
- Use MVC
- Model for database interface
- Controller to do the function work
- Routes deal with router to controllers
- jade as our view engine
- Login functions: use passport.js to fullfill the feature, quite hard when you start to do things
- How to deal with address:
- find the exact address of the user input
- how to calculate the address distance
- geocode it into [lat,lng] and decode it to address Solutions:
- Choose to use google map api to autocomplete the user's address from client side
and check it whether legal or not from server side - use mongodb 2dsphere index and $near which can calculate and sort by distance between address (google distance matrix can also be a option, but will not as fast as this one)
- to use the mongodb distance feature, address need to be stored in format [lng,lat].
so we use google map api again to geocode address and decode address
- Show matches via google map We use google map maker to deal with the diff json generated by server side
- We have the feature to leave a message, and we want to send an email to the receiver to notice him about who and what message are left to him.
So we use nodemailer library together with mailgun(mail server provider) to send email - Above are the main problems we faced and choices we have made, some other bugs and issues we have made are solved during the development process
The whole development is basically Test Driven Development.
However, as totoally new learners to mean stack, we don't have the experience about how to write a test case before we even haven't known exactly what the features will like.
So during the process, every time we finish a function, we just test it by hand, to make sure every feature and function works, and every branch is covered.
And after each feature finished, we will test the whole feature.
And in the end, we do the unit and user test.
-
Unit Test: Use path coverage Strategy, and try to cover 100% branches and statement
- In fact, when I start to do the test, I start to realize that it is not a good choice which we just use the REST API structure with the match list function.
It will be much easier to test, if we use that. - Also, the dependency between different files makes the test much harder.
- At the same time, when we find a bug, It will take long time to fix and the process is quite struggling
- to test, just run:
npm test
, and the test result will show on coverage folder
- In fact, when I start to do the test, I start to realize that it is not a good choice which we just use the REST API structure with the match list function.
-
User Test: Black Box Test, which without knowing about the code backend.
I asked my girl friend and my friends to test all the features, and gave some feedback They indeed gave some advice:- the user should have a username, not the same as the email
- the user should have a head portrait, for the partners to know each other
- when the net speed is very low, sometime show error
- can not be used on a small screen phone, because can not find login button. (which have been fixed)
- why not just show the address, but need to click to see the address
- in firefox49.9, can not click the edit preference button
Also we use selenium IDE tool to generate some script files with different scenarios, to make sure every time we modify something, the whole system works properly.
- About connect-flash: http://mclspace.com/2015/12/03/nodejs-flash-note/
- Passport.js Document: http://passportjs.org/features
- Geo Base query via mongodb: https://docs.mongodb.com/manual/core/geospatial-indexes/#GeospatialIndexing-geoNearCommand
- Mongoose Document: http://mongoosejs.com/docs/guide.html
- Google Map Api: https://developers.google.com/maps/documentation/geocoding/start#header
- Google Map Console: https://console.developers.google.com/apis/dashboard?project=sportpartner-1495264627869&duration=PT1H
- Mongoose document: https://scotch.io/tutorials/using-mongoosejs-in-node-js-and-mongodb-applications
- node-geocoder: https://github.com/nchaulet/node-geocoder
- node-sendmail:
http://javascript.tutorialhorizon.com/2015/07/02/send-email-node-js-express/ - nodemailer: https://github.com/nodemailer/nodemailer
- Basic Views
- Set up mongodb
- local machine
- mLab (add in the end, with some test data)
- Register Functions
- Register/Login with passport.js
- Validate Password
- client side
- server side
- the same
- not empty
- Validate email format
- not empty
- already token
- Change Password
- view, client side check
- server side change the password
- After register, redirect to login page and keep the username and password info
- error handle
- login: how to customize the Unauthorized white page
- register
- Edit profile
- view,models,controller,route framework
- autocomplete address via google api
- transform address to coordinate, via client side
- validate it and store info to database
- Decode coordinate to address from server side
- User stories
- view profile
- edit profile
- when the user login the first time, then redirect to edit page directly
- Edit prefernece
- models,view,controller,router
- edit and view preference
- Communication:
- view basic profile of other user
- models stored visitor and hosts
- list who view you and who you have viewed
- Leave Message
- See who are contacting with you
- See who you have contacted
- Add email notification: Use mailgun altogether with nodemailer to send email to receiver about content and sender when some leave message
- Show matches
- Location Based Match
- Match all people, around, and show on the map
- Match people meet prefer, show as a list, not included the one not meet requirements
- On the map, the people meet the preference will be labeled with order, and those who not meet requirements will be labeled as O
- use rest API, and Angular, not need to change prefer data, show the results
- Graphic Match Visualization
- Location Based Match
- Instructions about how to launching on a cloud platform
- user data collection
- display match
- explain match algorithm
- describe what architecture used, choices made, difficulities faced
- About test / validation Strategy and results
- Introduction and references
- the number value problem
- the match problem
- add some data in the mlab
- List all the info as about page of the web
-
basic login function vs customize error login
// Post to login // router.post('/login', passport.authenticate('local', { failureFlash: 'Invalid username or password.' }), function(req,res) { // console.log(res); // res.render('index',{username:req.body.username,user:req.user}); // });
// Post to login: need to deal with error information router.post('/login', function(req, res, next) { passport.authenticate('local', function(err, user, info) { if (err) { console.log('1');res.render('login',{message:"Username or Password Wrong"}); } if (!user) { console.log('2');res.render('login',{message:info.message}); } req.logIn(user, function(err) { if (err) { res.render('login',{message:'Username and Password not match'}); } res.render('index',{username:req.body.username,user:user}); }); })(req, res,next); });
- the logIn function is very important to keep alive
-
Sometimes, we need to define the passport Strategy by ourself
// passport.use(new LocalStrategy( // function(username, password, done) { // /* see done being invoked with different paramters // according to different situations */ // Account.findOne({ username: username }, function (err, user) { // if (err) { console.log('wrong'); return done(err); } // if (!user) { console.log('now user'); return done(null, false); } // // if (!user.verifyPassword(password)) { console.log('now xx'); return done(null, false); } // return done(null, user); // }); // } // ));
-
Javascript function won't wait, they are not the same pace
function geocodeAddress() {
var geocoder = new google.maps.Geocoder();
var address = document.getElementById('address').value;
console.log(address);
geocoder.geocode({'address': address}, function(results, status) {
console.log(status)
if (status === 'OK') {
console.log(results[0].geometry.location.lat());
console.log(results[0].geometry.location.lng());
// document.getElementById('addresslat').setAttribute('value',results[0].geometry.location.lat());
// document.getElementById('addresslng').value = results[0].geometry.location.lng();
document.getElementById('addresslat').value = results[0].geometry.location.lat();
document.getElementById('addresslng').value = results[0].geometry.location.lng();
console.log(document.profile.addresslat.value);
if(document.profile.addresslat.value!==''){
document.profile.submit();
console.log('here');
return true;
}
else{
return false;
}
} else {
alert('Please ensure your address valid!');
return false;
}
});
alert('Are you sure to submit?')
return false; /// there must be a return false here
- The mongoose query callback is really disaster, need to find some solutions.