This commit is contained in:
Chris Van 2013-12-13 15:11:36 -08:00
Коммит 0b840e64bf
17 изменённых файлов: 459 добавлений и 0 удалений

17
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
build
node_modules
settings_local.js

32
README.md Normal file
Просмотреть файл

@ -0,0 +1,32 @@
# galaxy-api
Here lives the API that is consumed by the front-end interface for
[cvan/galaxy](cvan/galaxy).
## Installation
* `npm install`
* `cp settings_local.js.dist settings_local.js`
* `nodemon app.js`
## Sample Usage
### Game Submission
curl -X POST 'http://localhost:5000/game/submit' -d 'name=Mario Bros&app_url=http://mariobro.se&icons=128&screenshots=yes'
### Game Details
curl 'http://localhost:5000/game/mario-bros/detail'
### Webapp
#### Manifest JSON (Firefox)
curl 'http://localhost:5000/game/mario-bros/manifest'
#### Manifest Launcher
curl 'http://localhost:5000/launch.html?https://mariobro.se'

17
app.js Normal file
Просмотреть файл

@ -0,0 +1,17 @@
var settings = require('./settings_local.js');
var db = require('./db');
var server = require('./server');
[
'game/detail',
'game/manifest',
'game/submit'
].forEach(function(view) {
require('./views/' + view)(server);
});
server.listen(process.env.PORT || 5000, function() {
console.log('%s listening at %s', server.name, server.url);
});

Просмотреть файл

@ -0,0 +1 @@
{"slug":"mario-bros","app_url":"http://mario.com","name":"Mario Bros","icons":"128","screenshots":"yes"}

56
db.js Normal file
Просмотреть файл

@ -0,0 +1,56 @@
var fs = require('fs');
var path = require('path');
var url = require('url');
var redis = require('redis');
var redisURL = url.parse(process.env.REDIS_URL ||
process.env.REDISCLOUD_URL ||
process.env.REDISTOGO_URL ||
'');
redisURL.hostname = redisURL.hostname || 'localhost';
redisURL.port = redisURL.port || 6379;
var redisClient = redis.createClient(redisURL.port, redisURL.hostname);
if (redisURL.auth) {
redisClient.auth(redisURL.auth.split(':')[1]);
}
var utils = require('./utils');
function flatDB() {
}
flatDB.prototype = {
write: function(type, slug, data) {
if (typeof data !== 'string') {
data = JSON.stringify(data);
}
var baseDir = path.resolve('data', type);
var fn = path.resolve(baseDir, utils.slugify(slug) + '.json');
if (!fs.existsSync(baseDir)) {
console.error('Directory "' + baseDir + '" does not exist');
utils.mkdirRecursive(baseDir);
}
fs.writeFile(fn, data, 'utf8', function(err) {
if (err) {
console.error('Error creating', fn + ':', err);
}
});
},
read: function(type, slug) {
var fn = path.resolve('data', type, utils.slugify(slug) + '.json');
try {
return JSON.parse(fs.readFileSync(fn, 'utf8') || '{}');
} catch(e) {
console.error('Error reading', fn + ':', e);
return {};
}
}
};
var flatDBClient = new flatDB();
module.exports.redis = redisClient;
module.exports.flatfile = flatDBClient;

19
package.json Normal file
Просмотреть файл

@ -0,0 +1,19 @@
{
"name": "galaxy-api",
"version": "0.0.1",
"dependencies": {
"bookshelf": "*",
"node-restify-swagger": "*",
"node-restify-validation": "*",
"redis": "*",
"restify": "*"
},
"engines": {
"node": ">=0.8.x",
"npm": ">=1.1.x"
},
"repository": {
"type": "git",
"url": "git://github.com/cvan/galaxy-api.git"
}
}

19
server.js Normal file
Просмотреть файл

@ -0,0 +1,19 @@
var restify = require('restify');
var restifySwagger = require('node-restify-swagger');
var restifyValidation = require('node-restify-validation');
var server = restify.createServer({
name: 'galaxy',
version: '0.0.1'
});
server.use(restify.acceptParser(server.acceptable));
server.use(restify.bodyParser());
server.use(restify.gzipResponse());
server.use(restify.queryParser());
server.use(restifyValidation.validationPlugin({errorsAsArray: false}));
restifySwagger.configure(server);
restifySwagger.loadRestifyRoutes();
module.exports = server;

0
settings.js Normal file
Просмотреть файл

0
settings_local.js Normal file
Просмотреть файл

0
settings_local.js.dist Normal file
Просмотреть файл

Двоичные данные
static/hexgl-crx.crx Normal file

Двоичный файл не отображается.

77
static/installer.html Normal file
Просмотреть файл

@ -0,0 +1,77 @@
<!doctype html>
<html>
<head>
<style>
body { margin: 15%; text-align: center; }
button { background: green; border: 1px solid #000; color: #fff; cursor: pointer; font-size: 200%; padding: 15px 60px; text-transform: uppercase; }
</style>
</head>
<body>
<button>Play</button>
<script>
(function() {
var apps = {};
window.apps = apps;
var mozApps = 'mozApps' in navigator;
var chromeApps = 'chrome' in window;
var manifest = 'http://cvan.github.io/HexGL/';
if (mozApps) {
manifest = 'http://localhost:5000/app/hexgl/manifest/firefox';
function getInstalled() {
// Don't getInstalled if the page isn't visible.
if (document.hidden) {
return;
}
// Get list of installed apps and mark as such.
var r = navigator.mozApps.getInstalled();
r.onsuccess = function() {
r.result.forEach(function(val) {
apps[val.manifestURL.split('?')[0]] = val;
});
};
}
getInstalled();
} else if (chromeApps) {
//manifest = 'http://localhost:5000/app/hexgl/manifest/chrome.crx';
//manifest = 'http://localhost:5000/static/hexgl-crx.crx';
}
var button = document.querySelector('button');
button.addEventListener('click', function() {
if (mozApps) {
if (manifest in apps) {
apps[manifest].launch();
return;
}
var request = navigator.mozApps.install(manifest);
request.onsuccess = function() {
console.error('Install onsuccess', manifest);
var app = this.result;
var status;
var isInstalled = setInterval(function() {
status = request.result.installState;
if (status == 'installed') {
console.log('Install succeded', manifest);
button.blur();
apps[manifest] = app;
app.launch();
clearInterval(isInstalled);
}
}, 250);
};
request.onerror = function() {
console.error('Install failed:', this.error.name);
};
} else if (chromeApps) {
button.blur();
//window.location = manifest;
window.open(manifest);
}
}, false);
})();
</script>
</body>
</html>

1
static/launcher.html Normal file
Просмотреть файл

@ -0,0 +1 @@
<script>window.location = window.location.search.substr(1);</script>

26
utils.js Normal file
Просмотреть файл

@ -0,0 +1,26 @@
var fs = require('fs');
var path = require('path');
function mkdirRecursive(dir) {
var parent = path.resolve(dir, '../');
if (!fs.existsSync(parent)) {
mkdirRecursive(parent);
}
fs.mkdirSync(dir);
}
function slugify(text) {
if (typeof text !== 'string') {
return text;
}
return text.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
module.exports.mkdirRecursive = mkdirRecursive;
module.exports.slugify = slugify;

64
views/game/detail.js Normal file
Просмотреть файл

@ -0,0 +1,64 @@
var db = require('../.././db');
module.exports = function(server) {
// Sample usage:
// % curl 'http://localhost:5000/game/mario-bros/detail'
server.get({
url: '/game/:slug/detail',
swagger: {
nickname: 'detail',
notes: 'Specific details and metadata about a game',
summary: 'Game Details'
}
}, function(req, res) {
var GET = req.params;
var slug = GET.slug;
var game = db.flatfile.read('game', slug);
var keys = [
'app_url',
'appcache_path',
'default_locale',
'description',
'developer_name',
'developer_url',
'fullscreen',
'homepage_url',
'icons',
'license',
'locales',
'name',
'orientation',
'privacy',
'screenshots',
'slug'
];
var data = {};
keys.forEach(function(v) {
var item = game[v];
if (typeof v === 'object') {
if (v.hasOwnProperty('length') && item.length) {
data[v] = item;
} else if (Object.keys(game[v]).length) {
data[v] = item;
}
} else {
data[v] = item;
}
});
res.json(data);
});
// TODO: Serve each manifest from a separate subdomain.
server.get({
url: '/manifest.html'
}, function(req, res) {
var app_url = req.url.split('?')[1];
res.send("<script>window.location = '" + app_url + "';</script>");
});
};

64
views/game/manifest.js Normal file
Просмотреть файл

@ -0,0 +1,64 @@
var db = require('../.././db');
module.exports = function(server) {
// Sample usage:
// % curl 'http://localhost:5000/game/mario-bros/manifest'
server.get({
url: '/game/:slug/manifest/firefox',
swagger: {
nickname: 'manifest',
notes: 'Firefox Webapp Manifest JSON synthesised on the fly from app data',
summary: 'Webapp manifest'
}
}, function(req, res) {
var GET = req.params;
var slug = GET.slug;
var game = db.flatfile.read('game', slug);
var keys = [
'appcache_path',
'default_locale',
'description',
'icons',
'locales',
'name',
'orientation'
];
var data = {};
keys.forEach(function(v) {
var item = game[v];
if (typeof v === 'object') {
if (v.hasOwnProperty('length') && item.length) {
data[v] = item;
} else if (Object.keys(game[v]).length) {
data[v] = item;
}
} else {
data[v] = item;
}
});
res.contentType = 'application/x-web-app-manifest+json';
res.send(JSON.stringify(data));
});
// Sample usage:
// % curl 'http://localhost:5000/launch.html?https://mariobro.se'
// TODO: Serve each manifest from a separate subdomain.
server.get({
url: '/launch.html',
swagger: {
nickname: 'launch'
notes: 'Launch webapp from a URL based on querystring'
summary: 'Webapp Launcher',
}
}, function(req, res) {
var app_url = req.url.split('?')[1];
res.send("<script>window.location = '" + app_url + "';</script>");
});
};

66
views/game/submit.js Normal file
Просмотреть файл

@ -0,0 +1,66 @@
var db = require('../.././db');
var utils = require('../.././utils');
module.exports = function(server) {
// Sample usage:
// % curl -X POST 'http://localhost:5000/game/submit' -d 'name=Mario Bros&app_url=http://mariobro.se&icons=128&screenshots=yes'
server.post({
url: '/game/submit',
swagger: {
nickname: 'submit',
notes: 'Submit game',
summary: 'Submission'
},
validation: {
app_url: {
description: 'App URL',
isRequired: true,
isUrl: true
},
homepage_url: {
description: 'Homepage URL',
isRequired: false,
isUrl: true
},
icons: {
description: 'Icons',
isRequired: true,
},
name: {
description: 'Name',
isRequired: true,
max: 128
},
screenshots: {
description: 'Screenshots',
isRequired: true
}
}
}, function(req, res) {
var POST = req.params;
slug = utils.slugify(POST.slug || POST.name);
var data = {
app_url: POST.app_url,
appcache_path: POST.appcache_path,
default_locale: POST.default_locale,
description: POST.description,
developer_name: POST.developer_name,
developer_url: POST.developer_url,
fullscreen: POST.fullscreen,
homepage_url: POST.homepage_url,
icons: POST.icons,
locales: POST.locales,
name: POST.name,
orientation: POST.orientation,
// Galaxy-specific metadata.
license: POST.license,
privacy: POST.privacy_policy,
screenshots: POST.screenshots,
slug: slug
};
db.flatfile.write('game', slug, data);
res.json(data);
});
};