Skip to content

Commit 1eb9cac

Browse files
committed
first commit
0 parents  commit 1eb9cac

File tree

14 files changed

+13349
-0
lines changed

14 files changed

+13349
-0
lines changed

.eslintrc.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"parserOptions": {
3+
"sourceType": "module"
4+
},
5+
"env": {
6+
"es6": true,
7+
"node": true,
8+
"browser": true
9+
},
10+
"extends": "eslint:recommended"
11+
}

.gitignore

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

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# lambda-view-server

app.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
body {
2+
font-family: sans-serif;
3+
}
4+
5+
span, a, a:link, a:visited, a:hover, a:active {
6+
display: inline-block;
7+
color: #fff;
8+
text-decoration: none;
9+
padding: 1rem;
10+
margin-right: 0.5rem;
11+
margin-bottom: 1rem;
12+
}
13+
14+
a {
15+
background-color: #88f;
16+
}
17+
18+
span {
19+
background-color: #f88;
20+
}
21+
22+
ul {
23+
list-style: none;
24+
padding: 0;
25+
display: flex;
26+
margin: 0 -0.5rem;
27+
}
28+
29+
li {
30+
padding: 0 0.5rem;
31+
}
32+
33+
img {
34+
width: 100%;
35+
}

app.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const Apollo = require('apollo-client');
2+
const fetch = require('isomorphic-fetch'); // eslint-disable-line no-unused-vars
3+
const gql = require('graphql-tag');
4+
5+
module.exports = w => {
6+
const enableEvents = typeof window !== 'undefined'; // only enable events if we are in the browser
7+
const initialRoute = w.location.pathname.replace(w.document.querySelector('base').getAttribute('href'), '').replace('/','');
8+
const routes = [
9+
{route:'agriculture', title:'Agriculture'},
10+
{route:'food', title:'Food'},
11+
{route:'retail', title:'Retail'}
12+
];
13+
const sectors = {
14+
'agriculture': 1,
15+
'food': 12,
16+
'retail': 7
17+
};
18+
19+
const client = new Apollo.ApolloClient({
20+
networkInterface: Apollo.createNetworkInterface({
21+
uri: 'https://api.kivaws.org/graphql'
22+
})
23+
});
24+
25+
// get loan data from the graphql endpoint
26+
const query = sector => client.query({
27+
query: gql`{
28+
loans(limit: 3, filters: {
29+
sector: ${sector}
30+
}) {
31+
values { name, use, image { url(presetSize: loan_default) } }
32+
}
33+
}`
34+
});
35+
36+
// attach dom event handlers after render
37+
const onRender = () => {
38+
// navigate on the client when links are clicked
39+
w.document.querySelectorAll('a').forEach(element => {
40+
element.addEventListener('click', event => {
41+
event.preventDefault();
42+
const route = event.target.getAttribute('href');
43+
w.history.pushState(route, `view:${route}`, route);
44+
render({route, loans:[]});
45+
query(sectors[route]).then(result => {
46+
render({
47+
route,
48+
loans: result.data && result.data.loans && result.data.loans.values || []
49+
});
50+
})
51+
});
52+
});
53+
};
54+
55+
// render to the dom using context ctx
56+
const render = ctx => {
57+
const existingRoot = w.document.getElementById('root');
58+
const newRoot = w.document.createElement('div');
59+
newRoot.id = 'root';
60+
61+
newRoot.innerHTML = `
62+
${routes.reduce((html, {route,title}) => `${html}
63+
${route === ctx.route ? `
64+
<span>${title}</span>
65+
` : `
66+
<a href="${route}">${title}</a>
67+
`}
68+
`,'')}
69+
<ul>
70+
${ctx.loans.reduce((html, loan) => `${html}
71+
<li>
72+
<img src="${loan.image.url}" alt="Picture of ${loan.name}">
73+
<h2>${loan.name}</h2>
74+
</li>
75+
`,'')}
76+
</ul>
77+
`;
78+
79+
if(existingRoot) {
80+
w.document.body.replaceChild(newRoot, existingRoot);
81+
}
82+
else {
83+
w.document.body.insertBefore(newRoot, w.document.body.firstChild);
84+
}
85+
86+
if(enableEvents) {
87+
onRender();
88+
}
89+
};
90+
91+
// attach browser navigation event handler
92+
if(enableEvents) {
93+
// update page if back/forward used
94+
w.addEventListener('popstate', event => {
95+
render({route: event.state});
96+
});
97+
}
98+
99+
// initial fetch/render
100+
return new Promise((resolve, reject) => {
101+
if(sectors[initialRoute] === undefined) {
102+
render({
103+
route: initialRoute,
104+
loans: []
105+
});
106+
resolve();
107+
}
108+
else {
109+
query(sectors[initialRoute]).then(result => {
110+
render({
111+
route: initialRoute,
112+
loans: result.data && result.data.loans && result.data.loans.values || []
113+
});
114+
resolve();
115+
}).catch(err => reject(err));
116+
}
117+
});
118+
}

client.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import './app.css';
2+
import app from './app';
3+
app(window);

package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "lambda-view-server",
3+
"version": "1.0.0",
4+
"description": "A no-db web server using AWS Lambda",
5+
"main": "server.js",
6+
"author": "emuvente",
7+
"license": "MIT",
8+
"scripts": {
9+
"deploy": "webpack && sls deploy"
10+
},
11+
"devDependencies": {
12+
"babel-core": "^6.24.1",
13+
"babel-loader": "^6.4.1",
14+
"babel-preset-env": "^1.4.0",
15+
"css-loader": "^0.28.0",
16+
"extract-text-webpack-plugin": "^2.1.0",
17+
"serverless-plugin-optimize": "^2.0.1-rc.1",
18+
"serverless-s3-assets": "^1.0.0",
19+
"webpack": "^2.4.1"
20+
},
21+
"dependencies": {
22+
"apollo-client": "^1.0.4",
23+
"graphql-tag": "^2.0.0",
24+
"isomorphic-fetch": "^2.2.1",
25+
"jsdom": "^9.12.0"
26+
}
27+
}

server.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint no-console: 0 */
2+
3+
const jsdom = require('jsdom');
4+
const app = require('./app');
5+
6+
module.exports.handle = (request, context, callback) => {
7+
const route = request.pathParameters && request.pathParameters.route || '';
8+
const docType = '<!DOCTYPE html>';
9+
const staticRoot = process.env.STATIC_ROOT || '';
10+
11+
jsdom.env({
12+
url: process.env.API_HOST + route,
13+
html: `
14+
${docType}
15+
<html>
16+
<head>
17+
<base href="/dev/view/" />
18+
<link rel="shortcut icon" href="${staticRoot}/favicon.ico" type="image/x-icon">
19+
<link rel="icon" href="${staticRoot}/favicon.ico" type="image/x-icon">
20+
<link rel="stylesheet" type="text/css" href="${staticRoot}/css/styles.css" />
21+
</head>
22+
<body>
23+
<script src="${staticRoot}/bundle.js"></script>
24+
</body>
25+
</html>
26+
`,
27+
done: (error, window) => {
28+
if(error) {
29+
callback(error);
30+
}
31+
else {
32+
app(window).then(() => {
33+
callback(null, {
34+
statusCode: 200,
35+
headers: {
36+
'content-type': 'text/html'
37+
},
38+
body: docType + window.document.documentElement.outerHTML
39+
});
40+
}).catch(err => console.error(err));
41+
}
42+
}
43+
});
44+
}

serverless.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
service: viewServer
2+
provider:
3+
name: aws
4+
runtime: nodejs6.10
5+
region: us-west-1
6+
package:
7+
individually: true
8+
exclude:
9+
- ./**
10+
include:
11+
- server.js
12+
- app.js
13+
- node_modules/**
14+
functions:
15+
server:
16+
handler: server.handle
17+
environment:
18+
API_HOST: ${env:AWS_API_HOST}
19+
STATIC_ROOT: ${env:AWS_STATIC_ROOT}
20+
events:
21+
- http: ANY view/
22+
- http: ANY view/{route+}
23+
plugins:
24+
- serverless-s3-assets
25+
- serverless-plugin-optimize
26+
custom:
27+
s3Assets:
28+
static:
29+
bucket: viewserver-dev-assets
30+
css:
31+
contentType: text/css

0 commit comments

Comments
 (0)