commit c4b358452c35bccd0f27f10dcbfc0d56cbf0915d Author: root Date: Sun Nov 27 20:58:22 2022 +0800 come on diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..93e01c1 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "public/libs" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d90498 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +public/libs +coverage +npm-debug.log diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a42b059 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: node_js +node_js: + - "5.5.0" +services: + - mongodb +before_install: + - npm install -g grunt-cli +install: + - npm install +before_script: + - bower install + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - ./node_modules/protractor/bin/webdriver-manager update --standalone + - npm run start-test & + - sleep 5 +script: + - grunt protractor_webdriver + - node_modules/.bin/protractor test/e2e/conf.js --browser=firefox + - npm run test-jasmine + - grunt karma diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..1d846e9 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,57 @@ +var path = require('path'); + +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + mongobin: { + options: { + host: 'localhost', + port: '27017', + db: 'makers-achievements-test' + } + }, + express: { + options: { + port: process.env.PORT || 8080, + hostname: 'localhost' + }, + test: { + options: { + server: path.resolve('./app') + }, + } + }, + karma: { + options: { + configFile: './test/front_end/karma.conf.js' + }, + run: { + } + }, + protractor: { + options: { + configFile: './test/e2e/conf.js', + keepAlive: true + }, + run: { + } + }, + protractor_webdriver: { + start: { + options: { + path: 'node_modules/protractor/bin/', + command: 'webdriver-manager start' + } + } + } + }); + + grunt.loadNpmTasks('grunt-mongo-bin'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-express'); + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-protractor-runner'); + grunt.loadNpmTasks('grunt-protractor-webdriver'); + grunt.registerTask('e2e', ['express:test', 'protractor_webdriver', 'protractor']); + +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..9995522 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# ![Alt text](http://i.imgur.com/9ptL8yf.png, 'Netstix') [![Build Status](https://travis-ci.org/michaellennox/netstix.svg?branch=master)](https://travis-ci.org/michaellennox/netstix) +An online achievement platform for tracking and displaying the brilliant things Makers students do. + +Initially inspired out of the Makers sticker system we have expanded the idea to be a fully interactive achievement system which allows users to keep track of any awesome things they do, create new achievements if they believe there is something other's should have a crack at and feel their competitive spirits rush as they race to the top of the leaderboard. + +![Alt text](http://i.imgur.com/7bfktU1.png, 'screenshot app') + +## So What's Here? + +Right now there's just the basics of the NetStix platform: + +* Local user authentication, sign up, sign in and sign out. +* The ability to create an achievement, view a list of achievements and view further detail on a specific achievement +* The ability to view a leaderboard of all users and their scores, ability to view a specific user and see everything they have done +* The ability to make submissions to an achievement and view proof from other locations + +## Technologies + +__API/Server__ + +* NodeJS +* ExpressJS +* MongoDB/Mongoose +* User Authentication with Passport +* Tested with jasmine-node and Frisby + +__Client__ + +* AngularJS +* Tested with Jasmine, Karma and Protractor + +## Installation Instructions + +You can try the app remotely: +>[https://netstix.herokuapp.com/](https://netstix.herokuapp.com/) + +or install it locally:
+Clone down from github and cd into the directory + +``` +$ git clone git@github.com:michaellennox/netstix.git +$ cd netstix +``` + +If you don't have MongoDB installed you will have to get it: + +``` +$ brew update +$ brew install mongodb +$ sudo mkdir -p /data/db +$ sudo chown /data/db +``` + +Install any dependencies then run the app + +``` +$ npm install +$ npm start +``` + +Visit `http://localhost:8080/#/` and enjoy your new, beautiful and awesome achievement system. + +## Future Improvements + +* BETA testing for achievements (think like codewars BETA tasks) +* Submission should be approved before applying to a user's profile +* Restrictions for different levels of users for eg voting BETA achievements through, approving submissions + + +## Contributions + +Feel free to get involved! Our waffleboard is available at https://waffle.io/michaellennox/netstix. + +## Contributors + +* [Michael Lennox](https://github.com/michaellennox) +* [Tom Bradley](https://github.com/trbradley) +* [Giamir Buoncristiani](https://github.com/giamir) +* [Andrew Htun](https://github.com/Htunny) diff --git a/app.js b/app.js new file mode 100644 index 0000000..d44a2fe --- /dev/null +++ b/app.js @@ -0,0 +1,80 @@ +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var mongoose = require('mongoose'); +var passport = require('passport'); +var LocalStrategy = require('passport-local').Strategy; + +var indexRouter = require('./app/routes/indexRouter'); +var achievementsRouter = require('./app/routes/achievementsRouter'); +var usersRouter = require('./app/routes/usersRouter'); +var sessionsRouter = require('./app/routes/sessionsRouter'); + +var app = express(); + +app.set('views', path.join(__dirname, 'app/views')); +app.set('view engine', 'jade'); + +app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, '/public'))); +app.use(require('express-session')({ + secret: 'keyboard cat', + resave: false, + saveUninitialized: false +})); +app.use(passport.initialize()); +app.use(passport.session()); + +// routing config +app.use('/', indexRouter); +app.use('/achievements', achievementsRouter); +app.use('/users', usersRouter); +app.use('/sessions', sessionsRouter); + +// passport config +var User = require('./app/models/user'); +passport.use(new LocalStrategy(User.authenticate())); +passport.serializeUser(User.serializeUser()); +passport.deserializeUser(User.deserializeUser()); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/app/controllers/achievementsController.js b/app/controllers/achievementsController.js new file mode 100644 index 0000000..601e6b3 --- /dev/null +++ b/app/controllers/achievementsController.js @@ -0,0 +1,45 @@ +var Achievement = require('../models/achievement'); + +var controller = {}; + +controller.list = function(req, res) { + Achievement.find(function(err, achievements) { + if(err) { + res.send(err); + } + res.json(achievements); + }); +}; + +controller.create = function(req, res) { + var achievement = new Achievement(); + achievement.title = req.body.title; + achievement.criteria = req.body.criteria; + achievement.points = req.body.points; + achievement.badgeLink = req.body.badgeLink; + achievement.challengeRepo = req.body.challengeRepo; + achievement.save(function(err) { + if(err) { + res.send(err); + } + res.json({ message: 'Achievement created!'}); + }); +}; + +controller.read = function(req, res) { + Achievement.findById(req.params.id) + .populate({ + path: 'submissions', + populate: { + path: 'user' + } + }) + .exec(function(err, achievement) { + if(err) { + res.send(err); + } + res.json(achievement); + }); +}; + +module.exports = controller; diff --git a/app/controllers/sessionsController.js b/app/controllers/sessionsController.js new file mode 100644 index 0000000..ac371df --- /dev/null +++ b/app/controllers/sessionsController.js @@ -0,0 +1,28 @@ +var passport = require('passport'); +var User = require('../models/user'); + +var controller = {}; + +controller.create = function(req, res) { + passport.authenticate('local', function(err, user, info) { + if(err) { + return res.status(500).json({ err: err }); + } + if(!user) { + return res.status(401).json({ err: info }); + } + req.logIn(user, function(err) { + if(err) { + return res.status(500).json({ err: 'Could not log in user' }); + } + res.status(200).json({ status: 'Login successful!', user: user }); + }); + })(req, res); +}; + +controller.destroy = function(req, res) { + req.logout(); + res.status(200).json({ status: 'Signed out successfully!' }); +}; + +module.exports = controller; diff --git a/app/controllers/submissionsController.js b/app/controllers/submissionsController.js new file mode 100644 index 0000000..d39d7f5 --- /dev/null +++ b/app/controllers/submissionsController.js @@ -0,0 +1,45 @@ +var Submission = require('../models/submission'); +var User = require('../models/user'); +var Achievement = require('../models/achievement'); + +var controller = {}; + +controller.create = function(req, res) { + var submission = new Submission(); + submission.link = req.body.link; + submission.comment = req.body.comment; + + Achievement.findById(req.params.id, function(err, achievement) { + if(err) { + res.send(err); + } + submission.achievement = achievement.id; + achievement.submissions.push(submission.id); + achievement.save(function(err) { + if(err) { + res.send(err); + } + User.findById(req.user._id, function(err, user) { + if(err) { + res.send(err); + } + user.score += achievement.points; + submission.user = user.id; + user.submissions.push(submission.id); + user.save(function(err) { + if(err) { + res.send(err); + } + submission.save(function(err) { + if(err) { + res.send(err); + } + res.json({ message: 'Submission created!' }); + }); + }); + }); + }); + }); +}; + +module.exports = controller; diff --git a/app/controllers/usersController.js b/app/controllers/usersController.js new file mode 100644 index 0000000..4f9eb0e --- /dev/null +++ b/app/controllers/usersController.js @@ -0,0 +1,47 @@ +var passport = require('passport'); +var User = require('../models/user'); + +var controller = {}; + +controller.list = function(req, res) { + User.find(function(err, users) { + if(err) { + res.send(err); + } + res.json(users); + }); +}; + +controller.create = function(req, res) { + var newUser = new User({ + username: req.body.username + }); + User.register( + newUser, req.body.password, function(err, account) { + if(err) { + return res.status(500).json({ err: err }); + } + passport.authenticate('local')(req, res, function() { + return res.status(200).json({ status: 'Registration successful!', user: newUser }); + }); + } + ); +}; + +controller.read = function(req, res) { + User.findById(req.params.id) + .populate({ + path: 'submissions', + populate: { + path: 'achievement' + } + }) + .exec(function(err, user) { + if(err) { + res.send(err); + } + res.json(user); + }); +}; + +module.exports = controller; diff --git a/app/models/achievement.js b/app/models/achievement.js new file mode 100644 index 0000000..f853316 --- /dev/null +++ b/app/models/achievement.js @@ -0,0 +1,14 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.Types.ObjectId; + +var achievementSchema = new Schema({ + title : String, + criteria : String, + challengeRepo : String, + points : { type: Number, default: 0}, + badgeLink : { type: String, default: '/images/badges/nobadge.png' }, + submissions : [{ type: ObjectId, ref: 'Submission' }] +}); + +module.exports = mongoose.model('Achievement', achievementSchema); diff --git a/app/models/submission.js b/app/models/submission.js new file mode 100644 index 0000000..9c321cc --- /dev/null +++ b/app/models/submission.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.Types.ObjectId; + +var submissionSchema = new Schema({ + link : String, + comment : String, + achievement : { type: ObjectId, ref: 'Achievement' }, + user : { type: ObjectId, ref: 'User' } +}); + +module.exports = mongoose.model('Submission', submissionSchema); diff --git a/app/models/user.js b/app/models/user.js new file mode 100644 index 0000000..c87f06e --- /dev/null +++ b/app/models/user.js @@ -0,0 +1,16 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.Types.ObjectId; + +var passportLocalMongoose = require('passport-local-mongoose'); + +var userSchema = new Schema({ + username : String, + password : String, + score : { type: Number, default: 0 }, + submissions : [{ type: ObjectId, ref: 'Submission' }] +}); + +userSchema.plugin(passportLocalMongoose); + +module.exports = mongoose.model('User', userSchema); diff --git a/app/routes/achievementsRouter.js b/app/routes/achievementsRouter.js new file mode 100644 index 0000000..628199e --- /dev/null +++ b/app/routes/achievementsRouter.js @@ -0,0 +1,12 @@ +var express = require('express'); +var router = express.Router(); +var achievementsController = require('../controllers/achievementsController'); + +router.route('/').get(achievementsController.list).post(achievementsController.create); + +router.route('/:id').get(achievementsController.read); + +var submissionsRouter = require('./submissionsRouter'); +router.use('/:id/submissions', submissionsRouter); + +module.exports = router; diff --git a/app/routes/indexRouter.js b/app/routes/indexRouter.js new file mode 100644 index 0000000..b3a064d --- /dev/null +++ b/app/routes/indexRouter.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); +var path = require('path'); + +router.get('/', function(req, res) { + res.sendFile(path.join(__dirname, '../../public/views', 'index.html')); +}); + +module.exports = router; diff --git a/app/routes/sessionsRouter.js b/app/routes/sessionsRouter.js new file mode 100644 index 0000000..72c7f46 --- /dev/null +++ b/app/routes/sessionsRouter.js @@ -0,0 +1,7 @@ +var express = require('express'); +var router = express.Router(); +var sessionsController = require('../controllers/sessionsController'); + +router.route('/').post(sessionsController.create).delete(sessionsController.destroy); + +module.exports = router; diff --git a/app/routes/submissionsRouter.js b/app/routes/submissionsRouter.js new file mode 100644 index 0000000..5883565 --- /dev/null +++ b/app/routes/submissionsRouter.js @@ -0,0 +1,8 @@ +var express = require('express'); +var router = express.Router({ mergeParams: true }); + +var submissionsController = require('../controllers/submissionsController'); + +router.route('/').post(submissionsController.create); + +module.exports = router; diff --git a/app/routes/usersRouter.js b/app/routes/usersRouter.js new file mode 100644 index 0000000..7e1cdba --- /dev/null +++ b/app/routes/usersRouter.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); +var usersController = require('../controllers/usersController'); + +router.route('/').post(usersController.create).get(usersController.list); + +router.route('/:id').get(usersController.read); + +module.exports = router; diff --git a/app/views/error.jade b/app/views/error.jade new file mode 100644 index 0000000..51ec12c --- /dev/null +++ b/app/views/error.jade @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/app/views/layout.jade b/app/views/layout.jade new file mode 100644 index 0000000..3d1cf82 --- /dev/null +++ b/app/views/layout.jade @@ -0,0 +1,5 @@ +doctype html +html + head + body + block content diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..9b730f3 --- /dev/null +++ b/bower.json @@ -0,0 +1,31 @@ +{ + "name": "netstix", + "description": "achievement application", + "main": "", + "authors": [ + "michaellennox", + "trbradley", + "htunny", + "giamir" + ], + "license": "MIT", + "moduleType": [], + "homepage": "", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "jquery": "^2.2.0", + "bootstrap": "^3.3.6", + "angular": "^1.4.9", + "angular-resource": "^1.4.9", + "angular-route": "^1.4.9", + "angular-mocks": "^1.4.9", + "angular-bootstrap": "~1.1.2", + "angular-loading-bar": "^0.8.0" + } +} diff --git a/config/env/dev b/config/env/dev new file mode 100644 index 0000000..ae0d4ac --- /dev/null +++ b/config/env/dev @@ -0,0 +1,99 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../../app'); +var debug = require('debug')('makers-achievements:server'); +var http = require('http'); +var mongoose = require('mongoose'); + +/** + * Connect to MongoDB + */ + +mongoose.connect('mongodb://localhost/makers-achievements-development', function(err, res) { + if(err) { + console.log('Error connecting to the database. ' + err); + } else { + console.log('Connected to Database: ' + 'mongodb://localhost/makers-achievements-development'); + } +}); + +var port = normalizePort(process.env.PORT || '8080'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/config/env/test b/config/env/test new file mode 100644 index 0000000..ab9edac --- /dev/null +++ b/config/env/test @@ -0,0 +1,103 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../../app'); +var debug = require('debug')('makers-achievements:server'); +var http = require('http'); +var mongoose = require('mongoose'); + +/** + * Connect to MongoDB + */ + +mongoose.connect('mongodb://localhost/makers-achievements-test', function(err, res) { + if(err) { + console.log('Error connecting to the database. ' + err); + } else { + console.log('Connected to Database: ' + 'mongodb://localhost/makers-achievements-test'); + } +}); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '8080'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/config/env/www b/config/env/www new file mode 100644 index 0000000..59a791f --- /dev/null +++ b/config/env/www @@ -0,0 +1,99 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../../app'); +var debug = require('debug')('makers-achievements:server'); +var http = require('http'); +var mongoose = require('mongoose'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '8080'); +app.set('port', port); + +mongoose.connect(process.env.MONGOLAB_URI, function(err, res) { + if(err) { + console.log('Error connecting to the database. ' + err); + } else { + console.log('Connected to Database: ' + process.env.MONGOLAB_URI); + } +}); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5575fbc --- /dev/null +++ b/package.json @@ -0,0 +1,71 @@ +{ + "name": "netstix", + "version": "0.0.0", + "description": "achievement application", + "scripts": { + "start": "node ./config/env/www", + "start-dev": "nodemon --debug ./config/env/dev", + "start-test": "nodemon --debug ./config/env/test", + "test": "npm run test-protractor && npm run test-karma && npm run test-jasmine", + "update-webdriver": "./node_modules/protractor/bin/webdriver-manager update --standalone --chrome", + "test-protractor": "npm run update-webdriver && protractor test/e2e/conf.js", + "test-karma": "grunt karma", + "test-jasmine": "npm run drop-test-db; jasmine-node test/server/controllers/userAuthSpec.js; jasmine-node test/server/controllers/achievementsSpec.js; jasmine-node test/server/controllers/submissionsSpec.js; npm run drop-test-db", + "drop-test-db": "mongo makers-achievements-test --eval 'db.dropDatabase()'", + "postinstall": "bower install" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/michaellennox/netstix.git" + }, + "author": "michaellennox, trbradley, htunny, giamir", + "license": "MIT", + "bugs": { + "url": "https://github.com/michaellennox/netstix/issues" + }, + "homepage": "https://github.com/michaellennox/netstix#readme", + "dependencies": { + "body-parser": "^1.14.2", + "bower": "^1.7.7", + "cookie-parser": "^1.4.1", + "debug": "^2.2.0", + "express": "^4.13.4", + "express-session": "^1.13.0", + "grunt": "^0.4.5", + "jade": "^1.11.0", + "kerberos": "0.0.18", + "mongoose": "^4.3.7", + "mongoose-autopopulate": "^0.4.0", + "morgan": "^1.6.1", + "passport": "^0.3.2", + "passport-local": "^1.0.0", + "passport-local-mongoose": "^4.0.0", + "serve-favicon": "^2.3.0" + }, + "devDependencies": { + "frisby": "^0.8.5", + "grunt-contrib-watch": "^0.6.1", + "grunt-express": "^1.4.1", + "grunt-express-server": "^0.5.1", + "grunt-karma": "^0.12.1", + "grunt-mongo-bin": "^0.1.0", + "grunt-parallel": "^0.4.1", + "grunt-protractor-runner": "^3.0.0", + "grunt-protractor-webdriver": "^0.2.5", + "jasmine-core": "^2.4.1", + "jasmine-node": "^1.14.5", + "jasmine-spec-reporter": "^2.4.0", + "karma": "^0.13.19", + "karma-chrome-launcher": "^0.2.2", + "karma-coverage": "^0.5.3", + "karma-jasmine": "^0.3.7", + "karma-phantomjs-launcher": "^1.0.0", + "karma-spec-reporter": "0.0.23", + "nodemon": "^1.8.1", + "phantomjs": "^2.1.3", + "phantomjs-prebuilt": "^2.1.3", + "protractor": "^3.0.0", + "protractor-http-mock": "^0.2.1", + "webdriver-manager": "^8.0.0" + } +} diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..0208ed8 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,302 @@ +/* universal */ + +@media (min-width: 1200px) { + .container{ + max-width: 800px; + } +} + +html, body { + background: #fafafa; + color: #4b4f54; + font-family: 'proxima-nova', 'Helvetica Neue', Arial, Helvetica, sans-serif; + font-size: 14px; + height: 100%; +} + +body.container { + padding: 0; +} + +a { + color: #125688; + font-weight: 600; +} + +a:hover{ + text-decoration: none; + color: #4b4f54; +} + +.form{ + min-height: 500px; + margin-bottom: 50px; +} +.form label{ + font-weight: normal; +} + +h1{ + display: inline-block; + margin: 0; + color: #4b4f54; + font-size: 36px; +} + +header{ + margin: 0 0 30px 0; + padding: 0 0 10px 0; + border-bottom: 2px solid #4b4f54; +} + +header b{ + font-size: 16px; + padding: 10px 10px 10px 0; +} + +header a i{ + font-size: 20px; + padding-right: 10px; +} + +header a{ + padding: 10px 10px 10px 0; +} + +form h2{ + margin-bottom: 40px; +} + +input[type="text"], +input[type="password"], +input[type="url"], +input[type="number"], +textarea { + outline: none; + box-shadow:none !important; + border: none; +} + +input.form-control, textarea{ + background: #fafafa; + margin-bottom: 20px; + border-radius: 0; + border-bottom: 1px solid #ccc; +} + +button[type='submit']{ + background: #e9e9e9; + margin: 20px 0 30px; + border: none; + border-radius: 0; + color: #4b4f54; + font-weight: 600; +} + +button[type='submit']:hover{ + background: #ccc; + border-radius: 0; + color: #4b4f54; +} + +input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 500px #fafafa inset !important; +} + +.alert{ + font-size: 10px; + background: #125688; + color: white; + text-align: center; + display: block; + text-transform: uppercase; + margin: 0 auto; +} + +p.alert{ + margin: 0; +} + + +/* navbar */ + +nav { + width: 100%; + height: 70px; + background: white; + border-radius: 0; + position: absolute; + top: 0; + left: 0; + border-bottom: 1px solid #edeeee; +} + +.navbar{ + margin-bottom: 0px; +} + +.navbar-toggle:before{ + content:"\e055";; + font-family:"Glyphicons Halflings"; + line-height:1; + margin:10px; + display:inline-block; +} +.navbar-toggle{ + margin-bottom: -16px; +} + +.nav > li{ + background-color: white; + padding-left: 10px; +} + +.nav > li > a{ + font-size:14px; + padding: 24px 7px; +} +.nav > li > a:hover, .nav > li > a:focus, .nav > li > a:visited{ + background: white; +} +.navbar-brand { + background: url('../images/netstix.png') no-repeat center center; + display:block; + height:29px; + text-indent:-9999px; + width:104px; + margin-top: 21px; + margin-left: 10px; +} +.nav .open > a, .nav .open > a:hover, .nav .open > a:focus{ + background-color: white; + color: #125688; +} +.dropdown-menu > li > a, .dropdown-menu > li > button{ + background: white; + color: #125688; + font-weight: 600; +} +.dropdown-menu > li > a:hover, .dropdown-menu > li > button:hover{ + text-decoration: none; + background: white; + color: #4b4f54; +} +.navbar-right .dropdown-menu{ + text-align: center; + left: -50px +} + +/* content */ + +.content{ + min-height: 100%; +} + +.content > div{ + margin-top: 120px; + padding: 30px 50px; + padding-bottom: 80px; +} + +.description{ + margin-bottom: 120px; + font-size: 20px; +} +.description figure{ + padding-top: 25px; +} + +.description .criteria h3{ + margin: 0 0 15px; + padding: 0 0 5px; + border-bottom: 2px solid #4b4f54; +} + +li.list-group-item{ + background: transparent; + border:0; + padding:25px; + border-bottom: 1px solid #e0e0e0; +} + +li.list-group-item > i{ + font-size: 20px; + padding-right: 25px; +} + +li.list-group-item > img{ + margin-right: 25px; +} + +li.list-group-item .pull-right{ + padding-top: 10px; +} + + +/* footer */ + +footer { + padding: 10px; + height: 80px; + text-align: center; + font-size: 12px; + position: relative; + margin-top: -80px; + clear:both; +} + + +/* mobile */ + +@media (max-width: 767px){ + body{ + text-align: center; + } + h1{ + font-size: 24px; + } + h2{ + font-size: 20px; + } + h3{ + font-size: 18px; + } + header{ + margin-bottom: 20px; + } + .achievements li > a, .user li > a { + display: block; + } + .achievements ul > li > img, .user ul > li > img { + margin: 10px auto; + } + + figure img.pull-right{ + width: 100px; + height: 100px; + display: block; + margin: 0 auto; + float: none !important; + } + .nav{ + position: absolute; + width: 100%; + z-index: 9999; + background: white; + padding-bottom: 20px; + } + .nav > li > a { + padding: 7px 10px 7px 0; + } + .nav > li > button{ + color: #125688; + font-weight: 600; + } + .content > div{ + margin-top: 22px; + } + .description{ + margin-bottom: 80px; + font-size: 16px; + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..f565f90 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/badges/Angel.png b/public/images/badges/Angel.png new file mode 100644 index 0000000..e36830d Binary files /dev/null and b/public/images/badges/Angel.png differ diff --git a/public/images/badges/Angel_thumb.jpg b/public/images/badges/Angel_thumb.jpg new file mode 100644 index 0000000..96dd67b Binary files /dev/null and b/public/images/badges/Angel_thumb.jpg differ diff --git a/public/images/badges/Architect_thumb.jpg b/public/images/badges/Architect_thumb.jpg new file mode 100644 index 0000000..8fb2e13 Binary files /dev/null and b/public/images/badges/Architect_thumb.jpg differ diff --git a/public/images/badges/Bolt.png b/public/images/badges/Bolt.png new file mode 100644 index 0000000..be272ae Binary files /dev/null and b/public/images/badges/Bolt.png differ diff --git a/public/images/badges/Bolt_thumb.jpg b/public/images/badges/Bolt_thumb.jpg new file mode 100644 index 0000000..7acf153 Binary files /dev/null and b/public/images/badges/Bolt_thumb.jpg differ diff --git a/public/images/badges/Data.png b/public/images/badges/Data.png new file mode 100644 index 0000000..2fcc28c Binary files /dev/null and b/public/images/badges/Data.png differ diff --git a/public/images/badges/Data_thumb.jpg b/public/images/badges/Data_thumb.jpg new file mode 100644 index 0000000..075d141 Binary files /dev/null and b/public/images/badges/Data_thumb.jpg differ diff --git a/public/images/badges/Frontman.png b/public/images/badges/Frontman.png new file mode 100644 index 0000000..643d565 Binary files /dev/null and b/public/images/badges/Frontman.png differ diff --git a/public/images/badges/Frontman_thumb.jpg b/public/images/badges/Frontman_thumb.jpg new file mode 100644 index 0000000..356a386 Binary files /dev/null and b/public/images/badges/Frontman_thumb.jpg differ diff --git a/public/images/badges/Machinist.png b/public/images/badges/Machinist.png new file mode 100644 index 0000000..4a04faf Binary files /dev/null and b/public/images/badges/Machinist.png differ diff --git a/public/images/badges/Machinist_thumb.jpg b/public/images/badges/Machinist_thumb.jpg new file mode 100644 index 0000000..a7fd4a4 Binary files /dev/null and b/public/images/badges/Machinist_thumb.jpg differ diff --git a/public/images/badges/Octocat.png b/public/images/badges/Octocat.png new file mode 100644 index 0000000..7d114a5 Binary files /dev/null and b/public/images/badges/Octocat.png differ diff --git a/public/images/badges/Octocat_thumb.jpg b/public/images/badges/Octocat_thumb.jpg new file mode 100644 index 0000000..98037f8 Binary files /dev/null and b/public/images/badges/Octocat_thumb.jpg differ diff --git a/public/images/badges/Polyglot.png b/public/images/badges/Polyglot.png new file mode 100644 index 0000000..c2b5446 Binary files /dev/null and b/public/images/badges/Polyglot.png differ diff --git a/public/images/badges/Polyglot_thumb.jpg b/public/images/badges/Polyglot_thumb.jpg new file mode 100644 index 0000000..aad2abf Binary files /dev/null and b/public/images/badges/Polyglot_thumb.jpg differ diff --git a/public/images/badges/Ronin.png b/public/images/badges/Ronin.png new file mode 100644 index 0000000..8511743 Binary files /dev/null and b/public/images/badges/Ronin.png differ diff --git a/public/images/badges/Ronin_thumb.jpg b/public/images/badges/Ronin_thumb.jpg new file mode 100644 index 0000000..0640f2c Binary files /dev/null and b/public/images/badges/Ronin_thumb.jpg differ diff --git a/public/images/badges/Rubyist.png b/public/images/badges/Rubyist.png new file mode 100644 index 0000000..a788cc5 Binary files /dev/null and b/public/images/badges/Rubyist.png differ diff --git a/public/images/badges/Rubyist_thumb.jpg b/public/images/badges/Rubyist_thumb.jpg new file mode 100644 index 0000000..9da9336 Binary files /dev/null and b/public/images/badges/Rubyist_thumb.jpg differ diff --git a/public/images/badges/Sentry.png b/public/images/badges/Sentry.png new file mode 100644 index 0000000..cb2d63a Binary files /dev/null and b/public/images/badges/Sentry.png differ diff --git a/public/images/badges/Sentry_thumb.jpg b/public/images/badges/Sentry_thumb.jpg new file mode 100644 index 0000000..46036ee Binary files /dev/null and b/public/images/badges/Sentry_thumb.jpg differ diff --git a/public/images/badges/Unixoid.png b/public/images/badges/Unixoid.png new file mode 100644 index 0000000..9cb6c47 Binary files /dev/null and b/public/images/badges/Unixoid.png differ diff --git a/public/images/badges/Unixoid_thumb.jpg b/public/images/badges/Unixoid_thumb.jpg new file mode 100644 index 0000000..e89e246 Binary files /dev/null and b/public/images/badges/Unixoid_thumb.jpg differ diff --git a/public/images/badges/architect.png b/public/images/badges/architect.png new file mode 100644 index 0000000..684aa01 Binary files /dev/null and b/public/images/badges/architect.png differ diff --git a/public/images/badges/dev-ops-logo.png b/public/images/badges/dev-ops-logo.png new file mode 100644 index 0000000..b3ac22a Binary files /dev/null and b/public/images/badges/dev-ops-logo.png differ diff --git a/public/images/badges/dev-ops-logo_thumb.jpg b/public/images/badges/dev-ops-logo_thumb.jpg new file mode 100644 index 0000000..8a05054 Binary files /dev/null and b/public/images/badges/dev-ops-logo_thumb.jpg differ diff --git a/public/images/badges/nobadge.png b/public/images/badges/nobadge.png new file mode 100644 index 0000000..49b1986 Binary files /dev/null and b/public/images/badges/nobadge.png differ diff --git a/public/images/badges/open-source-logo.png b/public/images/badges/open-source-logo.png new file mode 100644 index 0000000..89736a4 Binary files /dev/null and b/public/images/badges/open-source-logo.png differ diff --git a/public/images/badges/open-source-logo_thumb.jpg b/public/images/badges/open-source-logo_thumb.jpg new file mode 100644 index 0000000..fc7f265 Binary files /dev/null and b/public/images/badges/open-source-logo_thumb.jpg differ diff --git a/public/images/netstix.png b/public/images/netstix.png new file mode 100644 index 0000000..36f8c8f Binary files /dev/null and b/public/images/netstix.png differ diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..56f3853 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,37 @@ +var netstix = angular.module('Netstix', [ + 'ngResource', + 'ngRoute', + 'ui.bootstrap', + 'angular-loading-bar' + ]); + +netstix.config(['$routeProvider', + function($routeProvider) { + $routeProvider + .when('/', { + templateUrl: '../views/partials/users/leaderboard.html' + }) + .when('/achievements/', { + templateUrl: '../views/partials/achievements/index.html' + }) + .when('/achievements/new', { + templateUrl: '../views/partials/achievements/new.html' + }) + .when('/achievements/:id', { + templateUrl: '../views/partials/achievements/achievement.html' + }) + .when('/login', { + templateUrl: '../views/partials/users/login.html'}) + .when('/register', { + templateUrl: '../views/partials/users/register.html'}) + .when('/users/:id', { + templateUrl: '../views/partials/users/user.html' + }) + .when('/achievements/:id/submissions/new', { + templateUrl: '../views/partials/submissions/new.html' + }) + .otherwise({ + redirectTo: '/' + }); + } +]); diff --git a/public/js/controllers/achievementController.js b/public/js/controllers/achievementController.js new file mode 100644 index 0000000..1daf410 --- /dev/null +++ b/public/js/controllers/achievementController.js @@ -0,0 +1,17 @@ +netstix.controller('AchievementController', ['AchievementsResource', 'UserAuth', '$routeParams', function(AchievementsResource, UserAuth, $routeParams) { + var self = this; + self.id = $routeParams.id; + + self.init = function() { + AchievementsResource.getAchievement(self.id) + .then(function(response) { + self.achievement = response.data; + }); + }; + + self.isLoggedIn = function() { + return UserAuth.isLoggedIn(); + }; + + self.init(); +}]); diff --git a/public/js/controllers/achievementsController.js b/public/js/controllers/achievementsController.js new file mode 100644 index 0000000..e08930c --- /dev/null +++ b/public/js/controllers/achievementsController.js @@ -0,0 +1,16 @@ +netstix.controller('AchievementsController', ['AchievementsResource', 'UserAuth', function(AchievementsResource, UserAuth) { + var self = this; + + self.init = function() { + AchievementsResource.getAchievements() + .then(function(response) { + self.achievements = response.data; + }); + }; + + self.isLoggedIn = function() { + return UserAuth.isLoggedIn(); + }; + + self.init(); +}]); diff --git a/public/js/controllers/loginController.js b/public/js/controllers/loginController.js new file mode 100644 index 0000000..46cb47d --- /dev/null +++ b/public/js/controllers/loginController.js @@ -0,0 +1,21 @@ +netstix.controller('LoginController', ['UserAuth', '$window', '$scope', function(UserAuth, $window, $scope) { + var self = this; + + self.login = function() { + $scope.error = false; + $scope.disabled = true; + + UserAuth.login($scope.loginForm.username, $scope.loginForm.password) + .then(function () { + $window.location.href ='/#/achievements'; + $scope.disabled = false; + $scope.loginForm = {}; + }) + .catch(function () { + $scope.error = true; + $scope.errorMessage = "Invalid username and/or password"; + $scope.disabled = false; + $scope.loginForm = {}; + }); + }; +}]); diff --git a/public/js/controllers/logoutController.js b/public/js/controllers/logoutController.js new file mode 100644 index 0000000..6673920 --- /dev/null +++ b/public/js/controllers/logoutController.js @@ -0,0 +1,18 @@ +netstix.controller('LogoutController', ['UserAuth', '$window', '$scope', function(UserAuth, $window, $scope) { + var self = this; + + self.logout = function () { + UserAuth.logout() + .then(function () { + $window.location.href = '/#/achievements'; + }); + }; + + self.isLoggedIn = function() { + return UserAuth.isLoggedIn(); + }; + + self.getUser = function() { + return UserAuth.getUserStatus(); + }; +}]); diff --git a/public/js/controllers/newAchievementController.js b/public/js/controllers/newAchievementController.js new file mode 100644 index 0000000..16cd67c --- /dev/null +++ b/public/js/controllers/newAchievementController.js @@ -0,0 +1,11 @@ +netstix.controller('NewAchievementController', ['AchievementsResource', '$window', function(AchievementsResource, $window) { + var self = this; + + self.createNewAchievement = function() { + AchievementsResource.postAchievements(self.title, self.criteria, self.points, self.challengeRepo, self.badgeLink) + .then(function() { + $window.location.href ='/#/achievements'; + }); + }; + +}]); diff --git a/public/js/controllers/newSubmissionController.js b/public/js/controllers/newSubmissionController.js new file mode 100644 index 0000000..4026175 --- /dev/null +++ b/public/js/controllers/newSubmissionController.js @@ -0,0 +1,12 @@ +netstix.controller('NewSubmissionController', ['AchievementsResource', '$window', '$routeParams', function(AchievementsResource, $window, $routeParams) { + var self = this; + self.id = $routeParams.id; + + self.createNewSubmission = function() { + AchievementsResource.postSubmissions(self.link, self.comment, self.id) + .then(function() { + $window.location.href = ('/#/achievements/' + self.id); + }); + }; + +}]); diff --git a/public/js/controllers/registerController.js b/public/js/controllers/registerController.js new file mode 100644 index 0000000..f029ed5 --- /dev/null +++ b/public/js/controllers/registerController.js @@ -0,0 +1,21 @@ +netstix.controller('RegisterController', ['UserAuth', '$window', '$scope', function(UserAuth, $window, $scope) { + var self = this; + + self.register = function () { + $scope.error = false; + $scope.disabled = true; + + UserAuth.register($scope.registerForm.username, $scope.registerForm.password) + .then(function () { + $window.location.href ='/#/achievements'; + $scope.disabled = false; + $scope.registerForm = {}; + }) + .catch(function () { + $scope.error = true; + $scope.errorMessage = "Something went wrong!"; + $scope.disabled = false; + $scope.registerForm = {}; + }); + }; +}]); diff --git a/public/js/controllers/userController.js b/public/js/controllers/userController.js new file mode 100644 index 0000000..978cfb5 --- /dev/null +++ b/public/js/controllers/userController.js @@ -0,0 +1,13 @@ +netstix.controller('UserController', ['UsersResource', '$routeParams', function(UsersResource, $routeParams) { + var self = this; + self.id = $routeParams.id; + + self.init = function() { + UsersResource.getData(self.id) + .then(function(response) { + self.user = response.data; + }); + }; + + self.init(); +}]); diff --git a/public/js/controllers/usersController.js b/public/js/controllers/usersController.js new file mode 100644 index 0000000..d58f694 --- /dev/null +++ b/public/js/controllers/usersController.js @@ -0,0 +1,12 @@ +netstix.controller('UsersController', ['UsersResource', function(UsersResource) { + var self = this; + + self.init = function() { + UsersResource.getData() + .then(function(response) { + self.users = response.data; + }); + }; + + self.init(); +}]); diff --git a/public/js/factories/achievementsResourceFactory.js b/public/js/factories/achievementsResourceFactory.js new file mode 100644 index 0000000..1db8e1b --- /dev/null +++ b/public/js/factories/achievementsResourceFactory.js @@ -0,0 +1,51 @@ +netstix.factory('AchievementsResource', ['$http', '$q', function($http, $q) { + var achievementsResource = {}; + + achievementsResource.postAchievements = function(title, criteria, points, challengeRepo, badgeLink) { + var deferred = $q.defer(); + $http.post('/achievements', {title: title, criteria: criteria, points: points, challengeRepo: challengeRepo, badgeLink: badgeLink}) + .success(function (data, status) { + if(status === 200){ + deferred.resolve(data); + } else { + deferred.reject(); + } + }) + .error(function (data) { + deferred.reject(); + }); + return deferred.promise; + }; + + achievementsResource.getAchievement = function(id) { + return $http({ + url: ('/achievements/' + id), + method: 'GET' + }); + }; + + achievementsResource.getAchievements = function() { + return $http({ + url: '/achievements', + method: 'GET' + }); + }; + + achievementsResource.postSubmissions = function(link, comment, id) { + var deferred = $q.defer(); + $http.post('/achievements/' + id + '/submissions', {link: link, comment: comment}) + .success(function (data, status) { + if(status === 200){ + deferred.resolve(data); + } else { + deferred.reject(); + } + }) + .error(function (data) { + deferred.reject(); + }); + return deferred.promise; + }; + + return achievementsResource; +}]); diff --git a/public/js/factories/userAuthFactory.js b/public/js/factories/userAuthFactory.js new file mode 100644 index 0000000..d360134 --- /dev/null +++ b/public/js/factories/userAuthFactory.js @@ -0,0 +1,75 @@ +netstix.factory('UserAuth', ['$q', '$timeout', '$http', function ($q, $timeout, $http) { + var user = null; + + function isLoggedIn() { + if(user) { + return true; + } else { + return false; + } + } + + function getUserStatus() { + return user; + } + + function login(username, password) { + var deferred = $q.defer(); + $http.post('/sessions', {username: username, password: password}) + .success(function (data, status) { + if(status === 200 && data.status){ + user = data.user; + deferred.resolve(); + } else { + user = false; + deferred.reject(); + } + }) + .error(function (data) { + user = false; + deferred.reject(); + }); + return deferred.promise; + } + + function logout() { + var deferred = $q.defer(); + $http.delete('/sessions') + .success(function (data) { + user = false; + deferred.resolve(); + }) + .error(function (data) { + user = false; + deferred.reject(); + }); + return deferred.promise; + } + + function register(username, password) { + var deferred = $q.defer(); + $http.post('/users', {username: username, password: password}) + .success(function (data, status) { + if(status === 200 && data.status){ + user = data.user; + deferred.resolve(); + } else { + user = false; + deferred.reject(); + } + }) + .error(function (data) { + user = false; + deferred.reject(); + }); + return deferred.promise; + } + + return ({ + isLoggedIn: isLoggedIn, + getUserStatus: getUserStatus, + login: login, + logout: logout, + register: register + }); +}]); diff --git a/public/js/factories/usersResourceFactory.js b/public/js/factories/usersResourceFactory.js new file mode 100644 index 0000000..a9b7931 --- /dev/null +++ b/public/js/factories/usersResourceFactory.js @@ -0,0 +1,11 @@ +netstix.factory('UsersResource', ['$http', function($http) { + return { + getData: function(id) { + id = typeof id !== 'undefined' ? id : ''; + return $http({ + url: 'users/' + id, + method: 'GET' + }); + } + }; +}]); diff --git a/public/views/index.html b/public/views/index.html new file mode 100644 index 0000000..507435b --- /dev/null +++ b/public/views/index.html @@ -0,0 +1,52 @@ + + + + + + + + + + Netstix + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/public/views/partials/achievements/achievement.html b/public/views/partials/achievements/achievement.html new file mode 100644 index 0000000..60e97f3 --- /dev/null +++ b/public/views/partials/achievements/achievement.html @@ -0,0 +1,26 @@ +
+
+

{{ctrl.achievement.title}}

+ +
+
+
+

criteria

+

{{ctrl.achievement.criteria}}

+

challenge details

+

#{{ctrl.achievement.points}} points

+
+
+ +
+
+ +
+

rockstars

+
+ +
diff --git a/public/views/partials/achievements/index.html b/public/views/partials/achievements/index.html new file mode 100644 index 0000000..d1f1d42 --- /dev/null +++ b/public/views/partials/achievements/index.html @@ -0,0 +1,14 @@ +
+
+

achievements

+ +
+ + +
diff --git a/public/views/partials/achievements/new.html b/public/views/partials/achievements/new.html new file mode 100644 index 0000000..26332a6 --- /dev/null +++ b/public/views/partials/achievements/new.html @@ -0,0 +1,25 @@ +
+
+
+

create achievement

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ diff --git a/public/views/partials/footer.html b/public/views/partials/footer.html new file mode 100644 index 0000000..19ef61a --- /dev/null +++ b/public/views/partials/footer.html @@ -0,0 +1,3 @@ +
+

© copyright 2016 netstix its authors and contributors.

+
diff --git a/public/views/partials/navbar.html b/public/views/partials/navbar.html new file mode 100644 index 0000000..8ef3192 --- /dev/null +++ b/public/views/partials/navbar.html @@ -0,0 +1,32 @@ + diff --git a/public/views/partials/submissions/new.html b/public/views/partials/submissions/new.html new file mode 100644 index 0000000..08fb3b4 --- /dev/null +++ b/public/views/partials/submissions/new.html @@ -0,0 +1,16 @@ +
+
{{errorMessage}}
+
+

send new submission

+
+ +
+
+ +
+
+ +
+
+
+ diff --git a/public/views/partials/users/leaderboard.html b/public/views/partials/users/leaderboard.html new file mode 100644 index 0000000..2ff5be2 --- /dev/null +++ b/public/views/partials/users/leaderboard.html @@ -0,0 +1,11 @@ +
+
+

leaderboard

+
+ +
diff --git a/public/views/partials/users/login.html b/public/views/partials/users/login.html new file mode 100644 index 0000000..52c63ad --- /dev/null +++ b/public/views/partials/users/login.html @@ -0,0 +1,16 @@ +
+
{{errorMessage}}
+
+

login

+
+ +
+
+ +
+
+ +
+
+
+ diff --git a/public/views/partials/users/register.html b/public/views/partials/users/register.html new file mode 100644 index 0000000..7d0c6ca --- /dev/null +++ b/public/views/partials/users/register.html @@ -0,0 +1,16 @@ +
+
{{errorMessage}}
+
+

register

+
+ +
+
+ +
+
+ +
+
+
+ diff --git a/public/views/partials/users/user.html b/public/views/partials/users/user.html new file mode 100644 index 0000000..9ad6d49 --- /dev/null +++ b/public/views/partials/users/user.html @@ -0,0 +1,19 @@ +
+
+

{{ctrl.user.username}}

+ +
+
+ +

#{{ctrl.user.submissions.length}} badges

+
+
+

trophy cabinet

+
+ +
diff --git a/test/e2e/conf.js b/test/e2e/conf.js new file mode 100644 index 0000000..87b6e3b --- /dev/null +++ b/test/e2e/conf.js @@ -0,0 +1,16 @@ +exports.config = { + + seleniumAddress: 'http://localhost:4444/wd/hub', + capabilities: { 'browserName': 'chrome' }, + specs: ['features/*Feature.js'], + + jasmineNodeOpts: { + showColors: true, + print: function() {} + }, + + onPrepare: function() { + var SpecReporter = require('jasmine-spec-reporter'); + jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'all'})); + } +}; diff --git a/test/e2e/features/achievementsFeature.js b/test/e2e/features/achievementsFeature.js new file mode 100644 index 0000000..de4b1eb --- /dev/null +++ b/test/e2e/features/achievementsFeature.js @@ -0,0 +1,66 @@ +var mongoose = require('mongoose'); + +beforeEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +afterEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +describe('Achievements Features', function() { + it('a user can create an achievement, view a list of achievements then view a specific achievement', function() { + browser.get('http://localhost:8080/#/achievements'); + + var signUpLink = element(by.css('a[href*="#/register"]')); + + signUpLink.click(); + + var usernameInput = element(by.css('input[name="username"]')); + var passwordInput = element(by.css('input[name="password"]')); + var signUpForm = element(by.css('form')); + + usernameInput.sendKeys('test user'); + passwordInput.sendKeys('epicpassword'); + signUpForm.submit(); + + var achievementsList = element.all(by.repeater('achievement in ctrl.achievements')); + var firstAchievementTitle = achievementsList.get(0).element(by.css('a')); + + expect(achievementsList.count()).toEqual(0); + + var header = element(by.css('header')); + var createAchievementLink = header.element(by.css('a[href*="#/achievements/new"]')); + + createAchievementLink.click(); + + expect(browser.getCurrentUrl()).toContain('#/achievements/new'); + + var achievementTitleInput = element(by.css('input[name="title"]')); + var achievementCriteriaInput = element(by.css('textarea[name="criteria"]')); + var newAchievementForm = element(by.css('form')); + + achievementTitleInput.sendKeys('Create an achievement for the app'); + achievementCriteriaInput.sendKeys('This is where a user should write criteria'); + newAchievementForm.submit(); + + expect(achievementsList.count()).toEqual(1); + expect(browser.getCurrentUrl()).toContain('#/achievements'); + expect(firstAchievementTitle.getText()).toEqual('Create an achievement for the app'); + + var viewAchievementLink = achievementsList.get(0).element(by.css('a[href*="#/achievements/"]')); + + viewAchievementLink.click(); + + var achievementTitle = element(by.binding('achievement.title')); + var achievementCriteria = element(by.binding('achievement.criteria')); + + expect(browser.getCurrentUrl()).toContain('#/achievements/'); + expect(achievementTitle.getText()).toEqual('Create an achievement for the app'); + expect(achievementCriteria.getText()).toEqual('This is where a user should write criteria'); + }); +}); diff --git a/test/e2e/features/leaderboardFeature.js b/test/e2e/features/leaderboardFeature.js new file mode 100644 index 0000000..b06102a --- /dev/null +++ b/test/e2e/features/leaderboardFeature.js @@ -0,0 +1,91 @@ +var mongoose = require('mongoose'); + +beforeEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +afterEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +describe('Profiles/leaderboard Features', function() { + it('a user can see the leaderboard after submitting an achievement and view their own profile with individual achievements', function() { + + browser.get('http://localhost:8080/#/achievements'); + + var signUpLink = element(by.css('a[href*="#/register"]')); + + signUpLink.click(); + + var usernameInput = element(by.css('input[name="username"]')); + var passwordInput = element(by.css('input[name="password"]')); + var signUpForm = element(by.css('form')); + + usernameInput.sendKeys('test user'); + passwordInput.sendKeys('epicpassword'); + signUpForm.submit(); + + var header = element(by.css('header')); + var createAchievementLink = header.element(by.css('a[href*="#/achievements/new"]')); + + createAchievementLink.click(); + + var achievementTitleInput = element(by.css('input[name="title"]')); + var achievementCriteriaInput = element(by.css('textarea[name="criteria"]')); + var newAchievementForm = element(by.css('form')); + + achievementTitleInput.sendKeys('Create an achievement for the app'); + achievementCriteriaInput.sendKeys('This is where a user should write criteria'); + newAchievementForm.submit(); + + var achievementsList = element.all(by.repeater('achievement in ctrl.achievements')); + var viewAchievementLink = achievementsList.get(0).element(by.css('a[href*="#/achievements/"]')); + + viewAchievementLink.click(); + + var createSubmissionLink = element(by.css('a[href*="/submissions/new"]')); + + createSubmissionLink.click(); + + expect(browser.getCurrentUrl()).toContain('/submissions/new'); + + var submissionLinkInput = element(by.css('input[name="link"]')); + var submissionCommentInput = element(by.css('textarea[name="comment"]')); + var newSubmissionForm = element(by.css('form')); + + submissionLinkInput.sendKeys('A link to the relevant code or example'); + submissionCommentInput.sendKeys('A comment about the submission'); + newSubmissionForm.submit(); + + var achievementSubmissionsList = element.all(by.repeater('submission in ctrl.achievement.submissions')); + + expect(browser.getCurrentUrl()).toContain('/achievements/'); + expect(achievementSubmissionsList.count()).toEqual(1); + expect(achievementSubmissionsList.get(0).getText()).toEqual('test user'); + + var viewLeaderboardLink = element(by.css('.navbar-brand')); + + viewLeaderboardLink.click(); + + var leaderboardList = element.all(by.repeater('user in ctrl.users')); + + expect(browser.getCurrentUrl()).toContain('#/'); + expect(leaderboardList.count()).toEqual(1); + expect(leaderboardList.get(0).getText()).toContain('test user'); + + var viewProfileLink = leaderboardList.get(0).element(by.css('a[href*="#/users/"]')); + + viewProfileLink.click(); + + var trophyCabinetList = element.all(by.repeater('submission in ctrl.user.submissions')); + + expect(browser.getCurrentUrl()).toContain('#/users/'); + expect(trophyCabinetList.count()).toEqual(1); + expect(trophyCabinetList.get(0).getText()).toContain('Create an achievement for the app'); + + }); +}); diff --git a/test/e2e/features/submissionsFeature.js b/test/e2e/features/submissionsFeature.js new file mode 100644 index 0000000..799ea27 --- /dev/null +++ b/test/e2e/features/submissionsFeature.js @@ -0,0 +1,70 @@ +var mongoose = require('mongoose'); + +beforeEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +afterEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +describe('Submissions Features', function() { + it('a user can make a submission for a specific achievement', function() { + + browser.get('http://localhost:8080/#/achievements'); + + var signUpLink = element(by.css('a[href*="#/register"]')); + + signUpLink.click(); + + var usernameInput = element(by.css('input[name="username"]')); + var passwordInput = element(by.css('input[name="password"]')); + var signUpForm = element(by.css('form')); + + usernameInput.sendKeys('test user'); + passwordInput.sendKeys('epicpassword'); + signUpForm.submit(); + + var header = element(by.css('header')); + var createAchievementLink = header.element(by.css('a[href*="#/achievements/new"]')); + + createAchievementLink.click(); + + var achievementTitleInput = element(by.css('input[name="title"]')); + var achievementCriteriaInput = element(by.css('textarea[name="criteria"]')); + var newAchievementForm = element(by.css('form')); + + achievementTitleInput.sendKeys('Create an achievement for the app'); + achievementCriteriaInput.sendKeys('This is where a user should write criteria'); + newAchievementForm.submit(); + + var achievementsList = element.all(by.repeater('achievement in ctrl.achievements')); + var viewAchievementLink = achievementsList.get(0).element(by.css('a[href*="#/achievements/"]')); + + viewAchievementLink.click(); + + var createSubmissionLink = element(by.css('a[href*="/submissions/new"]')); + + createSubmissionLink.click(); + + expect(browser.getCurrentUrl()).toContain('/submissions/new'); + + var submissionLinkInput = element(by.css('input[name="link"]')); + var submissionCommentInput = element(by.css('textarea[name="comment"]')); + var newSubmissionForm = element(by.css('form')); + + submissionLinkInput.sendKeys('A link to the relevant code or example'); + submissionCommentInput.sendKeys('A comment about the submission'); + newSubmissionForm.submit(); + + var achievementSubmissionsList = element.all(by.repeater('submission in ctrl.achievement.submissions')); + + expect(browser.getCurrentUrl()).toContain('/achievements/'); + expect(achievementSubmissionsList.count()).toEqual(1); + expect(achievementSubmissionsList.get(0).getText()).toEqual('test user'); + }); +}); diff --git a/test/e2e/features/userAuthFeature.js b/test/e2e/features/userAuthFeature.js new file mode 100644 index 0000000..d20ec75 --- /dev/null +++ b/test/e2e/features/userAuthFeature.js @@ -0,0 +1,63 @@ +var mongoose = require('mongoose'); + +beforeEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +afterEach(function() { + mongoose.connect('mongodb://localhost/makers-achievements-test', function() { + mongoose.connection.db.dropDatabase(); + }); +}); + +describe('User Authentication', function() { + it('a user can sign up, sign out and sign in', function() { + browser.get('http://localhost:8080/#/achievements'); + + var signUpLink = element(by.css('a[href*="#/register"]')); + var signInLink = element(by.css('a[href*="#/login"]')); + + expect(signUpLink.isDisplayed()).toBeTruthy(); + expect(signInLink.isDisplayed()).toBeTruthy(); + + signUpLink.click(); + + expect(browser.getCurrentUrl()).toContain('#/register'); + + var usernameInput = element(by.css('input[name="username"]')); + var passwordInput = element(by.css('input[name="password"]')); + var signUpForm = element(by.css('form')); + + usernameInput.sendKeys('test user'); + passwordInput.sendKeys('epicpassword'); + signUpForm.submit(); + + expect(browser.getCurrentUrl()).toContain('#/achievements'); + expect(signUpLink.isDisplayed()).toBeFalsy(); + expect(signInLink.isDisplayed()).toBeFalsy(); + + var usernameDropdown = element(by.css('.dropdown-toggle')); + var signOutButton = element(by.css('.btn-logout')); + + usernameDropdown.click(); + signOutButton.click(); + + expect(browser.getCurrentUrl()).toContain('#/achievements'); + + signInLink.click(); + + expect(browser.getCurrentUrl()).toContain('#/login'); + + var signInForm = element(by.css('form')); + + usernameInput.sendKeys('test user'); + passwordInput.sendKeys('epicpassword'); + signInForm.submit(); + + expect(browser.getCurrentUrl()).toContain('#/achievements'); + expect(signUpLink.isDisplayed()).toBeFalsy(); + expect(signInLink.isDisplayed()).toBeFalsy(); + }); +}); diff --git a/test/front_end/controllers/achievementController.spec.js b/test/front_end/controllers/achievementController.spec.js new file mode 100644 index 0000000..ecf29bc --- /dev/null +++ b/test/front_end/controllers/achievementController.spec.js @@ -0,0 +1,27 @@ +describe('AchievementController', function() { + var response = { + data: { title: 'codewars', criteria: '150pts on codewars' } + }; + var ctrl; + var scope; + var AchievementsResourceFactoryMock; + + beforeEach(function() { + AchievementsResourceFactoryMock = jasmine.createSpyObj('AchievementsResource', ['getAchievement']); + module('Netstix', { + AchievementsResource: AchievementsResourceFactoryMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + AchievementsResourceFactoryMock.getAchievement.and.returnValue($q.when(response)); + ctrl = $controller('AchievementController', { $routeParams: {id: '2'} }); + scope = $rootScope; + })); + + it('initializes with achievement info from the GetAchievement Factory', function() { + scope.$digest(); + expect(ctrl.achievement) + .toEqual(response.data); + }); +}); diff --git a/test/front_end/controllers/achievementsController.spec.js b/test/front_end/controllers/achievementsController.spec.js new file mode 100644 index 0000000..0ef128b --- /dev/null +++ b/test/front_end/controllers/achievementsController.spec.js @@ -0,0 +1,27 @@ +describe('AchievementsController', function() { + var response = { + data: [{ title: 'codewars', criteria: '150pts on codewars' }] + }; + var ctrl; + var scope; + var AchievementsResourceFactoryMock; + + beforeEach(function() { + AchievementsResourceFactoryMock = jasmine.createSpyObj('AchievementsResource', ['getAchievements']); + module('Netstix', { + AchievementsResource: AchievementsResourceFactoryMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + AchievementsResourceFactoryMock.getAchievements.and.returnValue($q.when(response)); + ctrl = $controller('AchievementsController'); + scope = $rootScope; + })); + + it('initializes with achievements from the AchievementsResource Factory', function() { + scope.$digest(); + expect(ctrl.achievements) + .toEqual(response.data); + }); +}); diff --git a/test/front_end/controllers/newAchievementController.spec.js b/test/front_end/controllers/newAchievementController.spec.js new file mode 100644 index 0000000..5a8d8be --- /dev/null +++ b/test/front_end/controllers/newAchievementController.spec.js @@ -0,0 +1,32 @@ +describe('NewAchievementController', function() { + var ctrl; + var scope; + var AchievementsResourceMock; + var windowMock; + + beforeEach(function() { + windowMock = { location: { href: jasmine.createSpy() } }; + AchievementsResourceMock = jasmine.createSpyObj( + 'AchievementsResource', ['postAchievements'] + ); + module('Netstix', { + AchievementsResource: AchievementsResourceMock, + $window: windowMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + AchievementsResourceMock.postAchievements + .and.returnValue($q.when({})); + ctrl = $controller('NewAchievementController'); + scope = $rootScope; + })); + + describe('#createNewAchievement()', function() { + it('redirects to /#/achievements', function() { + ctrl.createNewAchievement(); + scope.$digest(); + expect(windowMock.location.href).toEqual('/#/achievements'); + }); + }); +}); diff --git a/test/front_end/controllers/newSubmissionController.spec.js b/test/front_end/controllers/newSubmissionController.spec.js new file mode 100644 index 0000000..abcd735 --- /dev/null +++ b/test/front_end/controllers/newSubmissionController.spec.js @@ -0,0 +1,32 @@ +describe('NewSubmissionController', function() { + var response = { message: 'ok' }; + var ctrl; + var scope; + var AchievementsResourceFactoryMock; + var windowMock; + var idMock; + + beforeEach(function() { + windowMock = { location: { href: jasmine.createSpy() } }; + AchievementsResourceFactoryMock = jasmine.createSpyObj('AchievementsResource', ['postSubmissions']); + module('Netstix', { + AchievementsResource: AchievementsResourceFactoryMock, + $window: windowMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + AchievementsResourceFactoryMock.postSubmissions.and.returnValue($q.when(response)); + ctrl = $controller('NewSubmissionController'); + scope = $rootScope; + })); + + describe('#createNewSubmission()', function() { + it('redirects to /#/achievements/:id when successful', function() { + ctrl.id = 55; + ctrl.createNewSubmission(); + scope.$digest(); + expect(windowMock.location.href).toEqual('/#/achievements/55'); + }); + }); +}); diff --git a/test/front_end/controllers/userAchievementController.spec.js b/test/front_end/controllers/userAchievementController.spec.js new file mode 100644 index 0000000..8179b56 --- /dev/null +++ b/test/front_end/controllers/userAchievementController.spec.js @@ -0,0 +1,27 @@ +describe('UserController', function() { + var response = { + data: { username: 'codewars', id: '3' } + }; + var ctrl; + var scope; + var UsersResourceFactoryMock; + + beforeEach(function() { + UsersResourceFactoryMock = jasmine.createSpyObj('UsersResource', ['getData']); + module('Netstix', { + UsersResource: UsersResourceFactoryMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + UsersResourceFactoryMock.getData.and.returnValue($q.when(response)); + ctrl = $controller('UserController', { $routeParams: {id: '3'} }); + scope = $rootScope; + })); + + it('initializes with user info from the UsersResource Factory', function() { + scope.$digest(); + expect(ctrl.user) + .toEqual(response.data); + }); +}); diff --git a/test/front_end/controllers/usersAchievementsController.spec.js b/test/front_end/controllers/usersAchievementsController.spec.js new file mode 100644 index 0000000..4a82798 --- /dev/null +++ b/test/front_end/controllers/usersAchievementsController.spec.js @@ -0,0 +1,27 @@ +describe('UsersController', function() { + var response = { + data: [{ username: 'giamir', id: '2' }] + }; + var ctrl; + var scope; + var UsersResourceFactoryMock; + + beforeEach(function() { + UsersResourceFactoryMock = jasmine.createSpyObj('UsersResourceFactory', ['getData']); + module('Netstix', { + UsersResource: UsersResourceFactoryMock + }); + }); + + beforeEach(inject(function($controller, $q, $rootScope) { + UsersResourceFactoryMock.getData.and.returnValue($q.when(response)); + ctrl = $controller('UsersController'); + scope = $rootScope; + })); + + it('initializes with users from the UsersResources Factory', function() { + scope.$digest(); + expect(ctrl.users) + .toEqual(response.data); + }); +}); diff --git a/test/front_end/factories/achievementsResourceFactory.spec.js b/test/front_end/factories/achievementsResourceFactory.spec.js new file mode 100644 index 0000000..5a44b1d --- /dev/null +++ b/test/front_end/factories/achievementsResourceFactory.spec.js @@ -0,0 +1,74 @@ +describe('factory: AchievementsResource', function() { + var achievementsResource; + + beforeEach(module('Netstix')); + + beforeEach(inject(function(AchievementsResource) { + achievementsResource = AchievementsResource; + })); + + beforeEach(inject(function($httpBackend) { + httpBackend = $httpBackend; + httpBackend + .whenPOST('/achievements').respond(function(){ + return [200, { message: 'Achievement created!'}, {}]; + }); + httpBackend + .whenGET("/achievements/2").respond( + { title: 'codewars', criteria: '150pts on codewars' } + ); + httpBackend + .whenGET("/achievements").respond( + [{ title: 'codewars', criteria: '150pts on codewars' }] + ); + httpBackend + .whenPOST('/achievements/55/submissions').respond(function(){ + return [200, { message: 'Submission sent!'}, {}]; + }); + })); + + afterEach(function() { + httpBackend.verifyNoOutstandingExpectation(); + httpBackend.verifyNoOutstandingRequest(); + }); + + describe('#postAchievements', function() { + it('returns a success message if the achievement has been created', function() { + achievementsResource.postAchievements('a title', 'a criteria') + .then(function(data) { + expect(data.message).toEqual('Achievement created!'); + }); + httpBackend.flush(); + }); + }); + + describe('#getAchievement', function() { + it('returns achievement hash', function() { + achievementsResource.getAchievement(2) + .then(function(response) { + expect(response.data).toEqual({ title: 'codewars', criteria: '150pts on codewars' }); + }); + httpBackend.flush(); + }); + }); + + describe('#getAchievements', function() { + it('returns achievements array', function() { + achievementsResource.getAchievements() + .then(function(response) { + expect(response.data[0]).toEqual({ title: 'codewars', criteria: '150pts on codewars' }); + }); + httpBackend.flush(); + }); + }); + + describe('#postSubmissions', function() { + it('returns a success message if the submission has been sent', function() { + achievementsResource.postSubmissions('https://github.com/Htunny', 'a comment', 55) + .then(function(data) { + expect(data.message).toEqual('Submission sent!'); + }); + httpBackend.flush(); + }); + }); +}); diff --git a/test/front_end/factories/usersResourceFactory.spec.js b/test/front_end/factories/usersResourceFactory.spec.js new file mode 100644 index 0000000..1983e56 --- /dev/null +++ b/test/front_end/factories/usersResourceFactory.spec.js @@ -0,0 +1,52 @@ +describe('factory: UsersResource', function() { + var usersResource; + var scope; + + beforeEach(module('Netstix')); + + beforeEach(inject(function(UsersResource) { + usersResource = UsersResource; + })); + + beforeEach(inject(function($httpBackend, $rootScope) { + httpBackend = $httpBackend; + httpBackend + .when( + "GET", + "users/" + ) + .respond( + [{ username: 'giamir', id: '2' }] + ); + httpBackend + .when( + "GET", + "users/2" + ) + .respond( + { username: 'giamir', id: '2' } + ); + })); + + afterEach(function() { + httpBackend.verifyNoOutstandingExpectation(); + httpBackend.verifyNoOutstandingRequest(); + }); + + describe('#getData', function() { + it('returns an array of users if no argument is passed', function() { + usersResource.getData() + .then(function(response) { + expect(response.data[0]).toEqual({ username: 'giamir', id: '2' }); + }); + httpBackend.flush(); + }); + it('returns a specific user if the id is passed', function() { + usersResource.getData(2) + .then(function(response) { + expect(response.data).toEqual({ username: 'giamir', id: '2' }); + }); + httpBackend.flush(); + }); + }); +}); diff --git a/test/front_end/karma.conf.js b/test/front_end/karma.conf.js new file mode 100644 index 0000000..9bec1ea --- /dev/null +++ b/test/front_end/karma.conf.js @@ -0,0 +1,44 @@ +module.exports = function(config) { + config.set({ + + basePath: '../', + frameworks: ['jasmine'], + files: [ + '../public/libs/angular/angular.js', + '../public/libs/angular-route/angular-route.js', + '../public/libs/angular-resource/angular-resource.js', + '../public/libs/angular-mocks/angular-mocks.js', + '../public/libs/angular-bootstrap/ui-bootstrap.min.js', + '../public/libs/angular-loading-bar/build/loading-bar.min.js', + '../public/js/**/*.js', + './front_end/**/*.spec.js' + ], + + exclude: [], + + preprocessors: { + '../public/js/**/*.js': ['coverage'] + }, + + reporters: ['spec', 'coverage'], + + port: 9876, + + colors: true, + + logLevel: config.LOG_INFO, + + autoWatch: true, + + // browsers: ['Chrome'], + browsers: ['PhantomJS'], + + singleRun: true, + + coverageReporter: { + type : 'html', + dir : 'front_end/coverage/' + } + + }); +}; diff --git a/test/server/controllers/achievementsSpec.js b/test/server/controllers/achievementsSpec.js new file mode 100644 index 0000000..72e41d5 --- /dev/null +++ b/test/server/controllers/achievementsSpec.js @@ -0,0 +1,38 @@ +var frisby = require('frisby'); +var mongoose = require('mongoose'); + +var URL = 'http://localhost:8080/achievements/'; + +frisby.create('api call to add an achievement') + .post(URL, { + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + + .after(function() { + frisby.create('view list of achievements') + .get(URL) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + .expectJSON('?', { + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + + .afterJSON(function(achievements) { + var achievement = achievements[0]; + frisby.create('view individual achievement') + .get(URL + achievement._id) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + .expectJSON({ + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + .toss(); + }) + .toss(); + }) +.toss(); diff --git a/test/server/controllers/submissionsSpec.js b/test/server/controllers/submissionsSpec.js new file mode 100644 index 0000000..3451cfa --- /dev/null +++ b/test/server/controllers/submissionsSpec.js @@ -0,0 +1,45 @@ +var frisby = require('frisby'); +var mongoose = require('mongoose'); + +var URL = 'http://localhost:8080/achievements/'; + +frisby.create('api call to add an achievement') + .post(URL, { + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + + .after(function() { + frisby.create('view list of achievements') + .get(URL) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + .expectJSON('?', { + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + + .afterJSON(function(achievements) { + var achievement = achievements[0]; + frisby.create('view individual achievement') + .get(URL + achievement._id) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + .expectJSON({ + title: 'Bug Hunter', + criteria: 'Find an error in the Makers Academy materials' + }) + .toss(); + }) + .afterJSON(function(achievement) { + frisby.create('make a submission') + .post(URL + achievement._id + '/submissions/') + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + .toss(); + }) + .toss(); + }) +.toss(); diff --git a/test/server/controllers/userAuthSpec.js b/test/server/controllers/userAuthSpec.js new file mode 100644 index 0000000..b1f71ae --- /dev/null +++ b/test/server/controllers/userAuthSpec.js @@ -0,0 +1,47 @@ +var frisby = require('frisby'); +var mongoose = require('mongoose'); + +var URL = 'http://localhost:8080/'; + +frisby.create('api call to create user') + .post(URL + 'users', { + username: 'testuser1', + password: 'password1' + }) + .expectStatus(200) + .expectHeaderContains('content-type', 'application/json; charset=utf-8') + + .after(function() { + frisby.create('can sign user out') + .delete(URL + 'sessions', { + }) + .expectStatus(200) + .expectJSON({ + status: 'Signed out successfully!' + }) + + .after(function() { + frisby.create('an invalid user cannot sign in') + .post(URL + 'sessions', { + username: 'invaliduser', + password: 'notpassword' + }) + .expectStatus(401) + + .after(function() { + frisby.create('a valid user can sign in') + .post(URL + 'sessions', { + username: 'testuser1', + password: 'password1' + }) + .expectStatus(200) + .expectJSON({ + status: 'Login successful!' + }) + .toss(); + }) + .toss(); + }) + .toss(); + }) +.toss(); diff --git a/test/server/support/jasmine.json b/test/server/support/jasmine.json new file mode 100644 index 0000000..ff6409e --- /dev/null +++ b/test/server/support/jasmine.json @@ -0,0 +1,8 @@ +{ + "spec_dir": "test/server", + "spec_files": [ + "**/*[sS]pec.js" + ], + "stopSpecOnExpectationFailure": false, + "random": false +}