Skip to content
This repository was archived by the owner on Feb 9, 2021. It is now read-only.

Commit d2eb5ab

Browse files
Moving hovercardsserver into hovercardsextension
#10
1 parent 02eda49 commit d2eb5ab

20 files changed

+2781
-9
lines changed

.eslintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"extends": "xo",
3+
"plugins": ["react"],
34
"env": {
45
"browser": true
56
},
@@ -17,6 +18,7 @@
1718
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
1819
"no-multi-spaces": [2, { "exceptions": { "VariableDeclarator": true } }],
1920
"object-curly-spacing": [2, "always"],
21+
"react/jsx-uses-react": 2,
2022
"space-before-function-paren": [2, "never"]
2123
}
2224
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.env
22
dist-landing/*
33
dist/*
4+
dump.rdb
45
node_modules/
56
npm-debug.log*

Procfile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
copy: watch 'npm run copy:other' app
2-
postcss: npm run postcss -- -w
3-
watchify: npm run watchify
4-
landing: webpack-dev-server --inline --hot
1+
web: node server

Procfile.dev

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
web: nodemon --ignore dist/ -e js,jsx server
2+
redis: redis-server
3+
copy: watch 'npm run copy:other' app
4+
postcss: npm run postcss -- -w
5+
watchify: npm run watchify
6+
landing: webpack-dev-server --inline --hot

app.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "hovercards",
3+
"env": {
4+
"CHROMIUM_IDS": { "required": true },
5+
"GOOGLE_SERVER_KEY": { "required": true },
6+
"IMGUR_CLIENT_ID": { "required": true },
7+
"INSTAGRAM_CLIENT_ID": { "required": true },
8+
"INSTAGRAM_CLIENT_SECRET": { "required": true },
9+
"MASHAPE_KEY": { "required": false },
10+
"REDDIT_CLIENT_ID": { "required": true },
11+
"SOUNDCLOUD_CLIENT_ID": { "required": true },
12+
"TWITTER_APP_ACCESS_TOKEN": { "required": true },
13+
"TWITTER_APP_ACCESS_TOKEN_SECRET": { "required": true },
14+
"TWITTER_CONSUMER_KEY": { "required": true },
15+
"TWITTER_CONSUMER_SECRET": { "required": true }
16+
},
17+
"addons": [
18+
"papertrail",
19+
"rediscloud",
20+
"securekey"
21+
]
22+
}

package.json

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
"scripts": {
2020
"browserify": "mkdir -p dist && browserify $npm_package_config_browserify_input $npm_package_config_browserify_output",
2121
"build:landing": "rm -rf dist && webpack -p",
22-
"clean": "rm -rf dist/* *.pem *.nex",
22+
"clean": "rm -rf dist/* dist-landing/* *.pem *.nex dump.rdb",
2323
"copy:other": "rsync -rm --include='*/' --include='*.json' --include='options.html' --exclude='*' app/* dist && mkdir -p dist/assets/images && cp app/images/logo-* dist/assets/images && cp app/images/*-icon-full_color.png dist/assets/images",
2424
"postcss": "postcss app/*.css -d dist/ -c postcss.js",
2525
"cssfmt": "( git diff --staged --name-only | grep \".s\\?css$\" | xargs -I '{}' -n 1 cssfmt {} ) && ( git diff --staged --name-only | grep \".s\\?css$\" | xargs git add )",
2626
"deploy:landing": "gh-pages -d dist-landing",
2727
"eslint-fix": "( git diff --staged --name-only | grep \".jsx\\?$\" | xargs eslint --fix ) && ( git diff --staged --name-only | grep \".jsx\\?$\" | xargs git add )",
2828
"fix": "npm run cssfmt && npm run eslint-fix",
2929
"pkg": "npm run clean && npm run copy:other && npm run postcss && npm run browserify && npm run uglifyjs && zip -q -r build/pkg-$npm_package_version.zip dist",
30-
"start": "foreman start",
30+
"start": "if [ \"$NODE_ENV\" = 'production' ]; then foreman start; else foreman start -f Procfile.dev; fi",
3131
"test": "eslint . --ignore-path .gitignore",
3232
"uglifyjs": "mkdir -p dist && find dist -name \\*.js | xargs -I FILENAME uglifyjs FILENAME -o FILENAME -c drop_console,warnings=false --stats",
3333
"watchify": "mkdir -p dist && watchify $npm_package_config_browserify_input -t [ envify --NODE_ENV development ] $npm_package_config_browserify_output --full-paths -v -d"
@@ -52,12 +52,25 @@
5252
]
5353
},
5454
"dependencies": {
55-
"async": "^1.4.2",
56-
"hovercardsshared": "cameronrohani/hovercardsshared#v3.0.0",
55+
"async": "^1.5.0",
56+
"cheerio": "^0.19.0",
57+
"connect-redis": "^3.0.2",
58+
"express": "^4.13.3",
59+
"express-session": "^1.11.3",
60+
"googleapis": "^2.1.6",
61+
"hovercardsshared": "git+https://0df3b2f2114c8ec057c69f5f3d53e9a16afd26a1:[email protected]:cameronrohani/hovercardsshared.git#v3.0.0",
62+
"instagram-node": "^0.5.8",
5763
"jquery": "^2.1.4",
5864
"memoizee": "^0.3.9",
65+
"passport": "^0.2.2",
66+
"passport-twitter": "^1.0.3",
5967
"ractive": "^0.7.3",
60-
"underscore": "^1.8.3"
68+
"redis": "^2.4.2",
69+
"request": "^2.65.0",
70+
"snoocore": "^3.2.0",
71+
"twit": "^2.1.1",
72+
"underscore": "^1.8.3",
73+
"urijs": "^1.17.0"
6174
},
6275
"devDependencies": {
6376
"autoprefixer": "^6.0.3",
@@ -68,6 +81,7 @@
6881
"envify": "^3.4.0",
6982
"eslint": "^1.10.3",
7083
"eslint-config-xo": "^0.9.1",
84+
"eslint-plugin-react": "^3.15.0",
7185
"extract-text-webpack-plugin": "^1.0.1",
7286
"factor-bundle": "^2.5.0",
7387
"file-loader": "^0.9.0",
@@ -76,6 +90,7 @@
7690
"html-webpack-plugin": "^2.22.0",
7791
"image-webpack-loader": "^2.0.0",
7892
"jquery": "^2.2.2",
93+
"nodemon": "^1.8.1",
7994
"postcss-class-prefix": "^0.3.0",
8095
"postcss-cli": "^2.3.2",
8196
"postcss-color-rgba-fallback": "^2.0.0",
@@ -97,6 +112,10 @@
97112
"webpack": "^1.13.1",
98113
"webpack-dev-server": "^1.14.1"
99114
},
115+
"engines": {
116+
"node": ">= 4.1.0",
117+
"npm": ">= 3.9.0"
118+
},
100119
"pre-commit": [
101120
"fix",
102121
"test"

server/api-routes.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
var _ = require('underscore');
2+
var async = require('async');
3+
var express = require('express');
4+
var passport = require('passport');
5+
var session = require('express-session');
6+
var RedisStore = require('connect-redis')(session);
7+
8+
var config = require('./config');
9+
var redis_client = require('./redis-client');
10+
11+
var routes = express.Router();
12+
13+
routes.use(session({
14+
store: new RedisStore({ client: redis_client }),
15+
secret: (process.env.SECURE_KEY || '').split(','),
16+
saveUninitialized: false,
17+
resave: false
18+
}));
19+
routes.use(passport.initialize());
20+
routes.use(passport.session());
21+
22+
var serialize = JSON.stringify;
23+
var deserialize = JSON.parse;
24+
25+
function respond_to_caller(req, res, callback, err, result, usage) {
26+
_.each(usage, function(value, header) {
27+
res.append('usage-' + header, value);
28+
});
29+
if (err) {
30+
return callback(err);
31+
}
32+
res.json(result);
33+
}
34+
35+
var callers = _.mapObject(config.apis, function(api_config) {
36+
return api_config.caller ? api_config.caller(api_config) : {};
37+
});
38+
39+
_.each(config.apis, function(api_config, api) {
40+
if (api_config.authenticate) {
41+
api_config.authenticate(routes);
42+
}
43+
if (callers[api].content) {
44+
routes.get('/' + api + '/content/:id', function(req, res, callback) {
45+
callers[api].content(_.defaults({}, req.params, req.query, req.headers), _.partial(respond_to_caller, req, res, callback));
46+
});
47+
}
48+
49+
if (callers[api].discussion) {
50+
routes.get('/' + api + '/content/:id/discussion', function(req, res, callback) {
51+
callers[api].discussion(_.defaults({}, req.params, req.query, req.headers), _.partial(respond_to_caller, req, res, callback));
52+
});
53+
}
54+
55+
_.chain(api_config.discussion_apis)
56+
.without(api)
57+
.each(function(discussion_api) {
58+
if (callers[discussion_api].discussion) {
59+
routes.get('/' + api + '/content/:id/discussion/' + discussion_api, function(req, res, callback) {
60+
callers[discussion_api].discussion(
61+
_.defaults({ for: _.defaults({ api: api, type: 'content', id: req.params.id }, req.query.for, req.header.for) }, req.query, req.headers),
62+
_.partial(respond_to_caller, req, res, callback)
63+
);
64+
});
65+
}
66+
})
67+
.value();
68+
69+
if (callers[api].account) {
70+
routes.get('/' + api + '/account/:id', function(req, res, callback) {
71+
callers[api].account(_.defaults({}, req.params, req.query, req.headers), _.partial(respond_to_caller, req, res, callback));
72+
});
73+
}
74+
75+
if (callers[api].account_content) {
76+
routes.get('/' + api + '/account/:id/content', function(req, res, callback) {
77+
callers[api].account_content(_.defaults({}, req.params, req.query, req.headers), _.partial(respond_to_caller, req, res, callback));
78+
});
79+
}
80+
81+
if (process.env.NODE_ENV === 'production') {
82+
_.extend(callers[api].model, _.mapObject(callers[api].model, function(func, name) {
83+
var promises = {};
84+
85+
return function(args, args_not_cached, usage, callback) {
86+
if (_.result(args_not_cached, 'user')) {
87+
// The server-side cache (ie redis) helps us use the same work we've done
88+
// for other users who want the same thing. An authenticated request needs
89+
// work that is specific to that user's view, so this doesn't apply there.
90+
// The user will have the request cached on the client-side, in those cases.
91+
return func(args, args_not_cached, usage, callback);
92+
}
93+
var key = _.chain(['cache', api, name])
94+
.union(_.map(args, function(val, key) {
95+
return key + ':' + JSON.stringify(val);
96+
}))
97+
.join('::')
98+
.value();
99+
100+
promises[key] = promises[key] || new Promise(function(resolve, reject) {
101+
redis_client.get(key, function(err, result) {
102+
if (!err && result) {
103+
return async.setImmediate(function() {
104+
resolve(deserialize(result));
105+
});
106+
}
107+
func(args, args_not_cached, usage, function(err, result) {
108+
if (err) {
109+
return reject(err);
110+
}
111+
redis_client.setex(key, (api_config['cache_' + name] || api_config.cache_default || 5 * 60 * 1000) / 1000, serialize(result));
112+
resolve(result);
113+
});
114+
});
115+
});
116+
117+
promises[key]
118+
.then(function(result) {
119+
delete promises[key];
120+
callback(null, result);
121+
})
122+
.catch(function(err) {
123+
delete promises[key];
124+
callback(err);
125+
});
126+
};
127+
}));
128+
}
129+
});
130+
131+
/* eslint-disable no-unused-vars */
132+
routes.use(function(err, req, res, callback) {
133+
/* eslint-enable no-unused-vars */
134+
err = _.defaults(err, { message: 'Do not recognize url ' + req.path, status: 404 });
135+
res.status(err.status).json(err);
136+
});
137+
138+
module.exports = routes;

server/config.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
var _ = require('underscore');
2+
var TwitterStrategy = require('passport-twitter').Strategy;
3+
var passport = require('passport');
4+
var redis_client = require('./redis-client');
5+
var shared_config = require('hovercardsshared/config');
6+
7+
var CHROMIUM_IDS = process.env.CHROMIUM_IDS.split(';');
8+
9+
var youtube_keys = [process.env.GOOGLE_SERVER_KEY];
10+
for (var i = 2; process.env['GOOGLE_SERVER_KEY_' + i]; i++) {
11+
youtube_keys.push(process.env['GOOGLE_SERVER_KEY_' + i]);
12+
}
13+
14+
var config = {
15+
apis: {
16+
imgur: {
17+
caller: require('hovercardsshared/imgur'),
18+
key: process.env.IMGUR_CLIENT_ID,
19+
mashape_key: process.env.MASHAPE_KEY
20+
},
21+
instagram: {
22+
caller: require('hovercardsshared/instagram'),
23+
key: process.env.INSTAGRAM_CLIENT_ID,
24+
secret: process.env.INSTAGRAM_CLIENT_SECRET
25+
},
26+
reddit: {},
27+
soundcloud: {},
28+
twitter: {
29+
caller: require('hovercardsshared/twitter'),
30+
key: process.env.TWITTER_CONSUMER_KEY,
31+
secret: process.env.TWITTER_CONSUMER_SECRET,
32+
app_user: process.env.TWITTER_APP_ACCESS_TOKEN,
33+
app_user_secret: process.env.TWITTER_APP_ACCESS_TOKEN_SECRET
34+
},
35+
youtube: (youtube_keys.length === 1) ? {
36+
caller: require('hovercardsshared/youtube'),
37+
key: _.first(youtube_keys)
38+
} : {
39+
caller: require('hovercardsshared/youtube'),
40+
keys: youtube_keys
41+
}
42+
}
43+
};
44+
45+
config.apis.twitter.secret_storage = {
46+
del: function(token, callback) {
47+
redis_client.del('auth:twitter:' + token, callback);
48+
},
49+
get: function(token, callback) {
50+
redis_client.get('auth:twitter:' + token, callback);
51+
}
52+
};
53+
54+
config.apis.twitter.authenticate = function(routes) {
55+
passport.use(new TwitterStrategy({
56+
consumerKey: process.env.TWITTER_CONSUMER_KEY,
57+
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
58+
callbackURL: '/v2/twitter/callback'
59+
}, function(token, tokenSecret, profile, callback) {
60+
redis_client.set('auth:twitter:' + token, tokenSecret, _.partial(callback, _, token));
61+
}));
62+
63+
routes.get('/twitter/authenticate', function(req, res, callback) {
64+
if (!_.contains(CHROMIUM_IDS, req.query.chromium_id)) {
65+
return callback({ status: 400 });
66+
}
67+
passport.authenticate('twitter', { session: false, callbackURL: '/v2/twitter/callback?chromium_id=' + req.query.chromium_id })(req, res, callback);
68+
});
69+
routes.get('/twitter/callback', passport.authenticate('twitter', { session: false }), function(req, res, callback) {
70+
if (!_.contains(CHROMIUM_IDS, req.query.chromium_id)) {
71+
return callback({ status: 400 });
72+
}
73+
res.redirect('https://' + req.query.chromium_id + '.chromiumapp.org/callback#access_token=' + req.user);
74+
});
75+
};
76+
77+
var apis = _.intersection(_.keys(config.apis), _.keys(shared_config.apis));
78+
79+
config.apis = _.chain(config.apis)
80+
.pick(apis)
81+
.each(function(api_config, api) {
82+
_.defaults(api_config, shared_config.apis[api]);
83+
})
84+
.value();
85+
86+
module.exports = config;

server/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var express = require('express');
2+
var os = require('os');
3+
4+
var app = express();
5+
6+
app.set('port', process.env.PORT || 5000);
7+
8+
app.get('/', function(req, res) {
9+
res.redirect('http://www.hovercards.com');
10+
});
11+
app.use('/v2', require('./api-routes'));
12+
app.use('/v1', require('./old/hovercards'));
13+
// app.use('/', require('./view-routes'));
14+
15+
app.listen(app.get('port'), function() {
16+
console.log('Server is running at', 'http://' + os.hostname() + ':' + app.get('port'));
17+
});

0 commit comments

Comments
 (0)