first version of the OpenBikeSensor Web API
This commit is contained in:
parent
eebc3cf927
commit
02e78c7b38
81
.gitignore
vendored
81
.gitignore
vendored
|
@ -1,104 +1,37 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.DS_Store
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
# Dependency directory
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
.idea
|
||||
|
|
84
app.js
Normal file
84
app.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
var http = require('http'),
|
||||
path = require('path'),
|
||||
methods = require('methods'),
|
||||
express = require('express'),
|
||||
bodyParser = require('body-parser'),
|
||||
session = require('express-session'),
|
||||
cors = require('cors'),
|
||||
passport = require('passport'),
|
||||
errorhandler = require('errorhandler'),
|
||||
mongoose = require('mongoose');
|
||||
|
||||
var isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
// Create global app object
|
||||
var app = express();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
// Normal express config defaults
|
||||
app.use(require('morgan')('dev'));
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(require('method-override')());
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
app.use(session({ secret: 'conduit', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));
|
||||
|
||||
if (!isProduction) {
|
||||
app.use(errorhandler());
|
||||
}
|
||||
|
||||
if(isProduction){
|
||||
mongoose.connect('mongodb://localhost/obs');
|
||||
} else {
|
||||
mongoose.connect('mongodb://localhost/obsTest');
|
||||
mongoose.set('debug', true);
|
||||
}
|
||||
|
||||
require('./models/User');
|
||||
require('./models/Track');
|
||||
require('./models/Comment');
|
||||
require('./config/passport');
|
||||
|
||||
app.use(require('./routes'));
|
||||
|
||||
/// 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 (!isProduction) {
|
||||
app.use(function(err, req, res, next) {
|
||||
console.log(err.stack);
|
||||
|
||||
res.status(err.status || 500);
|
||||
|
||||
res.json({'errors': {
|
||||
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.json({'errors': {
|
||||
message: err.message,
|
||||
error: {}
|
||||
}});
|
||||
});
|
||||
|
||||
// finally, let's start our server...
|
||||
var server = app.listen( process.env.PORT || 3000, function(){
|
||||
console.log('Listening on port ' + server.address().port);
|
||||
});
|
3
config/index.js
Normal file
3
config/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
secret: process.env.NODE_ENV === 'production' ? process.env.SECRET : 'secret'
|
||||
};
|
18
config/passport.js
Normal file
18
config/passport.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
var passport = require('passport');
|
||||
var LocalStrategy = require('passport-local').Strategy;
|
||||
var mongoose = require('mongoose');
|
||||
var User = mongoose.model('User');
|
||||
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'user[email]',
|
||||
passwordField: 'user[password]'
|
||||
}, function(email, password, done) {
|
||||
User.findOne({email: email}).then(function(user){
|
||||
if(!user || !user.validPassword(password)){
|
||||
return done(null, false, {errors: {'email or password': 'is invalid'}});
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
}).catch(done);
|
||||
}));
|
||||
|
56
models/Article.js
Normal file
56
models/Article.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
var mongoose = require('mongoose');
|
||||
var uniqueValidator = require('mongoose-unique-validator');
|
||||
var slug = require('slug');
|
||||
var User = mongoose.model('User');
|
||||
|
||||
var ArticleSchema = new mongoose.Schema({
|
||||
slug: {type: String, lowercase: true, unique: true},
|
||||
title: String,
|
||||
description: String,
|
||||
body: String,
|
||||
favoritesCount: {type: Number, default: 0},
|
||||
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
|
||||
tagList: [{ type: String }],
|
||||
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
|
||||
}, {timestamps: true});
|
||||
|
||||
ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});
|
||||
|
||||
ArticleSchema.pre('validate', function(next){
|
||||
if(!this.slug) {
|
||||
this.slugify();
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
ArticleSchema.methods.slugify = function() {
|
||||
this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||
};
|
||||
|
||||
ArticleSchema.methods.updateFavoriteCount = function() {
|
||||
var article = this;
|
||||
|
||||
return User.count({favorites: {$in: [article._id]}}).then(function(count){
|
||||
article.favoritesCount = count;
|
||||
|
||||
return article.save();
|
||||
});
|
||||
};
|
||||
|
||||
ArticleSchema.methods.toJSONFor = function(user){
|
||||
return {
|
||||
slug: this.slug,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
body: this.body,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
tagList: this.tagList,
|
||||
favorited: user ? user.isFavorite(this._id) : false,
|
||||
favoritesCount: this.favoritesCount,
|
||||
author: this.author.toProfileJSONFor(user)
|
||||
};
|
||||
};
|
||||
|
||||
mongoose.model('Article', ArticleSchema);
|
19
models/Comment.js
Normal file
19
models/Comment.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var mongoose = require('mongoose');
|
||||
|
||||
var CommentSchema = new mongoose.Schema({
|
||||
body: String,
|
||||
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
|
||||
article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article' }
|
||||
}, {timestamps: true});
|
||||
|
||||
// Requires population of author
|
||||
CommentSchema.methods.toJSONFor = function(user){
|
||||
return {
|
||||
id: this._id,
|
||||
body: this.body,
|
||||
createdAt: this.createdAt,
|
||||
author: this.author.toProfileJSONFor(user)
|
||||
};
|
||||
};
|
||||
|
||||
mongoose.model('Comment', CommentSchema);
|
42
models/Track.js
Normal file
42
models/Track.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
var mongoose = require('mongoose');
|
||||
var uniqueValidator = require('mongoose-unique-validator');
|
||||
var slug = require('slug');
|
||||
var User = mongoose.model('User');
|
||||
|
||||
var TrackSchema = new mongoose.Schema({
|
||||
slug: {type: String, lowercase: true, unique: true},
|
||||
title: String,
|
||||
description: String,
|
||||
body: String,
|
||||
numEvents: {type: Number, default: 0},
|
||||
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
|
||||
}, {timestamps: true});
|
||||
|
||||
TrackSchema.plugin(uniqueValidator, {message: 'is already taken'});
|
||||
|
||||
TrackSchema.pre('validate', function(next){
|
||||
if(!this.slug) {
|
||||
this.slugify();
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
TrackSchema.methods.slugify = function() {
|
||||
this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||
};
|
||||
|
||||
TrackSchema.methods.toJSONFor = function(user){
|
||||
return {
|
||||
slug: this.slug,
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
body: this.body,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
visibleForAll: user ? user.areTracksVisibleForAll : false,
|
||||
author: this.author.toProfileJSONFor(user)
|
||||
};
|
||||
};
|
||||
|
||||
mongoose.model('Track', TrackSchema);
|
105
models/User.js
Normal file
105
models/User.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
var mongoose = require('mongoose');
|
||||
var uniqueValidator = require('mongoose-unique-validator');
|
||||
var crypto = require('crypto');
|
||||
var jwt = require('jsonwebtoken');
|
||||
var secret = require('../config').secret;
|
||||
|
||||
var UserSchema = new mongoose.Schema({
|
||||
username: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9]+$/, 'is invalid'], index: true},
|
||||
email: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true},
|
||||
bio: String,
|
||||
image: String,
|
||||
favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Article' }],
|
||||
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
|
||||
areTracksVisibleForAll: Boolean,
|
||||
hash: String,
|
||||
salt: String
|
||||
}, {timestamps: true});
|
||||
|
||||
UserSchema.plugin(uniqueValidator, {message: 'is already taken.'});
|
||||
|
||||
UserSchema.methods.validPassword = function(password) {
|
||||
var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
return this.hash === hash;
|
||||
};
|
||||
|
||||
UserSchema.methods.setPassword = function(password){
|
||||
this.salt = crypto.randomBytes(16).toString('hex');
|
||||
this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
};
|
||||
|
||||
UserSchema.methods.generateJWT = function() {
|
||||
var today = new Date();
|
||||
var exp = new Date(today);
|
||||
exp.setDate(today.getDate() + 60);
|
||||
|
||||
return jwt.sign({
|
||||
id: this._id,
|
||||
username: this.username,
|
||||
exp: parseInt(exp.getTime() / 1000),
|
||||
}, secret);
|
||||
};
|
||||
|
||||
UserSchema.methods.toAuthJSON = function(){
|
||||
return {
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
token: this.generateJWT(),
|
||||
bio: this.bio,
|
||||
image: this.image
|
||||
};
|
||||
};
|
||||
|
||||
UserSchema.methods.toProfileJSONFor = function(user){
|
||||
return {
|
||||
username: this.username,
|
||||
bio: this.bio,
|
||||
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg',
|
||||
following: user ? user.isFollowing(this._id) : false
|
||||
};
|
||||
};
|
||||
|
||||
UserSchema.methods.favorite = function(id){
|
||||
if(this.favorites.indexOf(id) === -1){
|
||||
this.favorites.push(id);
|
||||
}
|
||||
|
||||
return this.save();
|
||||
};
|
||||
|
||||
UserSchema.methods.unfavorite = function(id){
|
||||
this.favorites.remove(id);
|
||||
return this.save();
|
||||
};
|
||||
|
||||
UserSchema.methods.isFavorite = function(id){
|
||||
return this.favorites.some(function(favoriteId){
|
||||
return favoriteId.toString() === id.toString();
|
||||
});
|
||||
};
|
||||
|
||||
//UserSchema.methods.areTracksVisibleForAll = function(id){
|
||||
// return this.areTracksVisibleForAll();
|
||||
//};
|
||||
|
||||
|
||||
UserSchema.methods.follow = function(id){
|
||||
if(this.following.indexOf(id) === -1){
|
||||
this.following.push(id);
|
||||
}
|
||||
|
||||
return this.save();
|
||||
};
|
||||
|
||||
UserSchema.methods.unfollow = function(id){
|
||||
this.following.remove(id);
|
||||
return this.save();
|
||||
};
|
||||
|
||||
UserSchema.methods.isFollowing = function(id){
|
||||
return this.following.some(function(followId){
|
||||
return followId.toString() === id.toString();
|
||||
});
|
||||
};
|
||||
|
||||
mongoose.model('User', UserSchema);
|
5405
package-lock.json
generated
Normal file
5405
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
43
package.json
Normal file
43
package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "conduit-node",
|
||||
"version": "1.0.0",
|
||||
"description": "conduit on node",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"mongo:start": "docker run --name realworld-mongo -p 27017:27017 mongo & sleep 5",
|
||||
"start": "node ./app.js",
|
||||
"dev": "nodemon ./app.js",
|
||||
"test": "newman run ./tests/api-tests.postman.json -e ./tests/env-api-tests.postman.json",
|
||||
"stop": "lsof -ti :3000 | xargs kill",
|
||||
"mongo:stop": "docker stop realworld-mongo && docker rm realworld-mongo"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gothinkster/productionready-node-api.git"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"body-parser": "1.15.0",
|
||||
"cors": "2.7.1",
|
||||
"ejs": "2.4.1",
|
||||
"errorhandler": "1.4.3",
|
||||
"express": "4.13.4",
|
||||
"express-jwt": "3.3.0",
|
||||
"express-session": "1.13.0",
|
||||
"jsonwebtoken": "7.1.9",
|
||||
"method-override": "2.3.5",
|
||||
"methods": "1.1.2",
|
||||
"mongoose": "5.6.11",
|
||||
"mongoose-unique-validator": "2.0.3",
|
||||
"morgan": "1.7.0",
|
||||
"passport": "0.3.2",
|
||||
"passport-local": "1.0.0",
|
||||
"request": "2.69.0",
|
||||
"slug": "0.9.1",
|
||||
"underscore": "1.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"newman": "^3.8.2",
|
||||
"nodemon": "^1.11.0"
|
||||
}
|
||||
}
|
BIN
project-logo.png
Normal file
BIN
project-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
0
public/.keep
Normal file
0
public/.keep
Normal file
22
routes/api/index.js
Normal file
22
routes/api/index.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
var router = require('express').Router();
|
||||
|
||||
router.use('/', require('./users'));
|
||||
router.use('/profiles', require('./profiles'));
|
||||
router.use('/tracks', require('./tracks'));
|
||||
router.use('/tags', require('./tags'));
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
if(err.name === 'ValidationError'){
|
||||
return res.status(422).json({
|
||||
errors: Object.keys(err.errors).reduce(function(errors, key){
|
||||
errors[key] = err.errors[key].message;
|
||||
|
||||
return errors;
|
||||
}, {})
|
||||
});
|
||||
}
|
||||
|
||||
return next(err);
|
||||
});
|
||||
|
||||
module.exports = router;
|
53
routes/api/profiles.js
Normal file
53
routes/api/profiles.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
var router = require('express').Router();
|
||||
var mongoose = require('mongoose');
|
||||
var User = mongoose.model('User');
|
||||
var auth = require('../auth');
|
||||
|
||||
// Preload user profile on routes with ':username'
|
||||
router.param('username', function(req, res, next, username){
|
||||
User.findOne({username: username}).then(function(user){
|
||||
if (!user) { return res.sendStatus(404); }
|
||||
|
||||
req.profile = user;
|
||||
|
||||
return next();
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.get('/:username', auth.optional, function(req, res, next){
|
||||
if(req.payload){
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if(!user){ return res.json({profile: req.profile.toProfileJSONFor(false)}); }
|
||||
|
||||
return res.json({profile: req.profile.toProfileJSONFor(user)});
|
||||
});
|
||||
} else {
|
||||
return res.json({profile: req.profile.toProfileJSONFor(false)});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:username/follow', auth.required, function(req, res, next){
|
||||
var profileId = req.profile._id;
|
||||
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
return user.follow(profileId).then(function(){
|
||||
return res.json({profile: req.profile.toProfileJSONFor(user)});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.delete('/:username/follow', auth.required, function(req, res, next){
|
||||
var profileId = req.profile._id;
|
||||
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
return user.unfollow(profileId).then(function(){
|
||||
return res.json({profile: req.profile.toProfileJSONFor(user)});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
12
routes/api/tags.js
Normal file
12
routes/api/tags.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var router = require('express').Router();
|
||||
var mongoose = require('mongoose');
|
||||
var Track = mongoose.model('Track');
|
||||
|
||||
// return a list of tags
|
||||
router.get('/', function(req, res, next) {
|
||||
Track.find().distinct('tagList').then(function(tags){
|
||||
return res.json({tags: tags});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
326
routes/api/tracks.js
Normal file
326
routes/api/tracks.js
Normal file
|
@ -0,0 +1,326 @@
|
|||
var router = require('express').Router();
|
||||
var mongoose = require('mongoose');
|
||||
var Track = mongoose.model('Track');
|
||||
var Comment = mongoose.model('Comment');
|
||||
var User = mongoose.model('User');
|
||||
var auth = require('../auth');
|
||||
|
||||
// Preload track objects on routes with ':track'
|
||||
router.param('track', function(req, res, next, slug) {
|
||||
Track.findOne({ slug: slug})
|
||||
.populate('author')
|
||||
.then(function (track) {
|
||||
if (!track) { return res.sendStatus(404); }
|
||||
|
||||
req.track = track;
|
||||
|
||||
return next();
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.param('comment', function(req, res, next, id) {
|
||||
Comment.findById(id).then(function(comment){
|
||||
if(!comment) { return res.sendStatus(404); }
|
||||
|
||||
req.comment = comment;
|
||||
|
||||
return next();
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.get('/', auth.optional, function(req, res, next) {
|
||||
var query = {};
|
||||
var limit = 20;
|
||||
var offset = 0;
|
||||
|
||||
if(typeof req.query.limit !== 'undefined'){
|
||||
limit = req.query.limit;
|
||||
}
|
||||
|
||||
if(typeof req.query.offset !== 'undefined'){
|
||||
offset = req.query.offset;
|
||||
}
|
||||
|
||||
if( typeof req.query.tag !== 'undefined' ){
|
||||
query.tagList = {"$in" : [req.query.tag]};
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
req.query.author ? User.findOne({username: req.query.author}) : null,
|
||||
req.query.favorited ? User.findOne({username: req.query.favorited}) : null
|
||||
]).then(function(results){
|
||||
var author = results[0];
|
||||
var favoriter = results[1];
|
||||
|
||||
if(author){
|
||||
query.author = author._id;
|
||||
}
|
||||
|
||||
if(favoriter){
|
||||
query._id = {$in: favoriter.favorites};
|
||||
} else if(req.query.favorited){
|
||||
query._id = {$in: []};
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
Track.find(query)
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
.sort({createdAt: 'desc'})
|
||||
.populate('author')
|
||||
.exec(),
|
||||
Track.count(query).exec(),
|
||||
req.payload ? User.findById(req.payload.id) : null,
|
||||
]).then(function(results){
|
||||
var tracks = results[0];
|
||||
var tracksCount = results[1];
|
||||
var user = results[2];
|
||||
|
||||
return res.json({
|
||||
tracks: tracks.map(function(track){
|
||||
return track.toJSONFor(user);
|
||||
}),
|
||||
tracksCount: tracksCount
|
||||
});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.get('/feed', auth.required, function(req, res, next) {
|
||||
var limit = 20;
|
||||
var offset = 0;
|
||||
|
||||
if(typeof req.query.limit !== 'undefined'){
|
||||
limit = req.query.limit;
|
||||
}
|
||||
|
||||
if(typeof req.query.offset !== 'undefined'){
|
||||
offset = req.query.offset;
|
||||
}
|
||||
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
if(user.following != '')
|
||||
{
|
||||
Promise.all([
|
||||
Track.find({ author: {$in: user.following}})
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
.populate('author')
|
||||
.exec(),
|
||||
Track.count({ author: {$in: user.following}})
|
||||
]).then(function(results){
|
||||
var tracks = results[0];
|
||||
var tracksCount = results[1];
|
||||
|
||||
return res.json({
|
||||
tracks: tracks.map(function(track){
|
||||
return track.toJSONFor(user);
|
||||
}),
|
||||
tracksCount: tracksCount
|
||||
});
|
||||
}).catch(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
Promise.all([
|
||||
Track.find({ author: {$in: req.payload.id}})
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
.populate('author')
|
||||
.exec(),
|
||||
Track.count({ author: {$in: req.payload.id}})
|
||||
]).then(function(results){
|
||||
var tracks = results[0];
|
||||
var tracksCount = results[1];
|
||||
|
||||
return res.json({
|
||||
tracks: tracks.map(function(track){
|
||||
return track.toJSONFor(user);
|
||||
}),
|
||||
tracksCount: tracksCount
|
||||
});
|
||||
}).catch(next);
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/', auth.required, function(req, res, next) {
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
var track = new Track(req.body.track);
|
||||
|
||||
track.author = user;
|
||||
|
||||
return track.save().then(function(){
|
||||
console.log(track.author);
|
||||
return res.json({track: track.toJSONFor(user)});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.post('/add', auth.optional, function(req, res, next) {
|
||||
console.log("Add");
|
||||
if(true)
|
||||
{
|
||||
console.log(req.body);
|
||||
console.log(req.payload);
|
||||
User.findById(req.body.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
var track = new Track(req.body.track);
|
||||
|
||||
track.author = user;
|
||||
|
||||
return track.save().then(function(){
|
||||
console.log(track.author);
|
||||
return res.json({track: track.toJSONFor(user)});
|
||||
});
|
||||
}).catch(next);
|
||||
}
|
||||
});
|
||||
|
||||
// return a track
|
||||
router.get('/:track', auth.optional, function(req, res, next) {
|
||||
Promise.all([
|
||||
req.payload ? User.findById(req.payload.id) : null,
|
||||
req.track.populate('author').execPopulate()
|
||||
]).then(function(results){
|
||||
var user = results[0];
|
||||
|
||||
return res.json({track: req.track.toJSONFor(user)});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
// update track
|
||||
router.put('/:track', auth.required, function(req, res, next) {
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if(req.track.author._id.toString() === req.payload.id.toString()){
|
||||
if(typeof req.body.track.title !== 'undefined'){
|
||||
req.track.title = req.body.track.title;
|
||||
}
|
||||
|
||||
if(typeof req.body.track.description !== 'undefined'){
|
||||
req.track.description = req.body.track.description;
|
||||
}
|
||||
|
||||
if(typeof req.body.track.body !== 'undefined'){
|
||||
req.track.body = req.body.track.body;
|
||||
}
|
||||
|
||||
if(typeof req.body.track.tagList !== 'undefined'){
|
||||
req.track.tagList = req.body.track.tagList
|
||||
}
|
||||
|
||||
req.track.save().then(function(track){
|
||||
return res.json({track: track.toJSONFor(user)});
|
||||
}).catch(next);
|
||||
} else {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// delete track
|
||||
router.delete('/:track', auth.required, function(req, res, next) {
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
if(req.track.author._id.toString() === req.payload.id.toString()){
|
||||
return req.track.remove().then(function(){
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
} else {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
// Favorite an track
|
||||
router.post('/:track/favorite', auth.required, function(req, res, next) {
|
||||
var trackId = req.track._id;
|
||||
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
return user.favorite(trackId).then(function(){
|
||||
return req.track.updateFavoriteCount().then(function(track){
|
||||
return res.json({track: track.toJSONFor(user)});
|
||||
});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
// Unfavorite an track
|
||||
router.delete('/:track/favorite', auth.required, function(req, res, next) {
|
||||
var trackId = req.track._id;
|
||||
|
||||
User.findById(req.payload.id).then(function (user){
|
||||
if (!user) { return res.sendStatus(401); }
|
||||
|
||||
return user.unfavorite(trackId).then(function(){
|
||||
return req.track.updateFavoriteCount().then(function(track){
|
||||
return res.json({track: track.toJSONFor(user)});
|
||||
});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
// return an track's comments
|
||||
router.get('/:track/comments', auth.optional, function(req, res, next){
|
||||
Promise.resolve(req.payload ? User.findById(req.payload.id) : null).then(function(user){
|
||||
return req.track.populate({
|
||||
path: 'comments',
|
||||
populate: {
|
||||
path: 'author'
|
||||
},
|
||||
options: {
|
||||
sort: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
}
|
||||
}).execPopulate().then(function(track) {
|
||||
return res.json({comments: req.track.comments.map(function(comment){
|
||||
return comment.toJSONFor(user);
|
||||
})});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
// create a new comment
|
||||
router.post('/:track/comments', auth.required, function(req, res, next) {
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if(!user){ return res.sendStatus(401); }
|
||||
|
||||
var comment = new Comment(req.body.comment);
|
||||
comment.track = req.track;
|
||||
comment.author = user;
|
||||
|
||||
return comment.save().then(function(){
|
||||
req.track.comments.push(comment);
|
||||
|
||||
return req.track.save().then(function(track) {
|
||||
res.json({comment: comment.toJSONFor(user)});
|
||||
});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.delete('/:track/comments/:comment', auth.required, function(req, res, next) {
|
||||
if(req.comment.author.toString() === req.payload.id.toString()){
|
||||
req.track.comments.remove(req.comment._id);
|
||||
req.track.save()
|
||||
.then(Comment.find({_id: req.comment._id}).remove().exec())
|
||||
.then(function(){
|
||||
res.sendStatus(204);
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
75
routes/api/users.js
Normal file
75
routes/api/users.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
var mongoose = require('mongoose');
|
||||
var router = require('express').Router();
|
||||
var passport = require('passport');
|
||||
var User = mongoose.model('User');
|
||||
var auth = require('../auth');
|
||||
|
||||
router.get('/user', auth.required, function(req, res, next){
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if(!user){ return res.sendStatus(401); }
|
||||
|
||||
return res.json({user: user.toAuthJSON()});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.put('/user', auth.required, function(req, res, next){
|
||||
User.findById(req.payload.id).then(function(user){
|
||||
if(!user){ return res.sendStatus(401); }
|
||||
|
||||
// only update fields that were actually passed...
|
||||
if(typeof req.body.user.username !== 'undefined'){
|
||||
user.username = req.body.user.username;
|
||||
}
|
||||
if(typeof req.body.user.email !== 'undefined'){
|
||||
user.email = req.body.user.email;
|
||||
}
|
||||
if(typeof req.body.user.bio !== 'undefined'){
|
||||
user.bio = req.body.user.bio;
|
||||
}
|
||||
if(typeof req.body.user.image !== 'undefined'){
|
||||
user.image = req.body.user.image;
|
||||
}
|
||||
if(typeof req.body.user.password !== 'undefined'){
|
||||
user.setPassword(req.body.user.password);
|
||||
}
|
||||
|
||||
return user.save().then(function(){
|
||||
return res.json({user: user.toAuthJSON()});
|
||||
});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
router.post('/users/login', function(req, res, next){
|
||||
if(!req.body.user.email){
|
||||
return res.status(422).json({errors: {email: "can't be blank"}});
|
||||
}
|
||||
|
||||
if(!req.body.user.password){
|
||||
return res.status(422).json({errors: {password: "can't be blank"}});
|
||||
}
|
||||
|
||||
passport.authenticate('local', {session: false}, function(err, user, info){
|
||||
if(err){ return next(err); }
|
||||
|
||||
if(user){
|
||||
user.token = user.generateJWT();
|
||||
return res.json({user: user.toAuthJSON()});
|
||||
} else {
|
||||
return res.status(422).json(info);
|
||||
}
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
router.post('/users', function(req, res, next){
|
||||
var user = new User();
|
||||
|
||||
user.username = req.body.user.username;
|
||||
user.email = req.body.user.email;
|
||||
user.setPassword(req.body.user.password);
|
||||
|
||||
user.save().then(function(){
|
||||
return res.json({user: user.toAuthJSON()});
|
||||
}).catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
27
routes/auth.js
Normal file
27
routes/auth.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
var jwt = require('express-jwt');
|
||||
var secret = require('../config').secret;
|
||||
|
||||
function getTokenFromHeader(req){
|
||||
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Token' ||
|
||||
req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
|
||||
return req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var auth = {
|
||||
required: jwt({
|
||||
secret: secret,
|
||||
userProperty: 'payload',
|
||||
getToken: getTokenFromHeader
|
||||
}),
|
||||
optional: jwt({
|
||||
secret: secret,
|
||||
userProperty: 'payload',
|
||||
credentialsRequired: false,
|
||||
getToken: getTokenFromHeader
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = auth;
|
5
routes/index.js
Normal file
5
routes/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
var router = require('express').Router();
|
||||
|
||||
router.use('/api', require('./api'));
|
||||
|
||||
module.exports = router;
|
1900
tests/api-tests.postman.json
Normal file
1900
tests/api-tests.postman.json
Normal file
File diff suppressed because it is too large
Load diff
14
tests/env-api-tests.postman.json
Normal file
14
tests/env-api-tests.postman.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "4aa60b52-97fc-456d-4d4f-14a350e95dff",
|
||||
"name": "Conduit API Tests - Environment",
|
||||
"values": [{
|
||||
"enabled": true,
|
||||
"key": "apiUrl",
|
||||
"value": "http://localhost:3000/api",
|
||||
"type": "text"
|
||||
}],
|
||||
"timestamp": 1505871382668,
|
||||
"_postman_variable_scope": "environment",
|
||||
"_postman_exported_at": "2017-09-20T01:36:34.835Z",
|
||||
"_postman_exported_using": "Postman/5.2.0"
|
||||
}
|
Loading…
Reference in a new issue