colouring-montreal/app/src/server.js

430 lines
12 KiB
JavaScript
Raw Normal View History

2018-09-30 14:50:09 -04:00
/**
* Server-side Express application
* - API methods
* - entry-point to shared React App
*
*/
import React from 'react';
import { StaticRouter } from 'react-router-dom';
import express from 'express';
import { renderToString } from 'react-dom/server';
import serialize from 'serialize-javascript';
2019-02-24 14:28:11 -05:00
import bodyParser from 'body-parser';
2018-09-13 14:54:16 -04:00
import session from 'express-session';
import pgConnect from 'connect-pg-simple';
import App from './frontend/app';
2018-09-30 11:18:57 -04:00
import db from './db';
2019-02-24 07:17:59 -05:00
import { authUser, createUser, getUserById, authAPIUser, getNewUserAPIKey } from './api/user';
2019-01-22 12:02:03 -05:00
import {
queryBuildingsAtPoint,
queryBuildingsByReference,
getBuildingById,
getBuildingLikeById,
getBuildingUPRNsById,
saveBuilding,
2019-01-22 12:52:32 -05:00
likeBuilding,
unlikeBuilding
2019-02-24 07:17:59 -05:00
} from './api/building';
import { queryLocation } from './api/search';
import tileserver from './tiles/tileserver';
2018-09-13 15:36:14 -04:00
import { parseBuildingURL } from './parse';
// create server
const server = express();
// reference packed assets
const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
// disable header
server.disable('x-powered-by');
// serve static files
server.use(express.static(process.env.RAZZLE_PUBLIC_DIR));
// parse POSTed json body
server.use(bodyParser.json());
// handle user sessions
2018-09-13 14:54:16 -04:00
const pgSession = pgConnect(session);
const sess = {
name: 'cl.session',
store: new pgSession({
2018-09-30 11:18:57 -04:00
pgPromise: db,
2019-02-24 14:28:11 -05:00
tableName: 'user_sessions'
}),
secret: process.env.APP_COOKIE_SECRET,
saveUninitialized: false,
resave: false,
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 } // 30 days
};
if (server.get('env') === 'production') {
// trust first proxy
server.set('trust proxy', 1)
// serve secure cookies
sess.cookie.secure = true
}
server.use(session(sess));
// handle HTML routes (server-side rendered React)
server.get('/*.html', frontendRoute);
server.get('/', frontendRoute);
function frontendRoute(req, res) {
2018-09-13 15:41:42 -04:00
const context = {};
const data = {};
2018-09-13 15:41:42 -04:00
context.status = 200;
2018-09-10 18:32:56 -04:00
const user_id = req.session.user_id;
2018-09-11 18:30:17 -04:00
const building_id = parseBuildingURL(req.url);
2019-05-27 11:31:48 -04:00
const is_building = (typeof (building_id) !== 'undefined');
2019-02-24 14:28:11 -05:00
if (is_building && isNaN(building_id)) {
2018-09-13 15:41:42 -04:00
context.status = 404;
}
2018-09-11 18:30:17 -04:00
Promise.all([
2019-02-24 14:28:11 -05:00
user_id ? getUserById(user_id) : undefined,
is_building ? getBuildingById(building_id) : undefined,
is_building ? getBuildingUPRNsById(building_id) : undefined,
(is_building && user_id) ? getBuildingLikeById(building_id, user_id) : false
]).then(function (values) {
2018-09-11 18:30:17 -04:00
const user = values[0];
const building = values[1];
const uprns = values[2];
const building_like = values[3];
2019-05-27 11:31:48 -04:00
if (is_building && typeof (building) === 'undefined') {
2018-09-13 15:41:42 -04:00
context.status = 404
}
2018-09-11 18:30:17 -04:00
data.user = user;
data.building = building;
2019-01-22 12:34:46 -05:00
data.building_like = building_like;
if (data.building != null) {
data.building.uprns = uprns;
}
2018-09-13 15:41:42 -04:00
renderHTML(context, data, req, res)
}).catch(error => {
console.error(error);
data.user = undefined;
data.building = undefined;
2019-01-22 12:34:46 -05:00
data.building_like = undefined;
2018-10-04 17:44:04 -04:00
context.status = 500;
renderHTML(context, data, req, res);
});
2018-09-11 18:30:17 -04:00
}
2018-09-10 18:32:56 -04:00
2019-02-24 14:28:11 -05:00
function renderHTML(context, data, req, res) {
const markup = renderToString(
<StaticRouter context={context} location={req.url}>
2019-01-22 12:34:46 -05:00
<App user={data.user} building={data.building} building_like={data.building_like} />
</StaticRouter>
);
if (context.url) {
res.redirect(context.url);
} else {
res.status(context.status).send(
2019-02-24 14:28:11 -05:00
`<!doctype html>
<html lang="">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title>Colouring London</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
@font-face {
font-family: 'glacial_cl';
src: url('/fonts/glacialindifference-regular-webfont.woff2') format('woff2'),
url('/fonts/glacialindifference-regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
</style>
${
2019-02-24 14:28:11 -05:00
assets.client.css
? `<link rel="stylesheet" href="${assets.client.css}">`
: ''
}
${
2019-02-24 14:28:11 -05:00
process.env.NODE_ENV === 'production'
? `<script src="${assets.client.js}" defer></script>`
: `<script src="${assets.client.js}" defer crossorigin></script>`
}
</head>
<body>
<div id="root">${markup}</div>
<script>
window.__PRELOADED_STATE__ = ${serialize(data)}
</script>
</body>
</html>`
);
}
}
2018-09-10 05:44:32 -04:00
// GET tiles
server.use('/tiles', tileserver);
// GET buildings
// not implemented - may be useful to GET all buildings, paginated
// GET buildings at point
2019-02-24 14:28:11 -05:00
server.get('/buildings/locate', function (req, res) {
const { lng, lat } = req.query;
2019-02-24 14:28:11 -05:00
queryBuildingsAtPoint(lng, lat).then(function (result) {
res.send(result);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
console.error(error);
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
})
});
// GET buildings by reference (UPRN/TOID or other identifier)
2019-02-24 14:28:11 -05:00
server.get('/buildings/reference', function (req, res) {
const { key, id } = req.query;
2019-02-24 14:28:11 -05:00
queryBuildingsByReference(key, id).then(function (result) {
res.send(result);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
console.error(error);
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
})
});
// GET individual building, POST building updates
2018-09-11 18:30:17 -04:00
server.route('/building/:building_id.json')
.get(function (req, res) {
const { building_id } = req.params;
2019-02-24 14:28:11 -05:00
getBuildingById(building_id).then(function (result) {
2018-10-20 09:51:39 -04:00
res.send(result);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
console.error(error);
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
2018-09-11 18:30:17 -04:00
})
})
.post(function (req, res) {
if (req.session.user_id) {
updateBuilding(req, res, req.session.user_id);
2019-02-24 14:28:11 -05:00
} else if (req.query.api_key) {
authAPIUser(req.query.api_key)
2019-02-24 14:28:11 -05:00
.then(function (user) {
updateBuilding(req, res, user.user_id)
})
2019-02-24 14:28:11 -05:00
.catch(function (err) {
console.error(err);
2019-02-24 14:28:11 -05:00
res.send({ error: 'Must be logged in' });
});
} else {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Must be logged in' });
}
2018-09-11 18:30:17 -04:00
})
2019-02-24 14:28:11 -05:00
function updateBuilding(req, res, user_id) {
const { building_id } = req.params;
const building = req.body;
saveBuilding(building_id, building, user_id).then(building => {
if (building.error) {
res.send(building)
return
}
2019-05-27 11:31:48 -04:00
if (typeof (building) === 'undefined') {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
return
}
res.send(building)
}).catch(
2019-02-24 14:28:11 -05:00
() => res.send({ error: 'Database error' })
)
}
2019-02-05 16:41:15 -05:00
// GET building UPRNs
server.get('/building/:building_id/uprns.json', function (req, res) {
2019-02-24 14:28:11 -05:00
const { building_id } = req.params;
getBuildingUPRNsById(building_id).then(function (result) {
2019-05-27 11:31:48 -04:00
if (typeof (result) === 'undefined') {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
return
}
res.send({
uprns: result
});
}).catch(function (error) {
console.error(error);
res.send({ error: 'Database error' })
})
2019-02-24 14:28:11 -05:00
})
2019-01-22 12:02:03 -05:00
// GET/POST like building
server.route('/building/:building_id/like.json')
2019-02-24 14:28:11 -05:00
.get(function (req, res) {
2019-01-22 12:02:03 -05:00
if (!req.session.user_id) {
2019-02-24 14:28:11 -05:00
res.send({ like: false }); // not logged in, so cannot have liked
return
}
2019-01-22 12:02:03 -05:00
const { building_id } = req.params;
getBuildingLikeById(building_id, req.session.user_id).then(like => {
// any value returned means like
res.send({ like: like })
2019-01-22 12:02:03 -05:00
}).catch(
2019-02-24 14:28:11 -05:00
() => res.send({ error: 'Database error' })
2019-01-22 12:02:03 -05:00
)
})
2019-02-24 14:28:11 -05:00
.post(function (req, res) {
2019-01-22 12:02:03 -05:00
if (!req.session.user_id) {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Must be logged in' });
return
}
2019-01-22 12:02:03 -05:00
const { building_id } = req.params;
2019-01-22 12:52:32 -05:00
const { like } = req.body;
2019-02-24 14:28:11 -05:00
if (like) {
2019-01-22 12:52:32 -05:00
likeBuilding(building_id, req.session.user_id).then(building => {
if (building.error) {
res.send(building)
return
}
2019-05-27 11:31:48 -04:00
if (typeof (building) === 'undefined') {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
2019-01-22 12:52:32 -05:00
return
}
2019-01-22 12:02:03 -05:00
res.send(building)
2019-01-22 12:52:32 -05:00
}).catch(
2019-02-24 14:28:11 -05:00
() => res.send({ error: 'Database error' })
2019-01-22 12:52:32 -05:00
)
} else {
unlikeBuilding(building_id, req.session.user_id).then(building => {
if (building.error) {
res.send(building)
return
}
2019-05-27 11:31:48 -04:00
if (typeof (building) === 'undefined') {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Database error' })
2019-01-22 12:52:32 -05:00
return
}
res.send(building)
}).catch(
2019-02-24 14:28:11 -05:00
() => res.send({ error: 'Database error' })
2019-01-22 12:52:32 -05:00
)
}
2019-01-22 12:02:03 -05:00
})
// POST new user
2019-02-24 14:28:11 -05:00
server.post('/users', function (req, res) {
const user = req.body;
if (req.session.user_id) {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Already signed in' });
2018-09-13 11:58:05 -04:00
return
}
2019-02-24 14:28:11 -05:00
if (user.email) {
if (user.email != user.confirm_email) {
2019-05-27 11:31:48 -04:00
res.send({ error: 'Email did not match confirmation.' });
return
}
} else {
user.email = null;
}
2019-02-24 14:28:11 -05:00
createUser(user).then(function (result) {
if (result.user_id) {
req.session.user_id = result.user_id;
2019-02-24 14:28:11 -05:00
res.send({ user_id: result.user_id });
} else {
req.session.user_id = undefined;
2019-02-24 14:28:11 -05:00
res.send({ error: result.error });
}
2019-02-24 14:28:11 -05:00
}).catch(function (err) {
console.error(err);
2018-09-13 11:58:05 -04:00
res.send(err)
});
});
// POST user auth
2019-02-24 14:28:11 -05:00
server.post('/login', function (req, res) {
authUser(req.body.username, req.body.password).then(function (user) {
if (user.user_id) {
req.session.user_id = user.user_id;
} else {
req.session.user_id = undefined;
}
res.send(user);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
res.send(error);
})
});
// POST user logout
2019-02-24 14:28:11 -05:00
server.post('/logout', function (req, res) {
req.session.user_id = undefined;
2019-02-24 14:28:11 -05:00
req.session.destroy(function (err) {
if (err) {
console.error(err);
2019-02-24 14:28:11 -05:00
res.send({ error: 'Failed to end session' })
}
2019-02-24 14:28:11 -05:00
res.send({ success: true });
});
});
// GET own user info
2019-02-24 14:28:11 -05:00
server.get('/users/me', function (req, res) {
if (!req.session.user_id) {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Must be logged in' });
return
}
2019-02-24 14:28:11 -05:00
getUserById(req.session.user_id).then(function (user) {
res.send(user);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
res.send(error);
});
});
2018-10-20 07:20:10 -04:00
// POST generate API key
2019-02-24 14:28:11 -05:00
server.post('/api/key', function (req, res) {
2018-10-20 07:20:10 -04:00
if (!req.session.user_id) {
2019-02-24 14:28:11 -05:00
res.send({ error: 'Must be logged in' });
2018-10-20 07:20:10 -04:00
return
}
2019-02-24 14:28:11 -05:00
getNewUserAPIKey(req.session.user_id).then(function (api_key) {
2018-10-20 07:20:10 -04:00
res.send(api_key);
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
2018-10-20 07:20:10 -04:00
res.send(error);
});
})
2019-02-05 08:37:44 -05:00
// GET search
2019-02-24 14:28:11 -05:00
server.get('/search', function (req, res) {
2019-02-05 08:37:44 -05:00
const search_term = req.query.q;
2019-02-24 14:28:11 -05:00
if (!search_term) {
2019-02-05 08:37:44 -05:00
res.send({
error: 'Please provide a search term'
})
return
}
queryLocation(search_term).then((results) => {
2019-05-27 11:31:48 -04:00
if (typeof (results) === 'undefined') {
2019-02-05 08:37:44 -05:00
res.send({
error: 'Database error'
})
return
}
res.send({
results: results.map(item => {
// map from DB results to GeoJSON Feature objects
const geom = JSON.parse(item.st_asgeojson)
return {
type: 'Feature',
attributes: {
label: item.search_str,
zoom: item.zoom || 9
},
geometry: geom
}
})
})
2019-02-24 14:28:11 -05:00
}).catch(function (error) {
2019-02-05 08:37:44 -05:00
res.send(error);
});
})
export default server;