Merge pull request #386 from mz8i/feature/383-refactor-routes
Refactor API and modify routes
This commit is contained in:
commit
cb64457cca
141
app/src/api/api.ts
Normal file
141
app/src/api/api.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
|
||||||
|
import { authUser, createUser, getUserById, getNewUserAPIKey } from './services/user';
|
||||||
|
import { queryLocation } from './services/search';
|
||||||
|
|
||||||
|
import buildingsRouter from './routes/buildingsRouter';
|
||||||
|
|
||||||
|
|
||||||
|
const server = express.Router()
|
||||||
|
|
||||||
|
// parse POSTed json body
|
||||||
|
server.use(bodyParser.json());
|
||||||
|
|
||||||
|
server.use('/buildings', buildingsRouter);
|
||||||
|
|
||||||
|
// 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((req, res) => {
|
||||||
|
res.status(404).json({ error: 'Resource not found'});
|
||||||
|
})
|
||||||
|
|
||||||
|
export default server;
|
154
app/src/api/controllers/buildingController.ts
Normal file
154
app/src/api/controllers/buildingController.ts
Normal file
@ -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
|
||||||
|
};
|
32
app/src/api/routes/buildingsRouter.ts
Normal file
32
app/src/api/routes/buildingsRouter.ts
Normal file
@ -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;
|
@ -2,8 +2,8 @@
|
|||||||
* Building data access
|
* Building data access
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import db from '../db';
|
import db from '../../db';
|
||||||
import { removeAllAtBbox } from '../tiles/cache';
|
import { removeAllAtBbox } from '../../tiles/cache';
|
||||||
|
|
||||||
// data type note: PostgreSQL bigint (64-bit) is handled as string in JavaScript, because of
|
// 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.
|
// JavaScript numerics are 64-bit double, giving only partial coverage.
|
@ -6,7 +6,7 @@
|
|||||||
* - this DOES expose geometry, another reason to keep this clearly separated from building
|
* - this DOES expose geometry, another reason to keep this clearly separated from building
|
||||||
* data
|
* data
|
||||||
*/
|
*/
|
||||||
import db from '../db';
|
import db from '../../db';
|
||||||
|
|
||||||
function queryLocation(term) {
|
function queryLocation(term) {
|
||||||
const limit = 5;
|
const limit = 5;
|
@ -2,7 +2,7 @@
|
|||||||
* User data access
|
* User data access
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import db from '../db';
|
import db from '../../db';
|
||||||
|
|
||||||
function createUser(user) {
|
function createUser(user) {
|
||||||
if (!user.password || user.password.length < 8) {
|
if (!user.password || user.password.length < 8) {
|
@ -85,7 +85,7 @@ class App extends React.Component<any, any> { // TODO: add proper types
|
|||||||
selectBuilding(building) {
|
selectBuilding(building) {
|
||||||
this.increaseRevision(building.revision_id);
|
this.increaseRevision(building.revision_id);
|
||||||
// get UPRNs and update
|
// get UPRNs and update
|
||||||
fetch(`/building/${building.building_id}/uprns.json`, {
|
fetch(`/api/buildings/${building.building_id}/uprns.json`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers:{
|
headers:{
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -106,7 +106,7 @@ class App extends React.Component<any, any> { // TODO: add proper types
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get if liked and update
|
// get if liked and update
|
||||||
fetch(`/building/${building.building_id}/like.json`, {
|
fetch(`/api/buildings/${building.building_id}/like.json`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers:{
|
headers:{
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -147,7 +147,7 @@ class App extends React.Component<any, any> { // TODO: add proper types
|
|||||||
}
|
}
|
||||||
|
|
||||||
likeBuilding(buildingId) {
|
likeBuilding(buildingId) {
|
||||||
fetch(`/building/${buildingId}/like.json`, {
|
fetch(`/api/buildings/${buildingId}/like.json`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers:{
|
headers:{
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -168,7 +168,7 @@ class App extends React.Component<any, any> { // TODO: add proper types
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateBuilding(buildingId, data){
|
updateBuilding(buildingId, data){
|
||||||
fetch(`/building/${buildingId}.json`, {
|
fetch(`/api/buildings/${buildingId}.json`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
headers:{
|
headers:{
|
||||||
|
@ -176,7 +176,7 @@ class EditForm extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const like = event.target.checked;
|
const like = event.target.checked;
|
||||||
|
|
||||||
fetch(`/building/${this.props.building_id}/like.json`, {
|
fetch(`/api/buildings/${this.props.building_id}/like.json`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers:{
|
headers:{
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -203,7 +203,7 @@ class EditForm extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({error: undefined})
|
this.setState({error: undefined})
|
||||||
|
|
||||||
fetch(`/building/${this.props.building_id}.json`, {
|
fetch(`/api/buildings/${this.props.building_id}.json`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(this.state),
|
body: JSON.stringify(this.state),
|
||||||
headers:{
|
headers:{
|
||||||
|
@ -39,7 +39,7 @@ class Login extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({error: undefined})
|
this.setState({error: undefined})
|
||||||
|
|
||||||
fetch('/login', {
|
fetch('/api/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(this.state),
|
body: JSON.stringify(this.state),
|
||||||
headers:{
|
headers:{
|
||||||
@ -52,7 +52,7 @@ class Login extends Component<any, any> { // TODO: add proper types
|
|||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.setState({error: res.error})
|
this.setState({error: res.error})
|
||||||
} else {
|
} else {
|
||||||
fetch('/users/me', {
|
fetch('/api/users/me', {
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin'
|
||||||
}).then(
|
}).then(
|
||||||
(res) => res.json()
|
(res) => res.json()
|
||||||
|
@ -61,7 +61,7 @@ class ColouringMap extends Component<any, any> { // TODO: add proper types
|
|||||||
const newCat = parseCategoryURL(this.props.match.url);
|
const newCat = parseCategoryURL(this.props.match.url);
|
||||||
const mapCat = newCat || 'age';
|
const mapCat = newCat || 'age';
|
||||||
fetch(
|
fetch(
|
||||||
'/buildings/locate?lat='+lat+'&lng='+lng
|
'/api/buildings/locate?lat='+lat+'&lng='+lng
|
||||||
).then(
|
).then(
|
||||||
(res) => res.json()
|
(res) => res.json()
|
||||||
).then(function(data){
|
).then(function(data){
|
||||||
|
@ -30,7 +30,7 @@ class MyAccountPage extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({error: undefined});
|
this.setState({error: undefined});
|
||||||
|
|
||||||
fetch('/logout', {
|
fetch('/api/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin'
|
||||||
}).then(
|
}).then(
|
||||||
@ -50,7 +50,7 @@ class MyAccountPage extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({error: undefined});
|
this.setState({error: undefined});
|
||||||
|
|
||||||
fetch('/api/key', {
|
fetch('/api/api/key', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin'
|
||||||
}).then(
|
}).then(
|
||||||
@ -81,7 +81,7 @@ class MyAccountPage extends Component<any, any> { // TODO: add proper types
|
|||||||
|
|
||||||
</p>
|
</p>
|
||||||
<ErrorBox msg={this.state.error} />
|
<ErrorBox msg={this.state.error} />
|
||||||
<form method="POST" action="/logout" onSubmit={this.handleLogout}>
|
<form onSubmit={this.handleLogout}>
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
<Link to="/edit/age.html" className="btn btn-warning">Start colouring</Link>
|
<Link to="/edit/age.html" className="btn btn-warning">Start colouring</Link>
|
||||||
<input className="btn btn-secondary" type="submit" value="Log out"/>
|
<input className="btn btn-secondary" type="submit" value="Log out"/>
|
||||||
@ -104,7 +104,7 @@ class MyAccountPage extends Component<any, any> { // TODO: add proper types
|
|||||||
<p>Are you a software developer? If so, you might be interested in these.</p>
|
<p>Are you a software developer? If so, you might be interested in these.</p>
|
||||||
<h3 className="h3">API key</h3>
|
<h3 className="h3">API key</h3>
|
||||||
<p>{this.props.user.api_key? this.props.user.api_key : '-'}</p>
|
<p>{this.props.user.api_key? this.props.user.api_key : '-'}</p>
|
||||||
<form method="POST" action="/api/key" onSubmit={this.handleGenerateKey} className="form-group mb-3">
|
<form onSubmit={this.handleGenerateKey} className="form-group mb-3">
|
||||||
<input className="btn btn-warning" type="submit" value="Generate API key"/>
|
<input className="btn btn-warning" type="submit" value="Generate API key"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class SearchBox extends Component<any, any> { // TODO: add proper types
|
|||||||
})
|
})
|
||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
'/search?q='+this.state.q
|
'/api/search?q='+this.state.q
|
||||||
).then(
|
).then(
|
||||||
(res) => res.json()
|
(res) => res.json()
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
@ -160,7 +160,7 @@ class SearchBox extends Component<any, any> { // TODO: add proper types
|
|||||||
: null;
|
: null;
|
||||||
return (
|
return (
|
||||||
<div className={`search-box ${this.props.isBuilding? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
|
<div className={`search-box ${this.props.isBuilding? 'building' : ''}`} onKeyDown={this.handleKeyPress}>
|
||||||
<form action="/search" method="GET" onSubmit={this.search} className="form-inline">
|
<form onSubmit={this.search} className="form-inline">
|
||||||
<div onClick={this.state.smallScreen ? this.expandSearch : null}>
|
<div onClick={this.state.smallScreen ? this.expandSearch : null}>
|
||||||
<SearchIcon/>
|
<SearchIcon/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@ class SignUp extends Component<any, any> { // TODO: add proper types
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({error: undefined})
|
this.setState({error: undefined})
|
||||||
|
|
||||||
fetch('/users', {
|
fetch('/api/users', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(this.state),
|
body: JSON.stringify(this.state),
|
||||||
headers:{
|
headers:{
|
||||||
@ -55,7 +55,7 @@ class SignUp extends Component<any, any> { // TODO: add proper types
|
|||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.setState({error: res.error})
|
this.setState({error: res.error})
|
||||||
} else {
|
} else {
|
||||||
fetch('/users/me', {
|
fetch('/api/users/me', {
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin'
|
||||||
}).then(
|
}).then(
|
||||||
(res) => res.json()
|
(res) => res.json()
|
||||||
|
@ -10,25 +10,19 @@ import express from 'express';
|
|||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
import serialize from 'serialize-javascript';
|
import serialize from 'serialize-javascript';
|
||||||
|
|
||||||
import bodyParser from 'body-parser';
|
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import pgConnect from 'connect-pg-simple';
|
import pgConnect from 'connect-pg-simple';
|
||||||
|
|
||||||
import App from './frontend/app';
|
import App from './frontend/app';
|
||||||
import db from './db';
|
import db from './db';
|
||||||
import { authUser, createUser, getUserById, authAPIUser, getNewUserAPIKey } from './api/user';
|
import { getUserById } from './api/services/user';
|
||||||
import {
|
import {
|
||||||
queryBuildingsAtPoint,
|
|
||||||
queryBuildingsByReference,
|
|
||||||
getBuildingById,
|
getBuildingById,
|
||||||
getBuildingLikeById,
|
getBuildingLikeById,
|
||||||
getBuildingUPRNsById,
|
getBuildingUPRNsById
|
||||||
saveBuilding,
|
} from './api/services/building';
|
||||||
likeBuilding,
|
|
||||||
unlikeBuilding
|
|
||||||
} from './api/building';
|
|
||||||
import { queryLocation } from './api/search';
|
|
||||||
import tileserver from './tiles/tileserver';
|
import tileserver from './tiles/tileserver';
|
||||||
|
import apiServer from './api/api';
|
||||||
import { parseBuildingURL } from './parse';
|
import { parseBuildingURL } from './parse';
|
||||||
|
|
||||||
// create server
|
// create server
|
||||||
@ -43,8 +37,6 @@ server.disable('x-powered-by');
|
|||||||
// serve static files
|
// serve static files
|
||||||
server.use(express.static(process.env.RAZZLE_PUBLIC_DIR));
|
server.use(express.static(process.env.RAZZLE_PUBLIC_DIR));
|
||||||
|
|
||||||
// parse POSTed json body
|
|
||||||
server.use(bodyParser.json());
|
|
||||||
|
|
||||||
// handle user sessions
|
// handle user sessions
|
||||||
const pgSession = pgConnect(session);
|
const pgSession = pgConnect(session);
|
||||||
@ -162,268 +154,11 @@ function renderHTML(context, data, req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET tiles
|
|
||||||
server.use('/tiles', tileserver);
|
server.use('/tiles', tileserver);
|
||||||
|
|
||||||
// GET buildings
|
server.use('/api', apiServer);
|
||||||
// not implemented - may be useful to GET all buildings, paginated
|
|
||||||
|
|
||||||
// GET buildings at point
|
// use the frontend route for anything else - will presumably show the 404 page
|
||||||
server.get('/buildings/locate', function (req, res) {
|
server.use(frontendRoute);
|
||||||
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;
|
export default server;
|
||||||
|
@ -59,7 +59,7 @@ def save_data(building_id, data, api_key, base_url):
|
|||||||
"""Save data to a building
|
"""Save data to a building
|
||||||
"""
|
"""
|
||||||
r = requests.post(
|
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
|
json=data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ def save_data(building_id, data, api_key, base_url):
|
|||||||
"""Save data to a building
|
"""Save data to a building
|
||||||
"""
|
"""
|
||||||
r = requests.post(
|
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
|
json=data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user