come on
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
public/libs
|
||||
coverage
|
||||
npm-debug.log
|
||||
21
.travis.yml
Normal file
@ -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
|
||||
57
Gruntfile.js
Normal file
@ -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']);
|
||||
|
||||
};
|
||||
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
#  [](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.
|
||||
|
||||

|
||||
|
||||
## 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:<br>
|
||||
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 <your username> /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)
|
||||
80
app.js
Normal file
@ -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;
|
||||
45
app/controllers/achievementsController.js
Normal file
@ -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;
|
||||
28
app/controllers/sessionsController.js
Normal file
@ -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;
|
||||
45
app/controllers/submissionsController.js
Normal file
@ -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;
|
||||
47
app/controllers/usersController.js
Normal file
@ -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;
|
||||
14
app/models/achievement.js
Normal file
@ -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);
|
||||
12
app/models/submission.js
Normal file
@ -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);
|
||||
16
app/models/user.js
Normal file
@ -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);
|
||||
12
app/routes/achievementsRouter.js
Normal file
@ -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;
|
||||
9
app/routes/indexRouter.js
Normal file
@ -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;
|
||||
7
app/routes/sessionsRouter.js
Normal file
@ -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;
|
||||
8
app/routes/submissionsRouter.js
Normal file
@ -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;
|
||||
9
app/routes/usersRouter.js
Normal file
@ -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;
|
||||
6
app/views/error.jade
Normal file
@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
5
app/views/layout.jade
Normal file
@ -0,0 +1,5 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
body
|
||||
block content
|
||||
31
bower.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
99
config/env/dev
vendored
Normal file
@ -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);
|
||||
}
|
||||
103
config/env/test
vendored
Normal file
@ -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);
|
||||
}
|
||||
99
config/env/www
vendored
Normal file
@ -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);
|
||||
}
|
||||
71
package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
302
public/css/style.css
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/badges/Angel.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
public/images/badges/Angel_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/badges/Architect_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/badges/Bolt.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
public/images/badges/Bolt_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Data.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/images/badges/Data_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Frontman.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/images/badges/Frontman_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Machinist.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/images/badges/Machinist_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/images/badges/Octocat.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/images/badges/Octocat_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Polyglot.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/images/badges/Polyglot_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Ronin.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/images/badges/Ronin_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Rubyist.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/badges/Rubyist_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Sentry.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/images/badges/Sentry_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/Unixoid.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/badges/Unixoid_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/badges/architect.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/images/badges/dev-ops-logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/badges/dev-ops-logo_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/badges/nobadge.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/images/badges/open-source-logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/badges/open-source-logo_thumb.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/netstix.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
37
public/js/app.js
Normal file
@ -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: '/'
|
||||
});
|
||||
}
|
||||
]);
|
||||
17
public/js/controllers/achievementController.js
Normal file
@ -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();
|
||||
}]);
|
||||
16
public/js/controllers/achievementsController.js
Normal file
@ -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();
|
||||
}]);
|
||||
21
public/js/controllers/loginController.js
Normal file
@ -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 = {};
|
||||
});
|
||||
};
|
||||
}]);
|
||||
18
public/js/controllers/logoutController.js
Normal file
@ -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();
|
||||
};
|
||||
}]);
|
||||
11
public/js/controllers/newAchievementController.js
Normal file
@ -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';
|
||||
});
|
||||
};
|
||||
|
||||
}]);
|
||||
12
public/js/controllers/newSubmissionController.js
Normal file
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
}]);
|
||||
21
public/js/controllers/registerController.js
Normal file
@ -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 = {};
|
||||
});
|
||||
};
|
||||
}]);
|
||||
13
public/js/controllers/userController.js
Normal file
@ -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();
|
||||
}]);
|
||||
12
public/js/controllers/usersController.js
Normal file
@ -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();
|
||||
}]);
|
||||
51
public/js/factories/achievementsResourceFactory.js
Normal file
@ -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;
|
||||
}]);
|
||||
75
public/js/factories/userAuthFactory.js
Normal file
@ -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
|
||||
});
|
||||
}]);
|
||||
11
public/js/factories/usersResourceFactory.js
Normal file
@ -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'
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
52
public/views/index.html
Normal file
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="Netstix">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- <meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="icon" href="../../favicon.ico"> -->
|
||||
|
||||
<title>Netstix</title>
|
||||
|
||||
<link rel="stylesheet" href="../libs/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel='stylesheet' href='../libs/angular-loading-bar/build/loading-bar.min.css'>
|
||||
<link rel="stylesheet" href="../css/style.css">
|
||||
|
||||
<script src="../libs/jquery/dist/jquery.min.js"></script>
|
||||
<script src="../libs/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="../libs/angular/angular.min.js"></script>
|
||||
<script src="../libs/angular-resource/angular-resource.min.js"></script>
|
||||
<script src="../libs/angular-route/angular-route.min.js"></script>
|
||||
<script src="../libs/angular-mocks/angular-mocks.js"></script>
|
||||
<script src="../libs/angular-bootstrap/ui-bootstrap.min.js"></script>
|
||||
<script src='../libs/angular-loading-bar/build/loading-bar.min.js'></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script src="../js/controllers/loginController.js"></script>
|
||||
<script src="../js/controllers/logoutController.js"></script>
|
||||
<script src="../js/controllers/registerController.js"></script>
|
||||
<script src="../js/controllers/achievementsController.js"></script>
|
||||
<script src="../js/controllers/newAchievementController.js"></script>
|
||||
<script src="../js/controllers/newSubmissionController.js"></script>
|
||||
<script src="../js/controllers/achievementController.js"></script>
|
||||
<script src="../js/controllers/usersController.js"></script>
|
||||
<script src="../js/controllers/userController.js"></script>
|
||||
<script src="../js/factories/userAuthFactory.js"></script>
|
||||
<script src="../js/factories/achievementsResourceFactory.js"></script>
|
||||
<script src="../js/factories/usersResourceFactory.js"></script>
|
||||
|
||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ng-include src="'views/partials/navbar.html'"></ng-include>
|
||||
<div ng-view class="content container"></div>
|
||||
<ng-include src="'views/partials/footer.html'"></ng-include>
|
||||
</body>
|
||||
</html>
|
||||
26
public/views/partials/achievements/achievement.html
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="container achievement row" ng-controller="AchievementController as ctrl">
|
||||
<header>
|
||||
<h1>{{ctrl.achievement.title}}</h1>
|
||||
<a ng-href="/#/achievements/{{ctrl.achievement._id}}/submissions/new" class="hidden-xs pull-right" ng-show="ctrl.isLoggedIn()"><i class="glyphicon glyphicon-download-alt"></i> Submit</a>
|
||||
</header>
|
||||
<section class="description row">
|
||||
<section class="col-xs-12 criteria col-md-7">
|
||||
<h3>criteria</h3>
|
||||
<p>{{ctrl.achievement.criteria}}</p>
|
||||
<p ng-show="ctrl.achievement.challengeRepo"><a ng-href="{{ctrl.achievement.challengeRepo}}" target="blank">challenge details</a></p>
|
||||
<p><b>#{{ctrl.achievement.points}} points</b></p>
|
||||
</section>
|
||||
<figure class="col-xs-12 col-md-offset-2 col-md-3">
|
||||
<img class="pull-right" ng-src="{{ctrl.achievement.badgeLink}}" width="100px" height="100px"/>
|
||||
</figure>
|
||||
</section>
|
||||
<a ng-href="/#/achievements/{{ctrl.achievement._id}}/submissions/new" class="hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()"><i class="glyphicon glyphicon-download-alt"></i> Submit</a>
|
||||
<header>
|
||||
<h2>rockstars</h2>
|
||||
</header>
|
||||
<ul class="items-result list-group">
|
||||
<li class="list-group-item" ng-repeat="submission in ctrl.achievement.submissions">
|
||||
<i class="glyphicon glyphicon-user hidden-xs "></i><a ng-href="/#/users/{{submission.user._id}}">{{submission.user.username}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
14
public/views/partials/achievements/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="container achievements" ng-controller="AchievementsController as ctrl">
|
||||
<header>
|
||||
<h1>achievements</h1>
|
||||
<a href="#/achievements/new" class="pull-right hidden-xs " ng-show="ctrl.isLoggedIn()" title="Create a new achievement"><i class="glyphicon glyphicon-plus"></i>Create a new achievement</a>
|
||||
</header>
|
||||
<ul class="items-result list-group" ng-show="ctrl.achievements.length">
|
||||
<li class="list-group-item" ng-repeat="achievement in ctrl.achievements">
|
||||
<img ng-src="{{achievement.badgeLink}}" width="50px" height="50px"/>
|
||||
<a ng-href="/#/achievements/{{achievement._id}}">{{achievement.title}}</a>
|
||||
<span class="pull-right hidden-xs "><b>{{achievement.points}} pts</b></span>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="#/achievements/new" class="pull-right hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()" title="Create a new achievement">Create a new achievement</a>
|
||||
</div>
|
||||
25
public/views/partials/achievements/new.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="row" ng-controller="NewAchievementController as ctrl">
|
||||
<div ng-show="error" class="alert alert-danger"></div>
|
||||
<form class="col-xs-12 col-md-offset-3 col-md-6 form" ng-submit="ctrl.createNewAchievement()">
|
||||
<h2>create achievement</h2>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="title" placeholder="title" ng-model="ctrl.title" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="number" class="form-control" name="points" placeholder="points" ng-model="ctrl.points" autocomplete="off" min="10" max="100" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="url" class="form-control" name="challengeRepo" placeholder="challenge repo (optional)" ng-model="ctrl.challengeRepo" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="badgeLink" placeholder="badge link (optional)" ng-model="ctrl.badgeLink" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea rows="5" class="form-control" name="criteria" placeholder="criteria" ng-model="ctrl.criteria" autocomplete="off" required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-default" ng-disabled="disabled">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
3
public/views/partials/footer.html
Normal file
@ -0,0 +1,3 @@
|
||||
<footer>
|
||||
<p>© copyright 2016 <a href="https://github.com/michaellennox/netstix">netstix</a> its authors and contributors.</p>
|
||||
</footer>
|
||||
32
public/views/partials/navbar.html
Normal file
@ -0,0 +1,32 @@
|
||||
<nav class="navbar" ng-controller="LogoutController as ctrl">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" ng-init="navCollapsed = true" ng-click="navCollapsed = !navCollapsed">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="/#/" class="navbar-brand">Netstix</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" uib-collapse="navCollapsed">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li ng-hide="ctrl.isLoggedIn()"><a ng-click="navCollapsed = !navCollapsed" href="/#/login">login</a></li>
|
||||
<li ng-hide="ctrl.isLoggedIn()"><a ng-click="navCollapsed = !navCollapsed" href="/#/register">register</a></li>
|
||||
<li ng-hide="ctrl.isLoggedIn()"><a ng-click="navCollapsed = !navCollapsed" href="/#/achievements">achievements</a></li>
|
||||
<li class="dropdown hidden-xs ">
|
||||
<a ng-show="ctrl.isLoggedIn()" class="dropdown-toggle hidden-xs " data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ctrl.getUser().username}}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="navCollapsed = !navCollapsed" href="/#/achievements">get achievements</a></li>
|
||||
<li><a ng-click="navCollapsed = !navCollapsed" href="/#/users/{{ctrl.getUser()._id}}">my achievements</a></li>
|
||||
<li><button ng-click="ctrl.logout()" ng-click="navCollapsed = !navCollapsed" class="btn btn-link btn-logout">logout</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()"><a>{{ctrl.getUser().username}}</a></li>
|
||||
<li class="hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()"><a ng-click="navCollapsed = !navCollapsed" href="/#/achievements">get achievements</a></li>
|
||||
<li class="hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()"><a ng-click="navCollapsed = !navCollapsed" href="/#/users/{{ctrl.getUser()._id}}">my achievements</a></li>
|
||||
<li class="hidden-md hidden-lg" ng-show="ctrl.isLoggedIn()"><button ng-click="ctrl.logout()" ng-click="navCollapsed = !navCollapsed" class="btn btn-link btn-logout">logout</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
16
public/views/partials/submissions/new.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="row" ng-controller="NewSubmissionController as ctrl">
|
||||
<div ng-show="error" class="alert alert-danger">{{errorMessage}}</div>
|
||||
<form class="col-xs-12 col-md-offset-3 col-md-6 form" ng-submit="ctrl.createNewSubmission()">
|
||||
<h2>send new submission</h2>
|
||||
<div class="form-group">
|
||||
<input type="url" class="form-control" name="link" placeholder="github link" ng-model="ctrl.link" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea rows="5" class="form-control" name="comment" placeholder="comments" ng-model="ctrl.comment" autocomplete="off"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-default" ng-disabled="disabled">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
11
public/views/partials/users/leaderboard.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="container" ng-controller="UsersController as ctrl">
|
||||
<header>
|
||||
<h1>leaderboard</h1>
|
||||
</header>
|
||||
<ul class="items-result list-group" ng-show="ctrl.users.length">
|
||||
<li class="list-group-item" ng-repeat="user in ctrl.users | orderBy:'-score'">
|
||||
<i class="glyphicon glyphicon-user hidden-xs"></i><a ng-href="#/users/{{user._id}}">{{user.username}}</a>
|
||||
<span class="pull-right hidden-xs "><b>{{user.score}} pts</b></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
16
public/views/partials/users/login.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="row" ng-controller="LoginController as ctrl">
|
||||
<div ng-show="error" class="alert alert-danger">{{errorMessage}}</div>
|
||||
<form class="col-xs-12 col-md-offset-3 col-md-6 form" ng-submit="ctrl.login()">
|
||||
<h2>login</h2>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="username" placeholder="username" ng-model="loginForm.username" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="password" placeholder="password" ng-model="loginForm.password" autocomplete="off" required>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-default" ng-disabled="disabled">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
16
public/views/partials/users/register.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="row" ng-controller="RegisterController as ctrl">
|
||||
<div ng-show="error" class="alert alert-danger">{{errorMessage}}</div>
|
||||
<form class="col-xs-12 col-md-offset-3 col-md-6 form" ng-submit="ctrl.register()">
|
||||
<h2>register</h2>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="username" placeholder="username" ng-model="registerForm.username" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="password" placeholder="password" ng-model="registerForm.password" autocomplete="off" required>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-default" ng-disabled="disabled">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
19
public/views/partials/users/user.html
Normal file
@ -0,0 +1,19 @@
|
||||
<div class="container row user" ng-controller="UserController as ctrl">
|
||||
<header>
|
||||
<h1>{{ctrl.user.username}}</h1>
|
||||
<b class="pull-right hidden-xs ">{{ctrl.user.score}} pts</b>
|
||||
</header>
|
||||
<section class="description">
|
||||
<p class="hidden-md hidden-lg"><b>{{ctrl.user.score}}</b> pts</p>
|
||||
<p><b>#{{ctrl.user.submissions.length}}</b> badges</p>
|
||||
</section>
|
||||
<header>
|
||||
<h2>trophy cabinet</h2>
|
||||
</header>
|
||||
<ul class="items-result list-group">
|
||||
<li class="list-group-item" ng-repeat="submission in ctrl.user.submissions">
|
||||
<img ng-src="{{submission.achievement.badgeLink}}" width="50px" height="50px"/><a ng-href="/#/achievements/{{submission.achievement._id}}">{{submission.achievement.title}}</a>
|
||||
<a class="pull-right hidden-xs " target="_blank" ng-href="{{submission.link}}"><i class="glyphicon glyphicon-pencil"></i> solution</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
16
test/e2e/conf.js
Normal file
@ -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'}));
|
||||
}
|
||||
};
|
||||
66
test/e2e/features/achievementsFeature.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
91
test/e2e/features/leaderboardFeature.js
Normal file
@ -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');
|
||||
|
||||
});
|
||||
});
|
||||
70
test/e2e/features/submissionsFeature.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
63
test/e2e/features/userAuthFeature.js
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
27
test/front_end/controllers/achievementController.spec.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
27
test/front_end/controllers/achievementsController.spec.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
32
test/front_end/controllers/newAchievementController.spec.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
32
test/front_end/controllers/newSubmissionController.spec.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
27
test/front_end/controllers/userAchievementController.spec.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
74
test/front_end/factories/achievementsResourceFactory.spec.js
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
52
test/front_end/factories/usersResourceFactory.spec.js
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
44
test/front_end/karma.conf.js
Normal file
@ -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/'
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
38
test/server/controllers/achievementsSpec.js
Normal file
@ -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();
|
||||
45
test/server/controllers/submissionsSpec.js
Normal file
@ -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();
|
||||
47
test/server/controllers/userAuthSpec.js
Normal file
@ -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();
|
||||
8
test/server/support/jasmine.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"spec_dir": "test/server",
|
||||
"spec_files": [
|
||||
"**/*[sS]pec.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
||||