Skip to content

Commit 8be6154

Browse files
committed
#0: Scaffold out a very basic API service
1 parent 709c72e commit 8be6154

16 files changed

+856
-15
lines changed

.lando.yml

+11
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@ env_file:
44
excludes:
55
- node_modules
66
proxy:
7+
api:
8+
- api.lndo.site:8011
79
docs:
810
- docs.lndo.site:8008
911
site:
1012
- site.lndo.site:8009
1113
metrics:
1214
- metrics.lndo.site:8010
1315
services:
16+
api:
17+
type: node:10
18+
build:
19+
- yarn install
20+
command: yarn run dev:api
21+
overrides:
22+
environment:
23+
LANDO_API_PORT: 8011
24+
port: 8011
1425
docs:
1526
type: node:10
1627
build:

.platform/routes.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Api
2+
http://api.{default}/:
3+
type: upstream
4+
upstream: api:http
5+
cache:
6+
headers: ['Accept', 'Accept-Language', 'Origin']
7+
default_ttl: 299
8+
enabled: true
9+
ssi:
10+
enabled: false
11+
112
# Site
213
http://devwithlando.io/:
314
type: redirect

api/.platform.app.yaml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: api
2+
type: nodejs:10
3+
4+
dependencies:
5+
nodejs:
6+
pm2: "^2.0.0"
7+
yarn: "*"
8+
9+
web:
10+
commands:
11+
start: "PM2_HOME=$PLATFORM_APP_DIR/run pm2 start server.js --no-daemon"
12+
locations:
13+
"/public":
14+
passthru: false
15+
root: "public"
16+
allow: true
17+
rules:
18+
'\.png$':
19+
allow: true
20+
expires: -1
21+
22+
mounts:
23+
"/run": "shared:files/run"
24+
25+
disk: 1500
26+
27+
hooks:
28+
build: |
29+
set -e
30+
yarn install
31+
curl -sS https://platform.sh/cli/installer | php
32+
33+
crons:
34+
snapshot:
35+
# Take a snapshot automatically every night at 3 am (UTC).
36+
spec: '0 3 * * *'
37+
cmd: |
38+
if [ "$PLATFORM_BRANCH" = master ]; then
39+
/app/.platformsh/bin/platform snapshot:create --yes --no-wait
40+
fi
41+
renewcert:
42+
# Force a redeploy at 9 am (UTC) on the 14th of every month.
43+
spec: '0 9 14 * *'
44+
cmd: |
45+
if [ "$PLATFORM_BRANCH" = master ]; then
46+
/app/.platformsh/bin/platform redeploy --yes --no-wait
47+
fi

api/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Lando API Server
2+
================
3+
4+
Lightweight node server that powers the Lando API.
5+
6+
Local Development
7+
-----------------
8+
9+
### Installing
10+
11+
Local development requires [lando](https://docs.lando.dev).
12+
13+
```bash
14+
# Clone the entire lando project
15+
git clone https://github.com/lando/lando.git
16+
cd lando && lando start
17+
```

api/data/sponsors.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
patriots:
2+
- name: Tandem
3+
logo: https://raw.githubusercontent.com/lando/lando/master/docs/.vuepress/public/images/tandem-pink.png
4+
url: https://thinktandem.io
5+
partners:
6+
- name: OpenDevShop
7+
logo: https://avatars1.githubusercontent.com/u/6655472?s=200&v=4
8+
url: http://getdevshop.com/
9+
teams:
10+
- name: Lullabot
11+
logo: https://www.drupal.org/files/Lullabot_Logo_Vertical-svg.png
12+
url: https://www.lullabot.com/
13+
individuals:
14+
- name: "@pirog"
15+
url: https://twitter.com/pirogcommamike
16+
- name: "@reynoldsalec"
17+
url: https://twitter.com/reynoldsalec

api/lib/config.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
5+
/*
6+
* Helper to load the config things
7+
*/
8+
module.exports = (config = {}) => {
9+
// Merge in .env file
10+
require('dotenv').config();
11+
12+
// Merge in process.env as relevant
13+
_.forEach(_.keys(config), key => {
14+
if (_.has(process.env, key)) {
15+
config[key] = process.env[key];
16+
}
17+
});
18+
19+
// Merge in platformsh stuff
20+
if (!_.isEmpty(process.env.PLATFORM_ENVIRONMENT)) {
21+
// Get platform config
22+
const pconfig = require('platformsh').config();
23+
// Load in platform consts
24+
if (!_.isEmpty(pconfig.constiables)) {
25+
_.forEach(pconfig.constiables, (value, key) => {
26+
config[key] = value;
27+
});
28+
}
29+
// Set the port
30+
config.LANDO_API_PORT = pconfig.port;
31+
}
32+
33+
// Return config
34+
return config;
35+
};

api/lib/logger.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const winston = require('winston');
5+
const log = new winston.Logger({
6+
transports: [
7+
new winston.transports.Console({colorize: true}),
8+
],
9+
});
10+
11+
/*
12+
* Add a request logger method
13+
*/
14+
log.request = req => {
15+
log.info(
16+
'%s request to %s with body %j',
17+
_.get(req, 'method', 'UNKNOWN'),
18+
_.get(req, 'url', 'UNKNOWN'),
19+
_.get(req, 'body', {})
20+
);
21+
};
22+
23+
/*
24+
* Add a request logger method
25+
*/
26+
log.response = (res, type = 'info', data = {}) => {
27+
log[type]('Responded with %s: %s',
28+
_.get(res, 'statusCode', 'UNKNOWN'),
29+
_.get(res, 'statusMessage', 'UNKNOWN')
30+
);
31+
log[type](data);
32+
};
33+
34+
/*
35+
* Return the log
36+
*/
37+
module.exports = log;
38+
39+

api/lib/utils.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const fs = require('fs');
2+
const yaml = require('js-yaml');
3+
4+
/*
5+
* Helper to load a YAML file into a JS Object
6+
*
7+
* @TODO: we should mem cache these results and only read the file
8+
* if its contents have changed
9+
*/
10+
exports.loadFile = file => {
11+
try {
12+
return yaml.safeLoad(fs.readFileSync(file));
13+
} catch (e) {
14+
throw Error(`Could not load file ${file}`);
15+
}
16+
};

api/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "lando-api",
3+
"repository": {
4+
"type": "git",
5+
"url": "https://github.com/lando/lando"
6+
},
7+
"bugs": {
8+
"email": "[email protected]",
9+
"url": "https://github.com/lando/lando"
10+
},
11+
"dependencies": {
12+
"body-parser": "^1.14.2",
13+
"compression": "^1.7.4",
14+
"express": "^4.13.3",
15+
"express-urlrewrite": "^1.2.0",
16+
"js-yaml": "^3.13.1",
17+
"lodash": "~4.17.13",
18+
"platformsh": "^1.0.0",
19+
"winston": "2.2"
20+
}
21+
}

api/routes/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = (api, handler) => {
2+
api.get('/v1', handler((req, res) => ({ping: 'pong'})));
3+
};

api/routes/sponsors.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const path = require('path');
2+
const utils = require('./../lib/utils');
3+
const sponsorsFile = path.resolve(__dirname, '..', 'data', 'sponsors.yml');
4+
5+
/*
6+
* Helper to get
7+
*/
8+
9+
module.exports = (api, handler) => {
10+
api.get('/v1/sponsors', handler((req, res) => {
11+
return utils.loadFile(sponsorsFile);
12+
}));
13+
api.get('/v1/sponsors/:type', handler((req, res) => {
14+
return utils.loadFile(sponsorsFile)[req.params.type] || res.redirect('/sponsors');
15+
}));
16+
};

api/server.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
const bodyParser = require('body-parser');
4+
const compression = require('compression');
5+
const rewrite = require('express-urlrewrite');
6+
const fs = require('fs');
7+
const log = require('./lib/logger.js');
8+
const path = require('path');
9+
const Promise = require('bluebird');
10+
const currentVersion = 'v1';
11+
12+
// Define default config
13+
const defaultConfig = {
14+
'LANDO_API_PORT': 80,
15+
'LANDO_API_TIMEOUT': 10000,
16+
};
17+
18+
// Get configuration
19+
const config = require('./lib/config.js')(defaultConfig);
20+
log.info('Starting app with config %j', config);
21+
22+
// Get the app ready
23+
const express = require('express');
24+
const api = express();
25+
26+
// App usage
27+
api.use(compression());
28+
api.use(bodyParser.urlencoded({extended: true}));
29+
api.use(bodyParser.json());
30+
api.use(rewrite('/*', `/${currentVersion}/$1`));
31+
32+
/**
33+
* Handler function.
34+
* @param {function} fn thing
35+
* @return {String} log shit
36+
*/
37+
const handler = fn => {
38+
// Returns a handler function.
39+
return (req, res) => {
40+
log.request(req, res);
41+
// Call fn in context of a promise.
42+
return Promise.try(fn, [req, res])
43+
// Make sure we have a timeout.
44+
.timeout(config.LANDO_API_TIMEOUT || 10 * 1000)
45+
// Handle success.
46+
.then(data => {
47+
res.status(200);
48+
res.json(data);
49+
res.end();
50+
log.response(res, 'info', data);
51+
})
52+
// Handler failure.
53+
.catch(err => {
54+
const code = err.statusCode || err.status || 500;
55+
const message = err.message || err.statusMessage || 'Unknown Error';
56+
res.status(code);
57+
res.send({code, message});
58+
res.end();
59+
log.response(res, 'error', err);
60+
});
61+
};
62+
};
63+
64+
// Start listening
65+
Promise.fromNode(cb => {
66+
api.listen(config.LANDO_API_PORT, cb);
67+
})
68+
// Load our routes
69+
.then(() => {
70+
fs.readdirSync(path.join(__dirname, 'routes')).map(file => {
71+
require(`./routes/${file}`)(api, handler);
72+
log.info('Loaded route %s', file);
73+
});
74+
log.info('Listening on port: %s', config.LANDO_API_PORT);
75+
});

0 commit comments

Comments
 (0)