diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0d1c2a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +test/ + +node_modules/ +.git/ +coverage/ +npm-debug.log + +.dockerignore +Dockerfile + +.gitignore +.gitattributes +.editorconfig +.idea/ +*.md +*.iml diff --git a/.gitignore b/.gitignore index 2981ae5..268d94a 100755 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,17 @@ .DS_Store -node_modules -public -.tmp +/node_modules +/public +/.tmp +/tmp .sass-cache -app/bower_components -heroku +/app/bower_components /views -dist -.idea +/dist +/.idea newrelic_agent.log .c9 mongodb +*.log .env .env-prod +npm-debug.log.* diff --git a/.nvmrc b/.nvmrc index 8ac28bf..6b9255c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -4.6.1 +6.9.2 diff --git a/.travis.yml b/.travis.yml index c47808e..de1ef79 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: node_js node_js: - - '0.12' - '4' - - '5' - '6' + - '7' before_script: - npm install -g bower grunt-cli - bower install diff --git a/Gruntfile.js b/Gruntfile.js index c7cffc8..c3124c9 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -282,7 +282,6 @@ module.exports = function (grunt) { src: [ 'package.json', 'server.js', - 'newrelic.js', 'lib/**/*' ] }] @@ -352,7 +351,7 @@ module.exports = function (grunt) { grunt.registerTask('serve', function (target) { if (target === 'dist') { - return grunt.task.run(['build', 'express:prod', 'open', 'express-keepalive']); + return grunt.task.run(['build', 'express:prod', 'express-keepalive']); } grunt.task.run([ diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..3b91a68 --- /dev/null +++ b/app.yaml @@ -0,0 +1,15 @@ +runtime: nodejs +env: flex +handlers: +- url: /.* + script: IGNORED +endpoints_api_service: + name: api.gdgx.io + config_id: 2017-03-18r0 +network: + instance_tag: https-server + name: default +automatic_scaling: + min_num_instances: 1 + max_num_instances: 2 + cool_down_period_sec: 300 diff --git a/cron.yaml b/cron.yaml new file mode 100644 index 0000000..7276ce1 --- /dev/null +++ b/cron.yaml @@ -0,0 +1,4 @@ +cron: +- description: daily 9am EST ingestion job + url: / + schedule: every day 13:00 diff --git a/lib/config/env/all.js b/lib/config/env/all.js index fc57f7d..77c65dd 100755 --- a/lib/config/env/all.js +++ b/lib/config/env/all.js @@ -6,12 +6,17 @@ var rootPath = path.normalize(__dirname + '/../../..'); module.exports = { root: rootPath, - port: process.env.PORT || process.env.NODEJS_PORT || 3000, - hostname: process.env.NODEJS_IP || undefined, + port: process.env.PORT || 3000, + hostname: undefined, mongo: { options: { db: { safe: true + }, + server: { + socketOptions: { + keepAlive: 1 + } } } }, diff --git a/lib/config/express.js b/lib/config/express.js index 69be2d5..ccfdc4d 100755 --- a/lib/config/express.js +++ b/lib/config/express.js @@ -8,13 +8,17 @@ var express = require('express'), path = require('path'), passport = require('passport'), config = require('./config'); -var errorhandler = require('errorhandler'); +var errorHandler = require('errorhandler'); var morgan = require('morgan'); var compression = require('compression'); var bodyParser = require('body-parser'); +// var yesHttps = require('yes-https'); const MongoStore = require('connect-mongo')(session); // var csrf = require('csurf'); +const STATIC_FILE_CACHE_AGE = 604800000; // 1 week +// const YES_HTTPS_MAX_AGE = 120000; // 2 minutes + /** * Express configuration */ @@ -34,7 +38,7 @@ module.exports = function (app) { app.use(express.static(path.join(config.root, '.tmp'))); app.use(express.static(path.join(config.root, 'app'))); - app.use(errorhandler()); + app.use(errorHandler()); app.set('views', config.root + '/app/views'); app.use(session({ @@ -49,8 +53,8 @@ module.exports = function (app) { if (process.env.NODE_ENV === 'production') { app.use(favicon(path.join(config.root, 'public', 'favicon.ico'), {})); app.use(compression()); - app.use(express.static(path.join(config.root, 'public'), {maxAge: 604800000})); - app.set('views', config.root + '/views'); + app.use(express.static(path.join(config.root, 'public'), { maxAge: STATIC_FILE_CACHE_AGE, etag: true })); + app.set('views', config.root + '/public/views'); app.use(session({ cookie: { secure: true }, @@ -61,6 +65,13 @@ module.exports = function (app) { })); } + // Configure GAE proxy and health checks. Force HTTPS. + app.enable('trust proxy'); + // app.use(yesHttps({ maxAge: YES_HTTPS_MAX_AGE, includeSubdomains: true, preload: true })); + app.get('/_ah/health', (req, res) => { + res.sendStatus(200); + }); + app.engine('html', require('ejs').renderFile); app.set('view engine', 'html'); app.use(morgan('[' + process.pid + '] :method :url :status :response-time ms - :res[content-length]')); diff --git a/lib/config/keys.js b/lib/config/keys.js index 94b3e20..a644278 100755 --- a/lib/config/keys.js +++ b/lib/config/keys.js @@ -12,7 +12,7 @@ module.exports = { }, frisbee: { serverClientId: process.env.SERVER_KEY_SECRET, - androidClientIds: process.env.ANDROID_CLIENT_IDS.split(',') + androidClientIds: process.env.ANDROID_CLIENT_IDS ? process.env.ANDROID_CLIENT_IDS.split(',') : [] } } }; diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..a23b2df --- /dev/null +++ b/openapi.json @@ -0,0 +1,131 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "GDG-X Hub API", + "description": "API for using data from the Google Developer Groups (GDG) Hub.", + "license": { + "name": "MIT" + }, + "contact": { + "name": "GDG-X Support", + "email": "support@gdgx.io" + } + }, + "host": "api.gdgx.io", + "basePath": "/api/v1", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "https" + ], + "paths": { + "/chapters": { + "get": { + "operationId": "listChapters", + "description": "Returns a list containing all Chapters", + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ChapterList" + } + } + } + } + } + }, + "definitions": { + "ChapterList": { + "type": "object", + "properties": { + "count": { + "type": "number" + }, + "pages": { + "type": "number" + }, + "page": { + "type": "number" + }, + "perpage": { + "type": "number" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Chapter" + } + } + } + }, + "Chapter": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "site": { + "type": "string" + }, + "group_type": { + "type": "string" + }, + "country": { + "type": "string" + }, + "state": { + "type": "string" + }, + "city": { + "type": "string" + }, + "name": { + "type": "string" + }, + "__v": { + "type": "number" + }, + "organizers": { + "type": "array", + "items": { + "type": "string" + } + }, + "geo": { + "$ref": "#/definitions/Location" + } + } + }, + "Location": { + "type": "object", + "required": [ + "lng", "lat" + ], + "properties": { + "lng": { + "type": "number" + }, + "lat": { + "type": "number" + } + } + } + }, + "security": [ + + ], + "securityDefinitions": { + + } +} diff --git a/package.json b/package.json index b6a5b69..28376d6 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,11 @@ }, "version": "0.2.1", "dependencies": { + "@google-cloud/debug-agent": "1.0.0", + "@google-cloud/trace-agent": "1.0.1", "async": "1.4.2", "body-parser": "1.17.1", + "bower": "1.8.0", "burrito": "0.2.12", "cacher": "1.0.0", "cheerio": "0.19.0", @@ -27,7 +30,7 @@ "connect-mongo": "1.3.2", "cookie-parser": "1.4.3", "cron": "1.0.9", - "csurf": "1.8.3", + "csurf": "1.9.0", "dotenv": "4.0.0", "ejs": "0.8.4", "errorhandler": "1.5.0", @@ -35,37 +38,12 @@ "express-session": "1.15.1", "google-oauth-jwt": "0.1.7", "googleapis": "0.8.0", - "lodash": "2.4.1", - "method-override": "2.3.7", - "methods": "1.1.2", - "moment": "2.10.6", - "mongoose": "3.5.5", - "morgan": "1.8.1", - "node-forge": "0.6.34", - "node-geocoder": "2.2.0", - "node-uuid": "1.4.3", - "nodemailer": "1.4.0", - "passport": "0.3.2", - "passport-http-bearer": "1.0.1", - "passport-http-oauth": "0.1.3", - "passport-localapikey": "0.0.3", - "serve-favicon": "2.4.1", - "slugify": "0.1.1", - "socket.io": "0.9.17", - "superagent": "0.17.0", - "superagent-retry": "0.3.0", - "timequeue": "0.2.2" - }, - "devDependencies": { - "bower": "1.7.9", - "connect-livereload": "0.6.0", "grunt": "0.4.5", "grunt-autoprefixer": "0.4.0", "grunt-bower-install": "0.7.0", "grunt-cli": "1.2.0", "grunt-concurrent": "0.4.1", "grunt-contrib-clean": "0.5.0", - "grunt-contrib-coffee": "0.7.0", "grunt-contrib-compass": "0.6.0", "grunt-contrib-concat": "0.3.0", "grunt-contrib-copy": "0.4.1", @@ -73,7 +51,7 @@ "grunt-contrib-htmlmin": "0.1.3", "grunt-contrib-imagemin": "1.0.1", "grunt-contrib-jshint": "1.0.0", - "grunt-contrib-uglify": "0.2.0", + "grunt-contrib-uglify": "2.2.0", "grunt-contrib-watch": "0.5.2", "grunt-express-server": "0.4.5", "grunt-google-cdn": "0.2.0", @@ -84,10 +62,37 @@ "grunt-rev": "0.1.0", "grunt-svgmin": "0.2.0", "grunt-usemin": "2.0.0", - "jasmine-core": "2.4.1", "jpegtran-bin": "0.2.0", "jshint-stylish": "2.2.1", "karma": "1.2.0", + "load-grunt-tasks": "0.2.0", + "lodash": "2.4.1", + "method-override": "2.3.7", + "methods": "1.1.2", + "moment": "2.10.6", + "mongoose": "4.9.0", + "morgan": "1.8.1", + "node-forge": "0.6.34", + "node-geocoder": "2.2.0", + "node-uuid": "1.4.3", + "nodemailer": "1.4.0", + "passport": "0.3.2", + "passport-http-bearer": "1.0.1", + "passport-http-oauth": "0.1.3", + "passport-localapikey": "0.0.3", + "requirejs": "2.1.20", + "serve-favicon": "2.4.1", + "slugify": "0.1.1", + "socket.io": "0.9.17", + "superagent": "0.17.0", + "superagent-retry": "0.3.0", + "time-grunt": "0.2.10", + "timequeue": "0.2.2", + "yes-https": "0.0.3" + }, + "devDependencies": { + "connect-livereload": "0.6.0", + "jasmine-core": "2.4.1", "karma-chrome-launcher": "2.0.0", "karma-coverage": "1.1.1", "karma-firefox-launcher": "1.0.0", @@ -95,24 +100,20 @@ "karma-jasmine": "1.0.2", "karma-junit-reporter": "1.1.0", "karma-ng-html2js-preprocessor": "1.0.0", - "karma-ng-scenario": "1.0.0", "karma-phantomjs-launcher": "1.0.1", "karma-requirejs": "1.0.0", "karma-script-launcher": "1.0.0", - "load-grunt-tasks": "0.2.0", - "phantomjs-prebuilt": "2.1.7", - "requirejs": "2.1.20", - "time-grunt": "0.2.10" + "phantomjs-prebuilt": "2.1.14" }, "engines": { - "node": ">=0.12.0" + "node": ">=6" }, "scripts": { - "postinstall": "bower install", - "prestart": "grunt", - "start": "grunt serve", - "startProd": "grunt serve:dist", - "configProd": "sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000;export PORT=3000;", + "deploy": "gcloud app deploy --project gdgx-cloud --no-promote", + "deploy-promote": "gcloud app deploy --project gdgx-cloud", + "deploy-api": "gcloud service-management deploy openapi.json --project gdgx-cloud", + "start": "grunt serve:dist", + "startDev": "grunt serve", "test": "grunt test" } } diff --git a/server.js b/server.js index e7101ac..1fea78f 100755 --- a/server.js +++ b/server.js @@ -1,5 +1,9 @@ 'use strict'; +if (process.env.NODE_ENV === 'production') { + require('@google-cloud/trace-agent').start(); +} + var env = require('dotenv').config({path: process.env.NODE_ENV === 'production' ? '.env-prod' : '.env'}); if (env && !env.error) { if (process.env.NODE_ENV !== 'production') { @@ -13,6 +17,8 @@ var express = require('express'), path = require('path'), fs = require('fs'), mongoose = require('mongoose'); +require('@google-cloud/debug-agent').start({ allowExpressions: true }); +var app = express(); /** * Main application file @@ -45,30 +51,38 @@ fs.readdirSync(modelsPath).forEach(function (file) { require(modelsPath + '/' + file); }); -// Import static data -require('./lib/fixtures')(); +db.connection + .on('error', console.error) + .on('disconnected', console.error) + .once('open', listen); -// Passport Configuration -require('./lib/config/passport')(); +function listen() { + console.log('Connected to MongoDb at ' + + `mongodb://${db.connection.host}/${db.connection.name}:${db.connection.port}.`); -// Initialize admin task handlers -require('./lib/tasks')(); + // Import static data + require('./lib/fixtures')(); -var app = express(); + // Passport Configuration + require('./lib/config/passport')(); -// Express settings -require('./lib/config/express')(app); + // Initialize admin task handlers + require('./lib/tasks')(); -// Routing -require('./lib/routes')(app); + // Express settings + require('./lib/config/express')(app); -// Initialize cron jobs -require('./lib/cron')(); + // Routing + require('./lib/routes')(app); -// Start server -app.listen(config.port, config.hostname, function () { - console.log('Express server listening on port %d in %s mode', config.port, app.get('env')); -}); + // Initialize cron jobs + require('./lib/cron')(); + + // Start server + app.listen(config.port, config.hostname, function() { + console.log('Express server listening on port %d in %s mode', config.port, app.get('env')); + }); +} // Expose app exports = module.exports = app;