diff --git a/app/src/api/api.ts b/app/src/api/api.ts index 3401bf39..3b799a38 100644 --- a/app/src/api/api.ts +++ b/app/src/api/api.ts @@ -1,161 +1,18 @@ import express from 'express'; +import bodyParser from 'body-parser'; - -import { authUser, createUser, getUserById, authAPIUser, getNewUserAPIKey } from './services/user'; -import { - queryBuildingsAtPoint, - queryBuildingsByReference, - getBuildingById, - getBuildingLikeById, - getBuildingUPRNsById, - saveBuilding, - likeBuilding, - unlikeBuilding -} from './services/building'; +import { authUser, createUser, getUserById, getNewUserAPIKey } from './services/user'; import { queryLocation } from './services/search'; -const server = express.Router(); +import buildingsRouter from './routes/buildingsRouter'; -// 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' }) - }) -}); +const server = express(); -// 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' }) - }) -}); +// parse POSTed json body +server.use(bodyParser.json()); -// 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' }) - ) - } - }) +server.use('/buildings', buildingsRouter); // POST new user server.post('/users', function (req, res) { diff --git a/app/src/api/controllers/buildingController.ts b/app/src/api/controllers/buildingController.ts new file mode 100644 index 00000000..7987293d --- /dev/null +++ b/app/src/api/controllers/buildingController.ts @@ -0,0 +1,154 @@ +import * as buildingService from '../services/building'; +import * as userService from '../services/user'; + + +// GET buildings +// not implemented - may be useful to GET all buildings, paginated + +// GET buildings at point +function getBuildingsByLocation(req, res) { + const { lng, lat } = req.query; + buildingService.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) +function getBuildingsByReference(req, res) { + const { key, id } = req.query; + buildingService.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 +function getBuildingById(req, res) { + const { building_id } = req.params; + buildingService.getBuildingById(building_id).then(function (result) { + res.send(result); + }).catch(function (error) { + console.error(error); + res.send({ error: 'Database error' }) + }) +} + +function updateBuildingById(req, res) { + if (req.session.user_id) { + updateBuilding(req, res, req.session.user_id); + } else if (req.query.api_key) { + userService.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; + buildingService.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 +function getBuildingUPRNsById(req, res) { + const { building_id } = req.params; + buildingService.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 +function getBuildingLikeById(req, res) { + if (!req.session.user_id) { + res.send({ like: false }); // not logged in, so cannot have liked + return + } + const { building_id } = req.params; + buildingService.getBuildingLikeById(building_id, req.session.user_id).then(like => { + // any value returned means like + res.send({ like: like }) + }).catch( + () => res.send({ error: 'Database error' }) + ) +} + +function updateBuildingLikeById(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) { + buildingService.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 { + buildingService.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' }) + ) + } +} + +export default { + getBuildingsByLocation, + getBuildingsByReference, + getBuildingById, + updateBuildingById, + getBuildingUPRNsById, + getBuildingLikeById, + updateBuildingLikeById +}; \ No newline at end of file diff --git a/app/src/api/routes/buildingsRouter.ts b/app/src/api/routes/buildingsRouter.ts new file mode 100644 index 00000000..a8921ab6 --- /dev/null +++ b/app/src/api/routes/buildingsRouter.ts @@ -0,0 +1,32 @@ +import express from 'express'; + +import buildingController from '../controllers/buildingController'; + +const router = express.Router(); + + +// GET buildings +// not implemented - may be useful to GET all buildings, paginated + +// GET buildings at point +router.get('/locate', buildingController.getBuildingsByLocation); + +// GET buildings by reference (UPRN/TOID or other identifier) +router.get('/reference', buildingController.getBuildingsByReference); + +router.route('/:building_id.json') + // GET individual building + .get(buildingController.getBuildingById) + // POST building updates + .post(buildingController.updateBuildingById); + + +// GET building UPRNs +router.get('/:building_id/uprns.json', buildingController.getBuildingUPRNsById); + +// GET/POST like building +router.route('/:building_id/like.json') + .get(buildingController.getBuildingLikeById) + .post(buildingController.updateBuildingLikeById); + +export default router; \ No newline at end of file diff --git a/app/src/frontend/app.tsx b/app/src/frontend/app.tsx index 0c824689..c060313b 100644 --- a/app/src/frontend/app.tsx +++ b/app/src/frontend/app.tsx @@ -85,7 +85,7 @@ class App extends React.Component { // TODO: add proper types selectBuilding(building) { this.increaseRevision(building.revision_id); // get UPRNs and update - fetch(`/building/${building.building_id}/uprns.json`, { + fetch(`/api/buildings/${building.building_id}/uprns.json`, { method: 'GET', headers:{ 'Content-Type': 'application/json' @@ -106,7 +106,7 @@ class App extends React.Component { // TODO: add proper types }); // get if liked and update - fetch(`/building/${building.building_id}/like.json`, { + fetch(`/api/buildings/${building.building_id}/like.json`, { method: 'GET', headers:{ 'Content-Type': 'application/json' @@ -147,7 +147,7 @@ class App extends React.Component { // TODO: add proper types } likeBuilding(buildingId) { - fetch(`/building/${buildingId}/like.json`, { + fetch(`/api/buildings/${buildingId}/like.json`, { method: 'POST', headers:{ 'Content-Type': 'application/json' @@ -168,7 +168,7 @@ class App extends React.Component { // TODO: add proper types } updateBuilding(buildingId, data){ - fetch(`/building/${buildingId}.json`, { + fetch(`/api/buildings/${buildingId}.json`, { method: 'POST', body: JSON.stringify(data), headers:{ diff --git a/app/src/frontend/building-edit.tsx b/app/src/frontend/building-edit.tsx index 80da4816..e7cc9eb8 100644 --- a/app/src/frontend/building-edit.tsx +++ b/app/src/frontend/building-edit.tsx @@ -176,7 +176,7 @@ class EditForm extends Component { // TODO: add proper types event.preventDefault(); const like = event.target.checked; - fetch(`/building/${this.props.building_id}/like.json`, { + fetch(`/api/buildings/${this.props.building_id}/like.json`, { method: 'POST', headers:{ 'Content-Type': 'application/json' @@ -203,7 +203,7 @@ class EditForm extends Component { // TODO: add proper types event.preventDefault(); this.setState({error: undefined}) - fetch(`/building/${this.props.building_id}.json`, { + fetch(`/api/buildings/${this.props.building_id}.json`, { method: 'POST', body: JSON.stringify(this.state), headers:{ diff --git a/app/src/frontend/login.tsx b/app/src/frontend/login.tsx index 569ec4c1..bd8c7302 100644 --- a/app/src/frontend/login.tsx +++ b/app/src/frontend/login.tsx @@ -39,7 +39,7 @@ class Login extends Component { // TODO: add proper types event.preventDefault(); this.setState({error: undefined}) - fetch('/login', { + fetch('/api/login', { method: 'POST', body: JSON.stringify(this.state), headers:{ @@ -52,7 +52,7 @@ class Login extends Component { // TODO: add proper types if (res.error) { this.setState({error: res.error}) } else { - fetch('/users/me', { + fetch('/api/users/me', { credentials: 'same-origin' }).then( (res) => res.json() diff --git a/app/src/frontend/map.tsx b/app/src/frontend/map.tsx index 013b04e6..e32c52b2 100644 --- a/app/src/frontend/map.tsx +++ b/app/src/frontend/map.tsx @@ -61,7 +61,7 @@ class ColouringMap extends Component { // TODO: add proper types const newCat = parseCategoryURL(this.props.match.url); const mapCat = newCat || 'age'; fetch( - '/buildings/locate?lat='+lat+'&lng='+lng + '/api/buildings/locate?lat='+lat+'&lng='+lng ).then( (res) => res.json() ).then(function(data){ diff --git a/app/src/frontend/my-account.tsx b/app/src/frontend/my-account.tsx index 3094e5cc..32c4a7ec 100644 --- a/app/src/frontend/my-account.tsx +++ b/app/src/frontend/my-account.tsx @@ -30,7 +30,7 @@ class MyAccountPage extends Component { // TODO: add proper types event.preventDefault(); this.setState({error: undefined}); - fetch('/logout', { + fetch('/api/logout', { method: 'POST', credentials: 'same-origin' }).then( @@ -50,7 +50,7 @@ class MyAccountPage extends Component { // TODO: add proper types event.preventDefault(); this.setState({error: undefined}); - fetch('/api/key', { + fetch('/api/api/key', { method: 'POST', credentials: 'same-origin' }).then( diff --git a/app/src/frontend/search-box.tsx b/app/src/frontend/search-box.tsx index 52b5a988..e33b05bc 100644 --- a/app/src/frontend/search-box.tsx +++ b/app/src/frontend/search-box.tsx @@ -78,7 +78,7 @@ class SearchBox extends Component { // TODO: add proper types }) fetch( - '/search?q='+this.state.q + '/api/search?q='+this.state.q ).then( (res) => res.json() ).then((data) => { diff --git a/app/src/frontend/signup.tsx b/app/src/frontend/signup.tsx index d78263fb..1163d933 100644 --- a/app/src/frontend/signup.tsx +++ b/app/src/frontend/signup.tsx @@ -42,7 +42,7 @@ class SignUp extends Component { // TODO: add proper types event.preventDefault(); this.setState({error: undefined}) - fetch('/users', { + fetch('/api/users', { method: 'POST', body: JSON.stringify(this.state), headers:{ @@ -55,7 +55,7 @@ class SignUp extends Component { // TODO: add proper types if (res.error) { this.setState({error: res.error}) } else { - fetch('/users/me', { + fetch('/api/users/me', { credentials: 'same-origin' }).then( (res) => res.json() diff --git a/app/src/server.tsx b/app/src/server.tsx index 581d772e..ce807088 100644 --- a/app/src/server.tsx +++ b/app/src/server.tsx @@ -10,7 +10,6 @@ import express from 'express'; import { renderToString } from 'react-dom/server'; import serialize from 'serialize-javascript'; -import bodyParser from 'body-parser'; import session from 'express-session'; import pgConnect from 'connect-pg-simple'; @@ -23,7 +22,7 @@ import { getBuildingUPRNsById } from './api/services/building'; import tileserver from './tiles/tileserver'; -import apiRouter from './api/api'; +import apiServer from './api/api'; import { parseBuildingURL } from './parse'; // create server @@ -38,8 +37,6 @@ 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 const pgSession = pgConnect(session); @@ -159,6 +156,6 @@ function renderHTML(context, data, req, res) { server.use('/tiles', tileserver); -server.use('/api', apiRouter); +server.use('/api', apiServer); export default server; diff --git a/etl/join_building_data/load_conservation_areas.py b/etl/join_building_data/load_conservation_areas.py index cd84c595..48832423 100644 --- a/etl/join_building_data/load_conservation_areas.py +++ b/etl/join_building_data/load_conservation_areas.py @@ -59,7 +59,7 @@ def save_data(building_id, data, api_key, base_url): """Save data to a building """ r = requests.post( - "{}/building/{}.json?api_key={}".format(base_url, building_id, api_key), + "{}/buildings/{}.json?api_key={}".format(base_url, building_id, api_key), json=data ) diff --git a/etl/join_building_data/load_data.py b/etl/join_building_data/load_data.py index 28ac7eca..4b805844 100644 --- a/etl/join_building_data/load_data.py +++ b/etl/join_building_data/load_data.py @@ -89,7 +89,7 @@ def save_data(building_id, data, api_key, base_url): """Save data to a building """ r = requests.post( - "{}/building/{}.json?api_key={}".format(base_url, building_id, api_key), + "{}/buildings/{}.json?api_key={}".format(base_url, building_id, api_key), json=data )