mirror of
https://github.com/koodiklinikka/koodiklinikka.fi-api.git
synced 2026-01-26 03:34:03 +00:00
Membership registration & payment API (#4)
* remove newrelic from use in devenv * add endpoint for membership payments * fix some wierd spaces * minor code styling and logging stuff * replace non-breaking spaces with normal ones * remove duplicate function * minor code styling * add functionality for writing new member to google sheets * add config example * update example config, start using config in google credentials * remove var creds from google sheets auth * rename config.example to config.template and fix readme * add async and google-spreadsheet packages * rename workingWithRows to addRow * return missing header from readme * minor code styling * flatten google config structure, add address fields * add request validation to membership endpoint * fix config field names * more error handling, fix indentation
This commit is contained in:
committed by
Riku Rouvila
parent
181a48c0f6
commit
f783a27045
@@ -1,5 +1,32 @@
|
|||||||
{
|
{
|
||||||
"all": {
|
"development": {
|
||||||
|
"stripe": {
|
||||||
|
"secretKey": ""
|
||||||
|
},
|
||||||
|
"slack": {
|
||||||
|
"token": "",
|
||||||
|
"privateChannel": "",
|
||||||
|
"publicChannel": ""
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"token": ""
|
||||||
|
},
|
||||||
|
"twitter": {
|
||||||
|
"consumerKey": "",
|
||||||
|
"consumerSecret": "",
|
||||||
|
"token": "",
|
||||||
|
"tokenSecret": ""
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"spreadsheetId": "",
|
||||||
|
"clientEmail": "",
|
||||||
|
"privateKey": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"stripe": {
|
||||||
|
"secretKey": ""
|
||||||
|
},
|
||||||
"slack": {
|
"slack": {
|
||||||
"token": "",
|
"token": "",
|
||||||
"privateChannel": "",
|
"privateChannel": "",
|
||||||
@@ -14,5 +41,7 @@
|
|||||||
"token": "",
|
"token": "",
|
||||||
"tokenSecret": ""
|
"tokenSecret": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"all": {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
index.js
10
index.js
@@ -1,4 +1,3 @@
|
|||||||
require('newrelic');
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
@@ -7,9 +6,13 @@ var cors = require('cors');
|
|||||||
var bodyParser = require('body-parser');
|
var bodyParser = require('body-parser');
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
|
if(app.get('env') != 'development') {
|
||||||
|
require('newrelic');
|
||||||
|
}
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({extended: true}));
|
app.use(bodyParser.urlencoded({extended: true}));
|
||||||
app.use(cors());
|
app.use(cors({credentials: true, origin: true}));
|
||||||
|
|
||||||
morgan.token('body', function(req) {
|
morgan.token('body', function(req) {
|
||||||
return JSON.stringify(req.body);
|
return JSON.stringify(req.body);
|
||||||
@@ -20,8 +23,9 @@ app.use(morgan(':method :url :status :response-time ms - :res[content-length] :b
|
|||||||
require('./routes/invite')(app);
|
require('./routes/invite')(app);
|
||||||
require('./routes/members')(app);
|
require('./routes/members')(app);
|
||||||
require('./routes/feeds')(app);
|
require('./routes/feeds')(app);
|
||||||
|
require('./routes/membership')(app);
|
||||||
|
|
||||||
app.use(function(err, req, res, next) {
|
app.use(function(err, req, res, next) {
|
||||||
/*jshint unused:false*/
|
/*jshint unused:false*/
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).send('Internal server error');
|
res.status(500).send('Internal server error');
|
||||||
|
|||||||
@@ -19,14 +19,19 @@
|
|||||||
"homepage": "https://github.com/koodiklinikka/koodiklinikka.fi-api",
|
"homepage": "https://github.com/koodiklinikka/koodiklinikka.fi-api",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apicache": "0.0.12",
|
"apicache": "0.0.12",
|
||||||
|
"async": "^2.5.0",
|
||||||
"bluebird": "^2.9.3",
|
"bluebird": "^2.9.3",
|
||||||
"body-parser": "^1.10.1",
|
"body-parser": "^1.10.1",
|
||||||
"cors": "^2.7.1",
|
"cors": "^2.7.1",
|
||||||
"express": "^4.11.0",
|
"express": "^4.11.0",
|
||||||
|
"google-spreadsheet": "^2.0.4",
|
||||||
|
"joi": "^10.6.0",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
|
"moment": "^2.18.1",
|
||||||
"morgan": "^1.5.1",
|
"morgan": "^1.5.1",
|
||||||
"newrelic": "^1.18.0",
|
"newrelic": "^1.18.0",
|
||||||
"node-twitter": "0.5.2",
|
"node-twitter": "0.5.2",
|
||||||
|
"stripe": "^4.23.1",
|
||||||
"superagent": "^0.21.0",
|
"superagent": "^0.21.0",
|
||||||
"validator": "^3.27.0"
|
"validator": "^3.27.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module.exports = function (app) {
|
|||||||
* GET /feeds
|
* GET /feeds
|
||||||
* Endpoint for fetching different information feeds (Twitter, GitHub etc.)
|
* Endpoint for fetching different information feeds (Twitter, GitHub etc.)
|
||||||
*/
|
*/
|
||||||
app.get('/feeds', cache('10 minutes'), function(req, res, next) {
|
app.get('/feeds', cache('10 minutes'), function(req, res, next) {
|
||||||
Promise.props({
|
Promise.props({
|
||||||
twitter: twitter.getTweets(40),
|
twitter: twitter.getTweets(40),
|
||||||
github: github.getEvents(40)
|
github: github.getEvents(40)
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ module.exports = function (app) {
|
|||||||
* Endpoint for sending invitations automatically
|
* Endpoint for sending invitations automatically
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.post('/invites', function(req, res, next) {
|
app.post('/invites', function(req, res, next) {
|
||||||
|
|
||||||
if(!validator.isEmail(req.body.email)) {
|
if(!validator.isEmail(req.body.email)) {
|
||||||
return res.status(400).send('invalid_email');
|
return res.status(400).send('invalid_email');
|
||||||
}
|
}
|
||||||
|
|
||||||
function success() {
|
function success() {
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ module.exports = function (app) {
|
|||||||
var message = 'User ' + user.login + ' invited to GitHub organization.'
|
var message = 'User ' + user.login + ' invited to GitHub organization.'
|
||||||
slack.createMessage(message);
|
slack.createMessage(message);
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
var message = 'Creating GitHub invitation failed for: ' + req.body.email + ' reason: ' + err;
|
var message = 'Creating GitHub invitation failed for: ' + req.body.email + ' reason: ' + err;
|
||||||
slack.createMessage(message);
|
slack.createMessage(message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ module.exports = function (app) {
|
|||||||
* GET /members
|
* GET /members
|
||||||
* Endpoint for fetching GitHub org public members
|
* Endpoint for fetching GitHub org public members
|
||||||
*/
|
*/
|
||||||
|
app.get('/members', cache('3 hours'), function(req, res, next) {
|
||||||
app.get('/members', cache('3 hours'), function(req, res, next) {
|
|
||||||
github.getMembers().then(function(data) {
|
github.getMembers().then(function(data) {
|
||||||
res.status(200).send(data);
|
res.status(200).send(data);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
@@ -21,8 +20,7 @@ module.exports = function (app) {
|
|||||||
* Post /members
|
* Post /members
|
||||||
* Endpoint for getting an invite to GitHub organization
|
* Endpoint for getting an invite to GitHub organization
|
||||||
*/
|
*/
|
||||||
|
app.post('/members', function(req, res, next) {
|
||||||
app.post('/members', function(req, res, next) {
|
|
||||||
if(!req.body.username) {
|
if(!req.body.username) {
|
||||||
return res.status(400).send('invalid_username');
|
return res.status(400).send('invalid_username');
|
||||||
}
|
}
|
||||||
|
|||||||
103
routes/membership.js
Normal file
103
routes/membership.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
var GoogleSpreadsheet = require('google-spreadsheet');
|
||||||
|
var async = require('async');
|
||||||
|
var moment = require('moment');
|
||||||
|
var Joi = require('joi');
|
||||||
|
|
||||||
|
var slack = require('../services/slack');
|
||||||
|
var config = require('../lib/config');
|
||||||
|
var stripe = require('stripe')(config.stripe.secretKey);
|
||||||
|
var validateRequest = require('../utils/validateRequest');
|
||||||
|
|
||||||
|
function log(message) {
|
||||||
|
console.log(message);
|
||||||
|
slack.createMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNewMemberToSheets(data, callback) {
|
||||||
|
var {name, email, address, postcode, city, handle} = data;
|
||||||
|
var doc = new GoogleSpreadsheet(config.google.spreadsheetId);
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function setAuth(cb) {
|
||||||
|
console.log('Start Google Spreadsheed auth.');
|
||||||
|
doc.useServiceAccountAuth({
|
||||||
|
client_email: config.google.clientEmail,
|
||||||
|
private_key: config.google.privateKey
|
||||||
|
}, (err) => cb(err));
|
||||||
|
},
|
||||||
|
function getInfoAndWorksheets(cb) {
|
||||||
|
console.log('Start Google Spreadsheet info fetch.');
|
||||||
|
doc.getInfo(function(err, info) {
|
||||||
|
if(err) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
cb(null, info.worksheets[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function addRow(sheet, cb) {
|
||||||
|
console.log('Start Google Spreadsheet row write.');
|
||||||
|
sheet.addRow({
|
||||||
|
'jäsenmaksu': true,
|
||||||
|
'koko nimi': name,
|
||||||
|
'liittymispäivä': moment().format('DD.MM.YYYY'),
|
||||||
|
'lisääjä': 'Koodiklinikka.fi-api',
|
||||||
|
'katuosoite': address,
|
||||||
|
'postinumero': postcode,
|
||||||
|
'paikkakunta': city,
|
||||||
|
'slack': handle,
|
||||||
|
'sähköposti': email
|
||||||
|
}, cb);
|
||||||
|
}
|
||||||
|
], callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
/*
|
||||||
|
* POST /membership
|
||||||
|
* Endpoint for adding a new member to the association
|
||||||
|
*/
|
||||||
|
|
||||||
|
const schema = Joi.object().keys({
|
||||||
|
userInfo: Joi.object().keys({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
email: Joi.string().email().required(),
|
||||||
|
handle: Joi.string().required(),
|
||||||
|
address: Joi.string().required(),
|
||||||
|
city: Joi.string().required(),
|
||||||
|
postcode: Joi.string().required()
|
||||||
|
}),
|
||||||
|
stripeToken: Joi.string().required()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/membership', validateRequest(schema), function(req, res, next) {
|
||||||
|
|
||||||
|
console.log(`Start membership addition with body: ${JSON.stringify(req.body)}`);
|
||||||
|
|
||||||
|
stripe.charges.create({
|
||||||
|
amount: config.membership.price,
|
||||||
|
card: req.body.stripeToken,
|
||||||
|
currency: 'eur',
|
||||||
|
description: `Koodiklinikka ry jäsenyys: ${req.body.name}`
|
||||||
|
}, function(err, charge) {
|
||||||
|
if (err) {
|
||||||
|
log(`Membership payment FAILED for: ${JSON.stringify(req.body)}. Reason: ${err.message}`);
|
||||||
|
res.status(500).send('payment_error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Membership payment SUCCESSFUL for: ${JSON.stringify(req.body)}`);
|
||||||
|
addNewMemberToSheets(req.body.userInfo, (err) => {
|
||||||
|
if(err) {
|
||||||
|
log(`Storing membership info FAILED for: ${JSON.stringify(req.body)}. Reason: ${err.message}`);
|
||||||
|
res.status(500).send('membership_storage_error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).send('payment_success');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
12
utils/validateRequest.js
Normal file
12
utils/validateRequest.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
var Joi = require('joi');
|
||||||
|
|
||||||
|
module.exports = function validateRequest(schema) {
|
||||||
|
return function handler(req, res, next) {
|
||||||
|
Joi.validate(req.body, schema, function (err, value) {
|
||||||
|
if(err) {
|
||||||
|
return res.status(400).send(err.details)
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user