- Initial commit... A DB, some routes, and basic authentication routines...
This commit is contained in:
30
config.js
Normal file
30
config.js
Normal file
@@ -0,0 +1,30 @@
|
||||
module.exports = {
|
||||
name: 'wEvent API',
|
||||
env: process.env.NODE_ENV || 'development',
|
||||
port: process.env.PORT || 3001,
|
||||
base_url: process.env.BASE_URL || 'http://localhost:3000',
|
||||
db: {
|
||||
uri: process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/wEvent-dev',
|
||||
},
|
||||
version: '0.0.1',
|
||||
assetStoreUrl: 'https://www.google.com/',
|
||||
services: {
|
||||
apple: {},
|
||||
facebook: {
|
||||
appId: '2359355590971136',
|
||||
appSecret: 'a5703f7d0af8e694aec5bd4175a85d6b',
|
||||
},
|
||||
google: {
|
||||
appId: '442412638360-p0idffou0qlpgor7agideudb1dh10mpf.apps.googleusercontent.com',
|
||||
appSecret: 'a7fmS7Wc9Ssycr21WXdQ4TYl',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
jwt: {
|
||||
audience: 'wEvents.io',
|
||||
daysValid: 365,
|
||||
issuer: 'patrons.wEvents.io',
|
||||
secret: 'Th!sIs a d3v3lopm3nt server $#cr¢T.',
|
||||
},
|
||||
},
|
||||
};
|
||||
66
index.js
Normal file
66
index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const mongoose = require('mongoose');
|
||||
const passport = require('passport');
|
||||
const restify = require('restify');
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
const auth = require('./strategies/auth')(passport);
|
||||
|
||||
const validateJsonData = require('./lib/validateType.js');
|
||||
|
||||
/**
|
||||
* Initialize Server
|
||||
*/
|
||||
const server = restify.createServer({
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
});
|
||||
|
||||
/**
|
||||
* Middleware
|
||||
*/
|
||||
server.use(restify.plugins.acceptParser(server.acceptable));
|
||||
server.use(restify.plugins.bodyParser({
|
||||
hash: 'sha1',
|
||||
mapParams: true,
|
||||
multiples: true,
|
||||
}));
|
||||
server.use(restify.plugins.fullResponse());
|
||||
server.use(restify.plugins.gzipResponse());
|
||||
server.use(restify.plugins.queryParser({ mapParams: true }));
|
||||
server.use(auth.passport.initialize());
|
||||
|
||||
/**
|
||||
* Error checking
|
||||
*/
|
||||
server.post('*', validateJsonData);
|
||||
server.put('*', validateJsonData);
|
||||
|
||||
/**
|
||||
* Start Server, Connect to DB & Require Routes
|
||||
*/
|
||||
server.listen(config.port, () => {
|
||||
// establish connection to mongodb
|
||||
mongoose.Promise = global.Promise;
|
||||
mongoose.connect(
|
||||
config.db.uri,
|
||||
{
|
||||
useCreateIndex: true,
|
||||
useFindAndModify: false,
|
||||
useNewUrlParser: true,
|
||||
},
|
||||
);
|
||||
|
||||
const db = mongoose.connection;
|
||||
|
||||
db.on('error', (err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
db.once('open', () => {
|
||||
require('./routes')(server, auth);
|
||||
console.log(`Server is listening on port ${config.port}`);
|
||||
});
|
||||
});
|
||||
11
lib/validateType.js
Normal file
11
lib/validateType.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const validateJsonData = (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = validateJsonData;
|
||||
36
models/bid.js
Normal file
36
models/bid.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const BidSchema = new mongoose.Schema(
|
||||
{
|
||||
itemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
bidderId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
bidAmount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
maxAmount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
time: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
BidSchema.plugin(timestamps);
|
||||
|
||||
const Bid = mongoose.model('Bid', BidSchema);
|
||||
module.exports = Bid;
|
||||
44
models/common/address.js
Normal file
44
models/common/address.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongooseStringQuery = require('mongoose-string-query');
|
||||
const mongooseTimestamps = require('mongoose-timestamp');
|
||||
|
||||
const AddressSchema = new mongoose.Schema(
|
||||
{
|
||||
address1: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
address2: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
locality: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
postalCode: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [ 'billing', 'shipping' ],
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
AddressSchema.plugin(mongooseStringQuery);
|
||||
AddressSchema.plugin(mongooseTimestamps);
|
||||
|
||||
module.exports = AddressSchema;
|
||||
30
models/common/email.js
Normal file
30
models/common/email.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongooseStringQuery = require('mongoose-string-query');
|
||||
const mongooseTimestamps = require('mongoose-timestamp');
|
||||
|
||||
const EmailSchema = new mongoose.Schema(
|
||||
{
|
||||
user: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
domain: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
EmailSchema.virtual('address').get(function() {
|
||||
return this.user + '@' + this.domain;
|
||||
});
|
||||
|
||||
EmailSchema.plugin(mongooseStringQuery);
|
||||
EmailSchema.plugin(mongooseTimestamps);
|
||||
|
||||
module.exports = EmailSchema;
|
||||
29
models/common/phone.js
Normal file
29
models/common/phone.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongooseStringQuery = require('mongoose-string-query');
|
||||
const mongooseTimestamps = require('mongoose-timestamp');
|
||||
|
||||
const PhoneSchema = new mongoose.Schema(
|
||||
{
|
||||
countryCode: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '1',
|
||||
},
|
||||
number: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [ 'home', 'mobile' ],
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
PhoneSchema.plugin(mongooseStringQuery);
|
||||
PhoneSchema.plugin(mongooseTimestamps);
|
||||
|
||||
module.exports = PhoneSchema;
|
||||
16
models/constants.js
Normal file
16
models/constants.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
ITEM_TYPES: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
'auction',
|
||||
'donation',
|
||||
'event',
|
||||
'raffle',
|
||||
'membership',
|
||||
'popup',
|
||||
'ticket',
|
||||
],
|
||||
default: 'auction',
|
||||
},
|
||||
};
|
||||
147
models/event.js
Normal file
147
models/event.js
Normal file
@@ -0,0 +1,147 @@
|
||||
const config = require('../config.js');
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const PostSchema = new mongoose.Schema(
|
||||
{
|
||||
author: String,
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
isPublic: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
scheduledPost: String,
|
||||
|
||||
sendNotification: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
notificationContent: String,
|
||||
notificationLinksTo: String,
|
||||
}
|
||||
);
|
||||
|
||||
const TicketSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
capacity: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
available: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
|
||||
itemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
startSale: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
endSale: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
const EventSchema = new mongoose.Schema(
|
||||
{
|
||||
eventDate: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
startTime: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
endTime: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
tagline: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
isTicketed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ticketClasses: [ TicketSchema ],
|
||||
|
||||
showFrom: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
showUntil: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
requireLoginToSeeAuction: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
images: [{
|
||||
url: String,
|
||||
}],
|
||||
url: String,
|
||||
|
||||
posts: [ PostSchema ],
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
EventSchema.plugin(timestamps);
|
||||
|
||||
EventSchema.path('images').get(v => `${config.assetStoreUrl}${v.url}`)
|
||||
|
||||
const Event = mongoose.model('Event', EventSchema);
|
||||
module.exports = Event;
|
||||
49
models/install.js
Normal file
49
models/install.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const InstallSchema = new mongoose.Schema(
|
||||
{
|
||||
timeZone: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
deviceType: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
enum: [ 'android', 'ios', 'web' ],
|
||||
},
|
||||
badge: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
installationId: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
appIdentifier: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
localeIdentifier: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
InstallSchema.plugin(timestamps);
|
||||
|
||||
const Install = mongoose.model('Install', InstallSchema);
|
||||
module.exports = Install;
|
||||
136
models/item.js
Normal file
136
models/item.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const { ITEM_TYPES } = require('./constants.js');
|
||||
|
||||
const config = require('../config.js');
|
||||
const mongoose = require('mongoose');
|
||||
const mongooseStringQuery = require('mongoose-string-query');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const ItemSchema = new mongoose.Schema(
|
||||
{
|
||||
eventId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
donor: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
images: [{
|
||||
url: String
|
||||
}],
|
||||
|
||||
type: ITEM_TYPES,
|
||||
|
||||
quantityAvailable: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 1,
|
||||
},
|
||||
soldCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
currentPrice: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
startingPrice: {
|
||||
type: Number,
|
||||
},
|
||||
reservePrice: {
|
||||
type: Number,
|
||||
},
|
||||
estimatedValue: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
currentWinner: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
bidders: [{
|
||||
name: String,
|
||||
}],
|
||||
bidCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
bidIncrement: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
|
||||
catalogNumber: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
start: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
end: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
hideBeforeStart: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
hideAfterEnd: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
notifyOnAvailable: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
|
||||
isShippable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
shippingCost: Number,
|
||||
|
||||
organizationTake: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
ItemSchema.plugin(timestamps);
|
||||
|
||||
ItemSchema.path('images').get(v => `${config.assetStoreUrl}${v.url}`);
|
||||
|
||||
/**
|
||||
* STATICS
|
||||
*/
|
||||
ItemSchema.statics.addBatch = function(data = [], callback = () => {}) {
|
||||
|
||||
};
|
||||
|
||||
const Item = mongoose.model('Item', ItemSchema);
|
||||
module.exports = Item;
|
||||
63
models/organization.js
Normal file
63
models/organization.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const PeopleSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
bio: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
address: AddressSchema,
|
||||
email: EmailSchema,
|
||||
phone: PhoneSchema,
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
const OrganizationSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
url: String,
|
||||
address: [ AddressSchema ],
|
||||
telephone: [ TelephoneSchema ],
|
||||
|
||||
about: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
team: [ PeopleSchema ],
|
||||
board: [ PeopleSchema ],
|
||||
|
||||
privacyUrl: String,
|
||||
tosUrl: String,
|
||||
|
||||
copyright: String,
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
OrganizationSchema.plugin(timestamps);
|
||||
|
||||
const Organization = mongoose.model('Organization', OrganizationSchema);
|
||||
module.exports = Organization;
|
||||
44
models/sale.js
Normal file
44
models/sale.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const { ITEM_TYPES } = require('./constants.js');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const SaleSchema = new mongoose.Schema(
|
||||
{
|
||||
itemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
|
||||
itemType: ITEM_TYPES,
|
||||
paymentToken: String,
|
||||
|
||||
isPaid: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
isPickedUp: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
SaleSchema.plugin(timestamps);
|
||||
|
||||
const Sale = mongoose.model('Sale', SaleSchema);
|
||||
module.exports = Sale;
|
||||
284
models/user.js
Normal file
284
models/user.js
Normal file
@@ -0,0 +1,284 @@
|
||||
const crypto = require('crypto');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const mongoose = require('mongoose');
|
||||
const timestamps = require('mongoose-timestamp');
|
||||
|
||||
const config = require('../config.js');
|
||||
|
||||
const AddressSchema = require('./common/address.js');
|
||||
const PhoneSchema = require('./common/phone.js');
|
||||
|
||||
const LoginSchema = new mongoose.Schema(
|
||||
{
|
||||
method: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [ 'apple', 'facebook', 'google', 'local' ],
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
accessToken: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
secret: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
profile: {},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
const UserSchema = new mongoose.Schema(
|
||||
{
|
||||
nomDeBid: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true,
|
||||
},
|
||||
firstName: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
lastName: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
|
||||
avatar: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
address: [ AddressSchema ],
|
||||
phone: [ PhoneSchema ],
|
||||
|
||||
credentials: [ LoginSchema ],
|
||||
|
||||
organizationIdentifier: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
paymentToken: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
|
||||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isAllowedToBid: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isOrganizationEmployee: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
{ minimize: false },
|
||||
);
|
||||
|
||||
/**
|
||||
* PLUGINS
|
||||
*/
|
||||
UserSchema.plugin(timestamps);
|
||||
|
||||
|
||||
/**
|
||||
* METHODS
|
||||
*/
|
||||
UserSchema.methods.authenticate = function (username, password) {
|
||||
const user = this.model('User').findOne({ email: username });
|
||||
const strategy = user ? user.getAuthStrategy('local') : null;
|
||||
|
||||
if (strategy) {
|
||||
let hash = crypto.pbkdf2Sync(password, strategy.get('secret'), 10000, 512, 'sha512').toString('hex')
|
||||
return strategy.get('accessToken') === hash;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
UserSchema.methods.generateJWT = function (props = {}) {
|
||||
const { exp, iss } = props;
|
||||
const today = new Date();
|
||||
|
||||
let expirationDate = exp;
|
||||
if (!expirationDate) {
|
||||
expirationDate = new Date(today);
|
||||
expirationDate.setDate(today.getDate() + config.security.jwt.daysValid);
|
||||
}
|
||||
|
||||
return jwt.sign({
|
||||
sub: this._id,
|
||||
iss: iss || config.security.jwt.issuer,
|
||||
aud: config.security.jwt.audience,
|
||||
iat: parseInt(today.getTime()),
|
||||
exp: parseInt(expirationDate.getTime() / 1000, 10),
|
||||
}, config.security.jwt.secret);
|
||||
}
|
||||
|
||||
UserSchema.methods.getAuthStrategy = function (method = 'local') {
|
||||
return this.credentials.filter((strategy) => {
|
||||
return strategy.method === method;
|
||||
}).pop() || false;
|
||||
};
|
||||
|
||||
UserSchema.methods.getNomDeBid = function () {
|
||||
return this.nomDeBid || `${this.firstName} ${this.lastName.charAt(0)}`;
|
||||
};
|
||||
|
||||
UserSchema.methods.isEventManager = function () {
|
||||
return this.isOrganizationEmployee || false;
|
||||
};
|
||||
|
||||
UserSchema.methods.isRegistrationVerified = function () {
|
||||
return this.isVerified || false;
|
||||
};
|
||||
|
||||
UserSchema.methods.setPassword = function (password, callback = () => {}) {
|
||||
const hasLocalStrategy = !!this.credentials.length &&
|
||||
!!this.credentials.filter(strategy => strategy.method === 'local').length;
|
||||
|
||||
const salt = crypto.randomBytes(16).toString('hex');
|
||||
const accessToken = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
|
||||
|
||||
const strategy = {
|
||||
accessToken,
|
||||
method: 'local',
|
||||
secret: salt,
|
||||
};
|
||||
|
||||
if (hasLocalStrategy) {
|
||||
this.model('User').findOneAndUpdate(
|
||||
{ _id: this._id, 'credentials.method': 'local' },
|
||||
{ $set: { 'credentials.$': strategy } },
|
||||
{ upsert: true },
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasLocalStrategy) {
|
||||
this.credentials.push(strategy);
|
||||
this.save(callback);
|
||||
}
|
||||
};
|
||||
|
||||
UserSchema.methods.toAuthJSON = function () {
|
||||
const hasNomDeBid = !!this.nomDeBid;
|
||||
const nomDeBid = this.getNomDeBid();
|
||||
|
||||
return {
|
||||
email: this.email,
|
||||
token: this.generateJWT(),
|
||||
user: {
|
||||
nomDeBid: nomDeBid,
|
||||
email: this.email,
|
||||
firstName: this.firstName,
|
||||
lastName: this.lastName,
|
||||
avatar: this.avatar,
|
||||
isAllowedToBid: this.isAllowedToBid,
|
||||
isOrganizationEmployee: this.isOrganizationEmployee,
|
||||
generatedNomDeBid: !hasNomDeBid,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
UserSchema.methods.validatePassword = function (password) {
|
||||
const strategy = this.getAuthStrategy('local');
|
||||
|
||||
if (strategy) {
|
||||
let hash = crypto.pbkdf2Sync(password, strategy.secret, 10000, 512, 'sha512').toString('hex');
|
||||
return strategy.accessToken === hash;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* STATICS
|
||||
*/
|
||||
UserSchema.statics.findOrCreate = function (filter = {}, profile = {}, callback = () => {}) {
|
||||
const self = this;
|
||||
|
||||
this.findOne(filter, function(err,result) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
self.create(profile, (err, result) => callback(err, result));
|
||||
}else{
|
||||
callback(err, result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
UserSchema.statics.findOneAndUpdateOrCreate = function (
|
||||
filter = {},
|
||||
strategy = {},
|
||||
profile = {},
|
||||
callback = () => {},
|
||||
) {
|
||||
const self = this;
|
||||
|
||||
this.findOne(filter, function(err, result) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
self.create(
|
||||
{
|
||||
strategy: [ strategy ],
|
||||
...profile
|
||||
},
|
||||
(err, result) => callback(err, result),
|
||||
);
|
||||
} else {
|
||||
const hasStrategy = !!result.credentials.length &&
|
||||
!!result.credentials.filter(auth => auth.method === strategy.method).length;
|
||||
|
||||
if (hasStrategy) {
|
||||
self.model('User').findOneAndUpdate(
|
||||
{ _id: result._id, 'credentials.method': strategy.method },
|
||||
{ $set: { 'credentials.$': strategy } },
|
||||
{ upsert: true },
|
||||
callback,
|
||||
);
|
||||
} else {
|
||||
result.credentials.push(strategy);
|
||||
result.save(callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* PATH OPERATIONS
|
||||
*/
|
||||
UserSchema.path('avatar').get(v => `${config.assetStoreUrl}${v}`);
|
||||
|
||||
/**
|
||||
* Export
|
||||
*/
|
||||
const User = mongoose.model('User', UserSchema);
|
||||
|
||||
module.exports = User;
|
||||
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "0.0.1",
|
||||
"description": "The wEvent API and DB server",
|
||||
"main": "index.js",
|
||||
"author": "mike@fitz.guru",
|
||||
"license": "MIT",
|
||||
"notes": "b'VJ!4a{L(8T(CvG8BKGj).]",
|
||||
"dependencies": {
|
||||
"api-query-params": "^4.13.0",
|
||||
"crypto": "^1.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mongoose": "^5.6.0",
|
||||
"mongoose-find-or-create": "^1.3.1",
|
||||
"mongoose-string-query": "^0.2.7",
|
||||
"mongoose-timestamp": "^0.6.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth": "^2.0.0",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"restify": "^8.3.3",
|
||||
"restify-errors": "^8.0.0",
|
||||
"restify-jwt-community": "^1.0.13",
|
||||
"restify-plugins": "^1.6.0"
|
||||
}
|
||||
}
|
||||
92
routes/auth.js
Normal file
92
routes/auth.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const config = require('../config');
|
||||
|
||||
const handlePassportResponse = (req, res, next) => (err, passportUser, info) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
const isVerifiedUser = passportUser.isRegistrationVerified();
|
||||
if (passportUser && isVerifiedUser) {
|
||||
const user = passportUser;
|
||||
user.token = passportUser.generateJWT();
|
||||
return res.send({ ...user.toAuthJSON() });
|
||||
} else if (passportUser && !isVerifiedUser){
|
||||
return res.send({
|
||||
registrationSuccess: true,
|
||||
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
|
||||
});
|
||||
}
|
||||
|
||||
return res.send(400, info);
|
||||
};
|
||||
|
||||
module.exports = function (server, auth) {
|
||||
const { passport } = auth;
|
||||
|
||||
/* Local Auth */
|
||||
server.post('/auth', (req, res, next) => {
|
||||
const { body: { username = null, password = null } = {} } = req;
|
||||
|
||||
if (!username || !password) {
|
||||
let errors = {};
|
||||
|
||||
if (!username) {
|
||||
errors.username = 'is required';
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
errors.password = 'is required';
|
||||
}
|
||||
|
||||
return res.send(422, { errors });
|
||||
}
|
||||
|
||||
const callback = handlePassportResponse(req, res, next);
|
||||
return passport.authenticate('local', { session: false }, callback)(req, res, next);
|
||||
});
|
||||
|
||||
/**
|
||||
* SERVICES
|
||||
*/
|
||||
|
||||
/* Google */
|
||||
server.get(
|
||||
'/auth/google',
|
||||
passport.authenticate('google', { scope: 'profile email', session: false }),
|
||||
);
|
||||
|
||||
server.get(
|
||||
'/auth/google/callback',
|
||||
(req, res, next) => {
|
||||
const callback = handlePassportResponse(req, res, next);
|
||||
return passport.authenticate(
|
||||
'google',
|
||||
{ failureRedirect: '/login' },
|
||||
callback,
|
||||
)(req, res, next);
|
||||
},
|
||||
);
|
||||
|
||||
/* Facebook */
|
||||
server.get(
|
||||
'/auth/facebook',
|
||||
passport.authenticate('facebook', {
|
||||
scope: ['email', 'public_profile'],
|
||||
session: false,
|
||||
}),
|
||||
);
|
||||
|
||||
server.get(
|
||||
'/auth/facebook/callback',
|
||||
(req, res, next) => {
|
||||
const callback = handlePassportResponse(req, res, next);
|
||||
return passport.authenticate(
|
||||
'facebook',
|
||||
{ failureRedirect: '/login' },
|
||||
callback,
|
||||
)(req, res, next);
|
||||
}
|
||||
);
|
||||
};
|
||||
110
routes/bids.js
Normal file
110
routes/bids.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const Bid = require('../models/bid');
|
||||
|
||||
module.exports = function(server) {
|
||||
server.post('/bids', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
let bid = new Bid(data);
|
||||
bid.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/bids', (req, res, next) => {
|
||||
Bid.apiQuery(req.params, function(err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/bids/:bid_id', (req, res, next) => {
|
||||
Bid.findOne({ _id: req.params.bid_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/bids/:bid_id', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.bid_id });
|
||||
}
|
||||
|
||||
Bid.findOne({ _id: req.params.bid_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Bid.update({ _id: data._id }, data, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/bids/:bid_id', (req, res, next) => {
|
||||
Bid.deleteOne({ _id: req.params.bid_id }, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
110
routes/events.js
Normal file
110
routes/events.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const Event = require('../models/event');
|
||||
|
||||
module.exports = function(server) {
|
||||
server.post('/events', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
let event = new Event(data);
|
||||
event.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/events', (req, res, next) => {
|
||||
Event.apiQuery(req.params, function(err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/events/:event_id', (req, res, next) => {
|
||||
Event.findOne({ _id: req.params.event_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/events/:event_id', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.event_id });
|
||||
}
|
||||
|
||||
Event.findOne({ _id: req.params.event_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Event.update({ _id: data._id }, data, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/events/:event_id', (req, res, next) => {
|
||||
Event.deleteOne({ _id: req.params.event_id }, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
9
routes/index.js
Normal file
9
routes/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = function(server, auth) {
|
||||
require('./auth.js')(server, auth);
|
||||
require('./bids.js')(server, auth);
|
||||
require('./events.js')(server, auth);
|
||||
require('./installs.js')(server, auth);
|
||||
require('./items.js')(server, auth);
|
||||
require('./sales.js')(server, auth);
|
||||
require('./users.js')(server, auth);
|
||||
};
|
||||
110
routes/installs.js
Normal file
110
routes/installs.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const Install = require('../models/install');
|
||||
|
||||
module.exports = function(server) {
|
||||
server.post('/installs', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
let install = new Install(data);
|
||||
install.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/installs', (req, res, next) => {
|
||||
Install.apiQuery(req.params, function(err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/installs/:install_id', (req, res, next) => {
|
||||
Install.findOne({ _id: req.params.install_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/installs/:install_id', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.install_id });
|
||||
}
|
||||
|
||||
Install.findOne({ _id: req.params.install_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Install.update({ _id: data._id }, data, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/installs/:install_id', (req, res, next) => {
|
||||
Install.deleteOne({ _id: req.params.install_id }, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
110
routes/items.js
Normal file
110
routes/items.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const Item = require('../models/item');
|
||||
|
||||
module.exports = function(server) {
|
||||
server.post('/items', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
let item = new Item(data);
|
||||
item.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/items', (req, res, next) => {
|
||||
Item.apiQuery(req.params, function(err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/items/:item_id', (req, res, next) => {
|
||||
Item.findOne({ _id: req.params.item_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/items/:item_id', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.item_id });
|
||||
}
|
||||
|
||||
Item.findOne({ _id: req.params.item_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Item.update({ _id: data._id }, data, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/items/:item_id', (req, res, next) => {
|
||||
Item.deleteOne({ _id: req.params.item_id }, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
116
routes/sales.js
Normal file
116
routes/sales.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Module Dependencies
|
||||
*/
|
||||
const errors = require('restify-errors');
|
||||
|
||||
/**
|
||||
* Model Schema
|
||||
*/
|
||||
const Sale = require('../models/sale');
|
||||
|
||||
module.exports = function(server) {
|
||||
server.post('/sales', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
let sale = new Sale(data);
|
||||
sale.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/sales', (req, res, next) => {
|
||||
Sale.apiQuery(req.params, function(err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/sales/:sale_id', (req, res, next) => {
|
||||
Sale.findOne({ _id: req.params.sale_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/sales/:sale_id', (req, res, next) => {
|
||||
if (!req.is('application/json')) {
|
||||
return next(
|
||||
new errors.InvalidContentError("Expects 'application/json'"),
|
||||
);
|
||||
}
|
||||
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.sale_id });
|
||||
}
|
||||
|
||||
Sale.findOne({ _id: req.params.sale_id }, function(err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Sale.update({ _id: data._id }, data, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/sales/:sale_id', (req, res, next) => {
|
||||
Sale.deleteOne({ _id: req.params.sale_id }, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err.errors.name.message),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
42
routes/signup.js
Normal file
42
routes/signup.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const User = require('../models/user');
|
||||
|
||||
module.exports = function (server, auth) {
|
||||
const { passport } = auth;
|
||||
|
||||
server.post('/signup', (req, res, next) => {
|
||||
const { body: { user = null } = {} } = req;
|
||||
|
||||
let errors = {};
|
||||
let errorCount = 0;
|
||||
if (!user) {
|
||||
errors.user = 'is required - can\'t make something from nothing...'';
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
if (errorCount) {
|
||||
return res.send(422, { errors });
|
||||
}
|
||||
|
||||
User.register(user, (err, user, info) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
if (info) {
|
||||
res.send(200, {
|
||||
registrationSuccess: false,
|
||||
nextSteps: 'Please fix the problems indicated and try again.'
|
||||
...info
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
res.send(200, {
|
||||
registrationSuccess: true,
|
||||
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
169
routes/users.js
Normal file
169
routes/users.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const aqp = require('api-query-params');
|
||||
const errors = require('restify-errors');
|
||||
|
||||
const User = require('../models/user');
|
||||
const { PUBLIC, PROTECTED } = require('../strategies/selects/user');
|
||||
|
||||
module.exports = function (server, auth) {
|
||||
server.post('/users', auth.manager, (req, res, next) => {
|
||||
let { password = null, ...data } = req.body || {};
|
||||
|
||||
let user = new User(data);
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(new errors.InternalError(err.message));
|
||||
next();
|
||||
}
|
||||
|
||||
if (password) {
|
||||
user.setPassword(password);
|
||||
}
|
||||
|
||||
res.send(201);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/users', auth.manager, (req, res, next) => {
|
||||
const { filter } = aqp(req.query);
|
||||
const select = req.user.isManager ? PROTECTED : PUBLIC;
|
||||
|
||||
User.find(filter, select, function (err, docs) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(docs);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.get('/users/:user_id', auth.managerOrSelf, (req, res, next) => {
|
||||
const select = req.user.isManager ? PROTECTED : PUBLIC;
|
||||
|
||||
User.findOne({ _id: req.params.user_id }, select, function (err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(doc);
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/users/:user_id', auth.managerOrSelf, (req, res, next) => {
|
||||
let data = req.body || {};
|
||||
|
||||
if (!data._id) {
|
||||
data = Object.assign({}, data, { _id: req.params.user_id });
|
||||
}
|
||||
|
||||
User.findOne({ _id: req.params.user_id }, function (err, doc) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
} else if (!doc) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The resource you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
User.update({ _id: data._id }, data, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/users/password/:user_id/:reset_token?', function (req, res, next) {
|
||||
let {
|
||||
currentPassword = null,
|
||||
newPassword = null,
|
||||
...data
|
||||
} = req.body || {};
|
||||
|
||||
if (!newPassword) {
|
||||
return next(
|
||||
new errors.InvalidContentError('Password cannot be empty.'),
|
||||
);
|
||||
}
|
||||
|
||||
let filter = { _id: req.params.user_id };
|
||||
let resetToken = req.params.reset_token || null;
|
||||
if (resetToken) {
|
||||
fiter.resetToken = resetToken;
|
||||
}
|
||||
|
||||
User.findOne(filter, function (err, user) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return next(
|
||||
new errors.ResourceNotFoundError(
|
||||
'The user you requested could not be found.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!resetToken &&
|
||||
!!user.getAuthStrategy('local') &&
|
||||
!user.validatePassword(currentPassword)
|
||||
) {
|
||||
return next(
|
||||
new errors.InvalidContentError(
|
||||
'The current password was incorrect.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
user.setPassword(newPassword, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(200, data);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.del('/users/:user_id', auth.manager, (req, res, next) => {
|
||||
User.deleteOne({ _id: req.params.user_id }, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return next(
|
||||
new errors.InvalidContentError(err),
|
||||
);
|
||||
}
|
||||
|
||||
res.send(204);
|
||||
next();
|
||||
});
|
||||
});
|
||||
};
|
||||
5
strategies/auth/apple.js
Normal file
5
strategies/auth/apple.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const passport = require('passport');
|
||||
|
||||
module.exports = function(passport) {
|
||||
return passport;
|
||||
};
|
||||
46
strategies/auth/facebook.js
Normal file
46
strategies/auth/facebook.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const passport = require('passport');
|
||||
const FacebookStrategy = require('passport-facebook').Strategy;
|
||||
|
||||
const config = require('../../config');
|
||||
const User = require('../../models/user');
|
||||
|
||||
module.exports = function(passport) {
|
||||
passport.use(new FacebookStrategy(
|
||||
{
|
||||
clientID: config.services.facebook.appId,
|
||||
clientSecret: config.services.facebook.appSecret,
|
||||
callbackURL: 'http://localhost:3001/auth/facebook/callback',
|
||||
profileFields: ['id', 'email', 'first_name', 'last_name', 'picture'],
|
||||
},
|
||||
(accessToken, refreshToken, profile, done) => {
|
||||
const {
|
||||
email,
|
||||
first_name: firstName,
|
||||
id: userId,
|
||||
last_name: lastName,
|
||||
picture: { data: { url = null } = {} } = {},
|
||||
} = profile._json;
|
||||
const avatar = url;
|
||||
|
||||
User.findOneAndUpdateOrCreate(
|
||||
{
|
||||
email,
|
||||
},
|
||||
{
|
||||
accessToken,
|
||||
method: profile.provider,
|
||||
userId,
|
||||
},
|
||||
{
|
||||
avatar,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
},
|
||||
(err, user) => {
|
||||
return done(err, user, { accessToken, refreshToken });
|
||||
}
|
||||
);
|
||||
}
|
||||
));
|
||||
};
|
||||
40
strategies/auth/google.js
Normal file
40
strategies/auth/google.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const passport = require('passport');
|
||||
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
|
||||
|
||||
const config = require('../../config');
|
||||
const User = require('../../models/user');
|
||||
|
||||
module.exports = function(passport) {
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: config.services.google.appId,
|
||||
clientSecret: config.services.google.appSecret,
|
||||
callbackURL: "http://www.example.com/auth/google/callback",
|
||||
},
|
||||
(accessToken, refreshToken, profile, callback) => {
|
||||
const googleUser = profile.getBasicProfile();
|
||||
|
||||
User.findOrCreate(
|
||||
{
|
||||
email: googleUser.getEmail(),
|
||||
'credentials.method': 'google',
|
||||
'credentials.userId': googleUser.getId(),
|
||||
},
|
||||
{
|
||||
avatar: googleUser.getImageUrl(),
|
||||
email: googleUser.getEmail(),
|
||||
firstName: googleUser.getGivenName(),
|
||||
lastName: googleUser.getFamilyName(),
|
||||
credentials: [{
|
||||
accessToken,
|
||||
userId: googleUser.getId(),
|
||||
method: 'facebook',
|
||||
profile,
|
||||
}],
|
||||
},
|
||||
(err, user) => {
|
||||
return done(err, user, { accessToken, refreshToken });
|
||||
}
|
||||
);
|
||||
}
|
||||
));
|
||||
};
|
||||
82
strategies/auth/index.js
Normal file
82
strategies/auth/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const createRequestUserObject = (req, user) => ({
|
||||
isGuest: !(user && user.id),
|
||||
isManager: user && user.isEventManager(),
|
||||
isSelf: user && user.id === req.params.user_id,
|
||||
record: user || null,
|
||||
});
|
||||
|
||||
const authenticateBasic = (passport) => (req, res, next) => (
|
||||
passport.authenticate('jwt', { session: false }, (err, user, info) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
req.user = createRequestUserObject(req, user);
|
||||
next();
|
||||
})(req, res, next)
|
||||
);
|
||||
|
||||
const authenticateEventManager = (passport) => (req, res, next) => (
|
||||
passport.authenticate('jwt', { session: false }, (err, user, info) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
const record = createRequestUserObject(req, user);
|
||||
|
||||
if (!user || !record.isManager) {
|
||||
return res.send(401);
|
||||
}
|
||||
|
||||
req.user = record;
|
||||
next();
|
||||
})(req, res, next)
|
||||
);
|
||||
|
||||
const authenticateEventManagerOrSelf = (passport) => (req, res, next) => (
|
||||
passport.authenticate('jwt', { session: false }, (err, user, info) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
const record = createRequestUserObject(req, user);
|
||||
|
||||
if (user && (!record.isManager && !record.isSelf)) {
|
||||
return res.send(401);
|
||||
}
|
||||
|
||||
req.user = record;
|
||||
next();
|
||||
})(req, res, next)
|
||||
);
|
||||
|
||||
const authenticateSecure = (passport) => (req, res, next) => (
|
||||
passport.authenticate('jwt', { session: false }, (err, user, info) => {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.send(401);
|
||||
}
|
||||
|
||||
req.user = createRequestUserObject(req, user);
|
||||
next();
|
||||
})(req, res, next)
|
||||
);
|
||||
|
||||
module.exports = function (passport) {
|
||||
require('./apple.js')(passport);
|
||||
require('./facebook.js')(passport);
|
||||
require('./google.js')(passport);
|
||||
require('./jwt.js')(passport);
|
||||
require('./local.js')(passport);
|
||||
|
||||
return {
|
||||
basic: authenticateBasic(passport),
|
||||
manager: authenticateEventManager(passport),
|
||||
managerOrSelf: authenticateEventManagerOrSelf(passport),
|
||||
passport,
|
||||
secure: authenticateSecure(passport),
|
||||
};
|
||||
};
|
||||
30
strategies/auth/jwt.js
Normal file
30
strategies/auth/jwt.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const passport = require('passport');
|
||||
const JwtStrategy = require('passport-jwt').Strategy;
|
||||
const ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
|
||||
const config = require('../../config');
|
||||
const User = require('../../models/user');
|
||||
|
||||
module.exports = function(passport) {
|
||||
passport.use(new JwtStrategy(
|
||||
{
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: config.security.jwt.secret,
|
||||
issuer: config.security.jwt.issuer,
|
||||
audience: config.security.jwt.audience,
|
||||
},
|
||||
(jwt_payload, done) => {
|
||||
User.findOne({ _id: jwt_payload.sub }, (err, user) => {
|
||||
if (err) {
|
||||
return done(err, false);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return done(null, user);
|
||||
}
|
||||
|
||||
return done(null, false);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
24
strategies/auth/local.js
Normal file
24
strategies/auth/local.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const passport = require('passport');
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
const User = require('../../models/user');
|
||||
|
||||
module.exports = function(passport) {
|
||||
passport.use(new LocalStrategy(
|
||||
{
|
||||
usernameField: 'username',
|
||||
passwordField: 'password',
|
||||
},
|
||||
(username, password, done) => {
|
||||
User.findOne({ email: username }, (err, user) => {
|
||||
if (err) { return done(err); }
|
||||
|
||||
if (!user || !user.validatePassword(password)) {
|
||||
return done(null, false, { message: 'Incorrect username or password.' });
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
};
|
||||
10
strategies/selects/user.js
Normal file
10
strategies/selects/user.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
PUBLIC: {
|
||||
credentials: 0,
|
||||
isVerified: 0,
|
||||
organizationIdentifier: 0,
|
||||
paymentToken: 0,
|
||||
},
|
||||
PROTECTED: {},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user