From a2394ef9627d5735babb548f9f3fe61a4a5b57b1 Mon Sep 17 00:00:00 2001 From: Maciej Ziarkowski Date: Wed, 14 Aug 2019 10:54:13 +0100 Subject: [PATCH] Move JSON API into its own directory --- app/src/api/api.ts | 280 +++++++++++++++++++++++++ app/src/api/{ => services}/building.ts | 4 +- app/src/api/{ => services}/search.ts | 2 +- app/src/api/{ => services}/user.ts | 2 +- app/src/server.tsx | 275 +----------------------- 5 files changed, 289 insertions(+), 274 deletions(-) create mode 100644 app/src/api/api.ts rename app/src/api/{ => services}/building.ts (99%) rename app/src/api/{ => services}/search.ts (96%) rename app/src/api/{ => services}/user.ts (99%) diff --git a/app/src/api/api.ts b/app/src/api/api.ts new file mode 100644 index 00000000..3401bf39 --- /dev/null +++ b/app/src/api/api.ts @@ -0,0 +1,280 @@ +import express from 'express'; + + +import { authUser, createUser, getUserById, authAPIUser, getNewUserAPIKey } from './services/user'; +import { + queryBuildingsAtPoint, + queryBuildingsByReference, + getBuildingById, + getBuildingLikeById, + getBuildingUPRNsById, + saveBuilding, + likeBuilding, + unlikeBuilding +} from './services/building'; +import { queryLocation } from './services/search'; + +const server = express.Router(); + +// GET buildings +// not implemented - may be useful to GET all buildings, paginated + +// GET buildings at point +server.get('/buildings/locate', function (req, res) { + const { lng, lat } = req.query; + queryBuildingsAtPoint(lng, lat).then(function (result) { + res.send(result); + }).catch(function (error) { + console.error(error); + res.send({ error: 'Database error' }) + }) +}); + +// GET buildings by reference (UPRN/TOID or other identifier) +server.get('/buildings/reference', function (req, res) { + const { key, id } = req.query; + queryBuildingsByReference(key, id).then(function (result) { + res.send(result); + }).catch(function (error) { + console.error(error); + res.send({ error: 'Database error' }) + }) +}); + +// GET individual building, POST building updates +server.route('/building/:building_id.json') + .get(function (req, res) { + const { building_id } = req.params; + getBuildingById(building_id).then(function (result) { + res.send(result); + }).catch(function (error) { + console.error(error); + res.send({ error: 'Database error' }) + }) + }) + .post(function (req, res) { + if (req.session.user_id) { + updateBuilding(req, res, req.session.user_id); + } else if (req.query.api_key) { + authAPIUser(req.query.api_key) + .then(function (user) { + updateBuilding(req, res, user.user_id) + }) + .catch(function (err) { + console.error(err); + res.send({ error: 'Must be logged in' }); + }); + } else { + res.send({ error: 'Must be logged in' }); + } + }) + +function updateBuilding(req, res, userId) { + const { building_id } = req.params; + const building = req.body; + saveBuilding(building_id, building, userId).then(building => { + if (building.error) { + res.send(building) + return + } + if (typeof (building) === 'undefined') { + res.send({ error: 'Database error' }) + return + } + res.send(building) + }).catch( + () => res.send({ error: 'Database error' }) + ) +} + +// GET building UPRNs +server.get('/building/:building_id/uprns.json', function (req, res) { + const { building_id } = req.params; + getBuildingUPRNsById(building_id).then(function (result) { + if (typeof (result) === 'undefined') { + res.send({ error: 'Database error' }) + return + } + res.send({ + uprns: result + }); + }).catch(function (error) { + console.error(error); + res.send({ error: 'Database error' }) + }) +}) + +// GET/POST like building +server.route('/building/:building_id/like.json') + .get(function (req, res) { + if (!req.session.user_id) { + res.send({ like: false }); // not logged in, so cannot have liked + return + } + const { building_id } = req.params; + getBuildingLikeById(building_id, req.session.user_id).then(like => { + // any value returned means like + res.send({ like: like }) + }).catch( + () => res.send({ error: 'Database error' }) + ) + }) + .post(function (req, res) { + if (!req.session.user_id) { + res.send({ error: 'Must be logged in' }); + return + } + const { building_id } = req.params; + const { like } = req.body; + if (like) { + likeBuilding(building_id, req.session.user_id).then(building => { + if (building.error) { + res.send(building) + return + } + if (typeof (building) === 'undefined') { + res.send({ error: 'Database error' }) + return + } + res.send(building) + }).catch( + () => res.send({ error: 'Database error' }) + ) + } else { + unlikeBuilding(building_id, req.session.user_id).then(building => { + if (building.error) { + res.send(building) + return + } + if (typeof (building) === 'undefined') { + res.send({ error: 'Database error' }) + return + } + res.send(building) + }).catch( + () => res.send({ error: 'Database error' }) + ) + } + }) + +// POST new user +server.post('/users', function (req, res) { + const user = req.body; + if (req.session.user_id) { + res.send({ error: 'Already signed in' }); + return + } + + if (user.email) { + if (user.email != user.confirm_email) { + res.send({ error: 'Email did not match confirmation.' }); + return + } + } else { + user.email = null; + } + + createUser(user).then(function (result) { + if (result.user_id) { + req.session.user_id = result.user_id; + res.send({ user_id: result.user_id }); + } else { + req.session.user_id = undefined; + res.send({ error: result.error }); + } + }).catch(function (err) { + console.error(err); + res.send(err) + }); +}); + +// POST user auth +server.post('/login', function (req, res) { + authUser(req.body.username, req.body.password).then(function (user: any) { // TODO: remove any + if (user.user_id) { + req.session.user_id = user.user_id; + } else { + req.session.user_id = undefined; + } + res.send(user); + }).catch(function (error) { + res.send(error); + }) +}); + +// POST user logout +server.post('/logout', function (req, res) { + req.session.user_id = undefined; + req.session.destroy(function (err) { + if (err) { + console.error(err); + res.send({ error: 'Failed to end session' }) + } + res.send({ success: true }); + }); +}); + +// GET own user info +server.get('/users/me', function (req, res) { + if (!req.session.user_id) { + res.send({ error: 'Must be logged in' }); + return + } + + getUserById(req.session.user_id).then(function (user) { + res.send(user); + }).catch(function (error) { + res.send(error); + }); +}); + +// POST generate API key +server.post('/api/key', function (req, res) { + if (!req.session.user_id) { + res.send({ error: 'Must be logged in' }); + return + } + + getNewUserAPIKey(req.session.user_id).then(function (apiKey) { + res.send(apiKey); + }).catch(function (error) { + res.send(error); + }); +}) + +// GET search +server.get('/search', function (req, res) { + const searchTerm = req.query.q; + if (!searchTerm) { + res.send({ + error: 'Please provide a search term' + }) + return + } + queryLocation(searchTerm).then((results) => { + if (typeof (results) === 'undefined') { + 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 + } + }) + }) + }).catch(function (error) { + res.send(error); + }); +}) + +export default server; \ No newline at end of file diff --git a/app/src/api/building.ts b/app/src/api/services/building.ts similarity index 99% rename from app/src/api/building.ts rename to app/src/api/services/building.ts index 39bf8d10..4d4befcc 100644 --- a/app/src/api/building.ts +++ b/app/src/api/services/building.ts @@ -2,8 +2,8 @@ * Building data access * */ -import db from '../db'; -import { removeAllAtBbox } from '../tiles/cache'; +import db from '../../db'; +import { removeAllAtBbox } from '../../tiles/cache'; // data type note: PostgreSQL bigint (64-bit) is handled as string in JavaScript, because of // JavaScript numerics are 64-bit double, giving only partial coverage. diff --git a/app/src/api/search.ts b/app/src/api/services/search.ts similarity index 96% rename from app/src/api/search.ts rename to app/src/api/services/search.ts index 08fbebe3..0defa117 100644 --- a/app/src/api/search.ts +++ b/app/src/api/services/search.ts @@ -6,7 +6,7 @@ * - this DOES expose geometry, another reason to keep this clearly separated from building * data */ -import db from '../db'; +import db from '../../db'; function queryLocation(term) { const limit = 5; diff --git a/app/src/api/user.ts b/app/src/api/services/user.ts similarity index 99% rename from app/src/api/user.ts rename to app/src/api/services/user.ts index 7cb3b6c2..da7e2e5d 100644 --- a/app/src/api/user.ts +++ b/app/src/api/services/user.ts @@ -2,7 +2,7 @@ * User data access * */ -import db from '../db'; +import db from '../../db'; function createUser(user) { if (!user.password || user.password.length < 8) { diff --git a/app/src/server.tsx b/app/src/server.tsx index fb80bbe9..581d772e 100644 --- a/app/src/server.tsx +++ b/app/src/server.tsx @@ -16,19 +16,14 @@ import pgConnect from 'connect-pg-simple'; import App from './frontend/app'; import db from './db'; -import { authUser, createUser, getUserById, authAPIUser, getNewUserAPIKey } from './api/user'; +import { getUserById } from './api/services/user'; import { - queryBuildingsAtPoint, - queryBuildingsByReference, getBuildingById, getBuildingLikeById, - getBuildingUPRNsById, - saveBuilding, - likeBuilding, - unlikeBuilding -} from './api/building'; -import { queryLocation } from './api/search'; + getBuildingUPRNsById +} from './api/services/building'; import tileserver from './tiles/tileserver'; +import apiRouter from './api/api'; import { parseBuildingURL } from './parse'; // create server @@ -162,268 +157,8 @@ function renderHTML(context, data, req, res) { } } -// GET tiles server.use('/tiles', tileserver); -// GET buildings -// not implemented - may be useful to GET all buildings, paginated - -// GET buildings at point -server.get('/buildings/locate', function (req, res) { - const { lng, lat } = req.query; - queryBuildingsAtPoint(lng, lat).then(function (result) { - res.send(result); - }).catch(function (error) { - console.error(error); - res.send({ error: 'Database error' }) - }) -}); - -// GET buildings by reference (UPRN/TOID or other identifier) -server.get('/buildings/reference', function (req, res) { - const { key, id } = req.query; - queryBuildingsByReference(key, id).then(function (result) { - res.send(result); - }).catch(function (error) { - console.error(error); - res.send({ error: 'Database error' }) - }) -}); - -// GET individual building, POST building updates -server.route('/building/:building_id.json') - .get(function (req, res) { - const { building_id } = req.params; - getBuildingById(building_id).then(function (result) { - res.send(result); - }).catch(function (error) { - console.error(error); - res.send({ error: 'Database error' }) - }) - }) - .post(function (req, res) { - if (req.session.user_id) { - updateBuilding(req, res, req.session.user_id); - } else if (req.query.api_key) { - authAPIUser(req.query.api_key) - .then(function (user) { - updateBuilding(req, res, user.user_id) - }) - .catch(function (err) { - console.error(err); - res.send({ error: 'Must be logged in' }); - }); - } else { - res.send({ error: 'Must be logged in' }); - } - }) - -function updateBuilding(req, res, userId) { - const { building_id } = req.params; - const building = req.body; - saveBuilding(building_id, building, userId).then(building => { - if (building.error) { - res.send(building) - return - } - if (typeof (building) === 'undefined') { - res.send({ error: 'Database error' }) - return - } - res.send(building) - }).catch( - () => res.send({ error: 'Database error' }) - ) -} - -// GET building UPRNs -server.get('/building/:building_id/uprns.json', function (req, res) { - const { building_id } = req.params; - getBuildingUPRNsById(building_id).then(function (result) { - if (typeof (result) === 'undefined') { - res.send({ error: 'Database error' }) - return - } - res.send({ - uprns: result - }); - }).catch(function (error) { - console.error(error); - res.send({ error: 'Database error' }) - }) -}) - -// GET/POST like building -server.route('/building/:building_id/like.json') - .get(function (req, res) { - if (!req.session.user_id) { - res.send({ like: false }); // not logged in, so cannot have liked - return - } - const { building_id } = req.params; - getBuildingLikeById(building_id, req.session.user_id).then(like => { - // any value returned means like - res.send({ like: like }) - }).catch( - () => res.send({ error: 'Database error' }) - ) - }) - .post(function (req, res) { - if (!req.session.user_id) { - res.send({ error: 'Must be logged in' }); - return - } - const { building_id } = req.params; - const { like } = req.body; - if (like) { - likeBuilding(building_id, req.session.user_id).then(building => { - if (building.error) { - res.send(building) - return - } - if (typeof (building) === 'undefined') { - res.send({ error: 'Database error' }) - return - } - res.send(building) - }).catch( - () => res.send({ error: 'Database error' }) - ) - } else { - unlikeBuilding(building_id, req.session.user_id).then(building => { - if (building.error) { - res.send(building) - return - } - if (typeof (building) === 'undefined') { - res.send({ error: 'Database error' }) - return - } - res.send(building) - }).catch( - () => res.send({ error: 'Database error' }) - ) - } - }) - -// POST new user -server.post('/users', function (req, res) { - const user = req.body; - if (req.session.user_id) { - res.send({ error: 'Already signed in' }); - return - } - - if (user.email) { - if (user.email != user.confirm_email) { - res.send({ error: 'Email did not match confirmation.' }); - return - } - } else { - user.email = null; - } - - createUser(user).then(function (result) { - if (result.user_id) { - req.session.user_id = result.user_id; - res.send({ user_id: result.user_id }); - } else { - req.session.user_id = undefined; - res.send({ error: result.error }); - } - }).catch(function (err) { - console.error(err); - res.send(err) - }); -}); - -// POST user auth -server.post('/login', function (req, res) { - authUser(req.body.username, req.body.password).then(function (user: any) { // TODO: remove any - if (user.user_id) { - req.session.user_id = user.user_id; - } else { - req.session.user_id = undefined; - } - res.send(user); - }).catch(function (error) { - res.send(error); - }) -}); - -// POST user logout -server.post('/logout', function (req, res) { - req.session.user_id = undefined; - req.session.destroy(function (err) { - if (err) { - console.error(err); - res.send({ error: 'Failed to end session' }) - } - res.send({ success: true }); - }); -}); - -// GET own user info -server.get('/users/me', function (req, res) { - if (!req.session.user_id) { - res.send({ error: 'Must be logged in' }); - return - } - - getUserById(req.session.user_id).then(function (user) { - res.send(user); - }).catch(function (error) { - res.send(error); - }); -}); - -// POST generate API key -server.post('/api/key', function (req, res) { - if (!req.session.user_id) { - res.send({ error: 'Must be logged in' }); - return - } - - getNewUserAPIKey(req.session.user_id).then(function (apiKey) { - res.send(apiKey); - }).catch(function (error) { - res.send(error); - }); -}) - -// GET search -server.get('/search', function (req, res) { - const searchTerm = req.query.q; - if (!searchTerm) { - res.send({ - error: 'Please provide a search term' - }) - return - } - queryLocation(searchTerm).then((results) => { - if (typeof (results) === 'undefined') { - 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 - } - }) - }) - }).catch(function (error) { - res.send(error); - }); -}) +server.use('/api', apiRouter); export default server;