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
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
.DS_Store
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
npm-debug.log*
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
*.pid
|
*.pid
|
||||||
*.seed
|
*.seed
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
lib-cov
|
lib-cov
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
.grunt
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
# node-waf configuration
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directory
|
||||||
node_modules/
|
node_modules
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
# Optional npm cache directory
|
||||||
.npm
|
.npm
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
# Optional REPL history
|
||||||
.node_repl_history
|
.node_repl_history
|
||||||
|
|
||||||
# Output of 'npm pack'
|
.idea
|
||||||
*.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
|
|
||||||
|
|
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