Skip to content

Commit a02cd40

Browse files
committed
Created serverless backend
1 parent 0188377 commit a02cd40

File tree

13 files changed

+300
-23
lines changed

13 files changed

+300
-23
lines changed

config/environments.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module.exports = {
1616
// Overrides when NODE_ENV === 'production'
1717
// ======================================================
1818
production : (config) => ({
19+
api_server_host : 'https://eq0z5hd8w3.execute-api.eu-west-1.amazonaws.com/prod/',
20+
s3_server_host : 'http://es6console.s3-website-eu-west-1.amazonaws.com/',
1921
compiler_public_path : '/',
2022
compiler_fail_on_warning : false,
2123
compiler_hash_type : 'chunkhash',

config/project.config.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ const config = {
2525
// Server Configuration
2626
// ----------------------------------
2727
server_host : ip.address(), // use string 'localhost' to prevent exposure on local network
28-
server_port : process.env.PORT || 3000,
28+
server_port : process.env.PORT || 8000,
29+
30+
// ----------------------------------
31+
// API Server Configuration
32+
// ----------------------------------
33+
api_server_host : 'http://localhost:3000/', // use string 'localhost' to prevent exposure on local network
34+
s3_server_host : 'http://localhost:8000/',
2935

3036
// ----------------------------------
3137
// Compiler Configuration
@@ -70,22 +76,6 @@ Edit at Your Own Risk
7076
-------------------------------------------------
7177
************************************************/
7278

73-
// ------------------------------------
74-
// Environment
75-
// ------------------------------------
76-
// N.B.: globals added here must _also_ be added to .eslintrc
77-
config.globals = {
78-
'process.env' : {
79-
'NODE_ENV' : JSON.stringify(config.env)
80-
},
81-
'NODE_ENV' : config.env,
82-
'__DEV__' : config.env === 'development',
83-
'__PROD__' : config.env === 'production',
84-
'__TEST__' : config.env === 'test',
85-
'__COVERAGE__' : !argv.watch && config.env === 'test',
86-
'__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
87-
}
88-
8979
// ------------------------------------
9080
// Validate Vendor Dependencies
9181
// ------------------------------------
@@ -130,4 +120,22 @@ if (overrides) {
130120
debug('No environment overrides found, defaults will be used.')
131121
}
132122

123+
// ------------------------------------
124+
// Environment
125+
// ------------------------------------
126+
// N.B.: globals added here must _also_ be added to .eslintrc
127+
config.globals = {
128+
'process.env' : {
129+
'NODE_ENV' : JSON.stringify(config.env)
130+
},
131+
'NODE_ENV' : config.env,
132+
'__DEV__' : config.env === 'development',
133+
'__PROD__' : config.env === 'production',
134+
'__TEST__' : config.env === 'test',
135+
'__COVERAGE__' : !argv.watch && config.env === 'test',
136+
'__BASENAME__' : JSON.stringify(process.env.BASENAME || ''),
137+
'API_SERVER_HOST' : JSON.stringify(config.api_server_host),
138+
'S3_SERVER_HOST': JSON.stringify(config.s3_server_host),
139+
}
140+
133141
module.exports = config

lambda/storage/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# package directories
2+
node_modules
3+
jspm_packages
4+
5+
# Serverless directories
6+
.serverless

lambda/storage/api/create.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
const dynamo = require('./dynamodb');
3+
4+
const options = {
5+
region: 'eu-west-1',
6+
};
7+
8+
module.exports.handler = (event, context, callback) => {
9+
let body;
10+
11+
try {
12+
body = JSON.parse(event.body);
13+
} catch (error) {
14+
console.error(error);
15+
return callback(error);
16+
}
17+
18+
const now = Date.now()
19+
const snippetId = parseInt(now, 10).toString(36);
20+
21+
const params = {
22+
TableName: process.env.DYNAMODB_TABLE,
23+
Item: {
24+
id: {
25+
S: snippetId,
26+
},
27+
code: {
28+
S: body.code,
29+
}
30+
}
31+
};
32+
33+
dynamo.putItem(params, (err, data) => {
34+
if (err) {
35+
console.error(err);
36+
return callback(err);
37+
}
38+
39+
const response = {
40+
statusCode: 201,
41+
body: JSON.stringify({
42+
message: 'Saved snippet',
43+
saved: true,
44+
id: snippetId,
45+
}),
46+
};
47+
48+
callback(null, response);
49+
});
50+
};

lambda/storage/api/dynamodb.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
4+
5+
let options = {};
6+
7+
// connect to local DB if running offline
8+
if (process.env.IS_OFFLINE) {
9+
options = {
10+
region: 'localhost',
11+
endpoint: 'http://localhost:8001',
12+
};
13+
}
14+
15+
const client = new AWS.DynamoDB(options);
16+
17+
module.exports = client;

lambda/storage/api/examples.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
const aws = require('aws-sdk');
3+
4+
const options = {
5+
region: 'eu-west-1',
6+
};
7+
8+
function listObjects(s3, params) {
9+
return new Promise((resolve, reject) => {
10+
s3.listObjects(params, (err, data) => {
11+
if (err) return reject(err);
12+
return resolve(data);
13+
});
14+
});
15+
}
16+
17+
function trimPrefix(prefix) {
18+
const pattern = /examples\/(\w+)\//;
19+
return prefix.match(pattern)[1];
20+
}
21+
22+
function trimItem(key) {
23+
const pattern = /examples\/\w+\/(.*)/;
24+
return key.match(pattern)[1];
25+
}
26+
27+
function makeBody(data) {
28+
const result = {};
29+
30+
data.forEach((set) => {
31+
result[trimPrefix(set.Prefix)] = set.Contents.map((item) => trimItem(item.Key));
32+
});
33+
34+
return {
35+
examples: result,
36+
};
37+
}
38+
39+
module.exports.handler = (event, context, callback) => {
40+
const s3 = new aws.S3(options);
41+
42+
const params = {
43+
Bucket: 'es6console',
44+
Delimiter: '/',
45+
Prefix: 'examples/',
46+
};
47+
48+
s3.listObjects(params, (err, data) => {
49+
if (err) return callback(err);
50+
51+
const prefixes = data.CommonPrefixes.map((item) => item.Prefix);
52+
53+
Promise.all(prefixes.map((prefix) =>
54+
listObjects(s3, {
55+
Bucket: 'es6console',
56+
Delimiter: '/',
57+
Prefix: prefix,
58+
})
59+
)).then((data) => {
60+
const content = makeBody(data);
61+
const response = {
62+
statusCode: 200,
63+
body: JSON.stringify(content),
64+
};
65+
66+
return callback(null, response);
67+
}).catch((err) => {
68+
return callback(err);
69+
});
70+
71+
});
72+
};

lambda/storage/api/get.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
const dynamo = require('./dynamodb');
3+
4+
module.exports.handler = (event, context, callback) => {
5+
const params = {
6+
TableName: process.env.DYNAMODB_TABLE,
7+
Key: {
8+
id: {
9+
S: event.pathParameters.id,
10+
}
11+
}
12+
};
13+
14+
dynamo.getItem(params, (err, data) => {
15+
if (err) return callback(err);
16+
17+
if (! Object.hasOwnProperty.call(data, 'Item')) {
18+
return callback(null, { statusCode: 404 });
19+
}
20+
21+
const response = {
22+
statusCode: 200,
23+
body: JSON.stringify({
24+
message: 'Retrieved snippet',
25+
snippet: data.Item.code.S,
26+
}),
27+
};
28+
29+
callback(null, response);
30+
});
31+
};

lambda/storage/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "storage",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "Matthisk <m@tthisk.nl> (http://matthisk.nl/)",
10+
"license": "ISC",
11+
"dependencies": {
12+
"aws-sdk": "~2.62.0",
13+
"serverless-dynamodb-local": "~0.2.22",
14+
"serverless-offline": "~3.14.1"
15+
}
16+
}

lambda/storage/serverless.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
service: storage
2+
3+
plugins:
4+
- serverless-dynamodb-local
5+
- serverless-offline
6+
7+
custom:
8+
dynamodb:
9+
start:
10+
port: 8001
11+
inMemory: true
12+
migration: true
13+
14+
provider:
15+
name: aws
16+
runtime: nodejs6.10
17+
stage: dev
18+
region: eu-west-1
19+
environment:
20+
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
21+
iamRoleStatements:
22+
- Effect: Allow
23+
Action:
24+
- dynamodb:Query
25+
- dynamodb:Scan
26+
- dynamodb:GetItem
27+
- dynamodb:PutItem
28+
- dynamodb:UpdateItem
29+
- dynamodb:DeleteItem
30+
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
31+
32+
functions:
33+
get:
34+
handler: api/get.handler
35+
events:
36+
- http:
37+
path: api/snippet/{id}
38+
method: get
39+
cors: true
40+
create:
41+
handler: api/create.handler
42+
events:
43+
- http:
44+
path: api/snippet/save
45+
method: post
46+
cors: true
47+
listExamples:
48+
handler: api/examples.handler
49+
events:
50+
- http:
51+
path: api/examples
52+
method: get
53+
cors: true
54+
55+
resources:
56+
Resources:
57+
snippetsTable:
58+
Type: AWS::DynamoDB::Table
59+
Properties:
60+
TableName: ${self:provider.environment.DYNAMODB_TABLE}
61+
AttributeDefinitions:
62+
- AttributeName: id
63+
AttributeType: S
64+
KeySchema:
65+
- AttributeName: id
66+
KeyType: HASH
67+
ProvisionedThroughput:
68+
ReadCapacityUnits: 1
69+
WriteCapacityUnits: 1

src/components/Editor/Editor.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,16 @@ class _Editor extends Component {
142142
}
143143
};
144144

145+
let code = this.props.code;
146+
if (typeof this.props.code !== 'string') {
147+
console.warn(`Expected code property to be of type 'string' and not '${typeof this.props.code}'`);
148+
code = '';
149+
}
150+
145151
return (
146152
<CodeMirror
147153
ref='editor'
148-
value={this.props.code}
154+
value={code}
149155
options={options}
150156
className={this.props.errors.length <= 0 ?
151157
"no-errors" :

src/store/examples.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as actionTypes from 'store/actionTypes'
1111
export function loadExamples() {
1212
return {
1313
[CALL_API]: {
14-
endpoint : '/api/examples/',
14+
endpoint : `${API_SERVER_HOST}api/examples/`,
1515
method : 'GET',
1616
types : [actionTypes.EXAMPLES_REQUEST,
1717
actionTypes.EXAMPLES_SUCCESS,
@@ -22,7 +22,7 @@ export function loadExamples() {
2222

2323
export function showExample(group, example) {
2424
return dispatch => {
25-
fetch(`/examples/${group}/${example}`)
25+
fetch(`${S3_SERVER_HOST}examples/${group}/${example}`)
2626
.then(res => res.text())
2727
.then(code => {
2828
dispatch({

src/store/ide.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export function updateEditorConfig(key, value) {
124124
export function loadSnippet(id) {
125125
return {
126126
[CALL_API]: {
127-
endpoint: `/api/snippet/${id}/`,
127+
endpoint: `${API_SERVER_HOST}api/snippet/${id}/`,
128128
method: 'GET',
129129
types: [actionTypes.LOAD_REQUEST,
130130
actionTypes.LOAD_SUCCESS,
@@ -139,7 +139,7 @@ export function saveSnippet(code) {
139139
return dispatch => {
140140
dispatch({
141141
[CALL_API]: {
142-
endpoint: '/api/snippet/save/',
142+
endpoint: `${API_SERVER_HOST}api/snippet/save/`,
143143
method: 'POST',
144144
headers: {
145145
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)