Add API helpers
This commit is contained in:
parent
82a50d77d6
commit
dbd6487b0b
44
app/src/frontend/apiHelpers.ts
Normal file
44
app/src/frontend/apiHelpers.ts
Normal file
@ -0,0 +1,44 @@
|
||||
type JsonReviver = (name: string, value: any) => any;
|
||||
|
||||
export function apiGet(path: string, options?: {
|
||||
jsonReviver?: JsonReviver
|
||||
}): Promise<any> {
|
||||
return apiRequest(path, 'GET', null, options);
|
||||
}
|
||||
|
||||
export function apiPost(path: string, data?: object, options?: {
|
||||
jsonReviver?: JsonReviver
|
||||
}): Promise<any> {
|
||||
return apiRequest(path, 'POST', data, options);
|
||||
}
|
||||
|
||||
export function apiDelete(path: string, options?: {
|
||||
jsonReviver?: JsonReviver
|
||||
}): Promise<any> {
|
||||
return apiRequest(path, 'DELETE', null, options);
|
||||
}
|
||||
|
||||
async function apiRequest(
|
||||
path: string,
|
||||
method: 'GET' | 'POST' | 'DELETE',
|
||||
data?: object,
|
||||
options?: {
|
||||
jsonReviver?: JsonReviver
|
||||
}
|
||||
): Promise<any> {
|
||||
const res = await fetch(path, {
|
||||
method: method,
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: data == undefined ? null : JSON.stringify(data),
|
||||
});
|
||||
|
||||
const reviver = options?.jsonReviver;
|
||||
if (reviver != undefined) {
|
||||
return JSON.parse(await res.text(), reviver);
|
||||
} else {
|
||||
return await res.json();
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink, Redirect } from 'react-router-dom';
|
||||
|
||||
import { apiPost } from '../apiHelpers';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import InfoBox from '../components/info-box';
|
||||
import { compareObjects } from '../helpers';
|
||||
@ -162,15 +163,10 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
|
||||
*/
|
||||
async handleLike(like: boolean) {
|
||||
try {
|
||||
const res = await fetch(`/api/buildings/${this.props.building.building_id}/like.json`, {
|
||||
method: 'POST',
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({like: like})
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await apiPost(
|
||||
`/api/buildings/${this.props.building.building_id}/like.json`,
|
||||
{like: like}
|
||||
);
|
||||
|
||||
if (data.error) {
|
||||
this.setState({error: data.error});
|
||||
@ -188,15 +184,10 @@ const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>)
|
||||
this.setState({error: undefined});
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/buildings/${this.props.building.building_id}.json`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(this.state.buildingEdits),
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await apiPost(
|
||||
`/api/buildings/${this.props.building.building_id}.json`,
|
||||
this.state.buildingEdits
|
||||
);
|
||||
|
||||
if (data.error) {
|
||||
this.setState({error: data.error});
|
||||
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
|
||||
import './edit-history.css';
|
||||
|
||||
import { apiGet } from '../../apiHelpers';
|
||||
import { Building } from '../../models/building';
|
||||
import { EditHistoryEntry } from '../../models/edit-history-entry';
|
||||
import ContainerHeader from '../container-header';
|
||||
@ -17,10 +18,9 @@ const EditHistory: React.FunctionComponent<EditHistoryProps> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const res = await fetch(`/api/buildings/${props.building.building_id}/history.json`);
|
||||
const data = await res.json();
|
||||
const {history} = await apiGet(`/api/buildings/${props.building.building_id}/history.json`);
|
||||
|
||||
setHistory(data.history);
|
||||
setHistory(history);
|
||||
};
|
||||
|
||||
if (props.building != undefined) { // only call fn if there is a building provided
|
||||
|
@ -5,6 +5,7 @@ import { Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { parseJsonOrDefault } from '../helpers';
|
||||
import { strictParseInt } from '../parse';
|
||||
|
||||
import { apiGet, apiPost } from './apiHelpers';
|
||||
import BuildingView from './building/building-view';
|
||||
import Categories from './building/categories';
|
||||
import { EditHistory } from './building/edit-history/edit-history';
|
||||
@ -64,16 +65,9 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
|
||||
async fetchLatestRevision() {
|
||||
try {
|
||||
const res = await fetch(`/api/buildings/revision`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
const data = await res.json();
|
||||
const {latestRevisionId} = await apiGet(`/api/buildings/revision`);
|
||||
|
||||
this.increaseRevision(data.latestRevisionId);
|
||||
this.increaseRevision(latestRevisionId);
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -89,27 +83,9 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
// TODO: simplify API calls, create helpers for fetching data
|
||||
const buildingId = strictParseInt(this.props.match.params.building);
|
||||
let [building, building_uprns, building_like] = await Promise.all([
|
||||
fetch(`/api/buildings/${buildingId}.json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(res => res.json()),
|
||||
fetch(`/api/buildings/${buildingId}/uprns.json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(res => res.json()),
|
||||
fetch(`/api/buildings/${buildingId}/like.json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(res => res.json())
|
||||
apiGet(`/api/buildings/${buildingId}.json`),
|
||||
apiGet(`/api/buildings/${buildingId}/uprns.json`),
|
||||
apiGet(`/api/buildings/${buildingId}/like.json`)
|
||||
]);
|
||||
|
||||
building.uprns = building_uprns.uprns;
|
||||
@ -158,15 +134,8 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
|
||||
this.increaseRevision(building.revision_id);
|
||||
// get UPRNs and update
|
||||
fetch(`/api/buildings/${building.building_id}/uprns.json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then((res) => {
|
||||
apiGet(`/api/buildings/${building.building_id}/uprns.json`)
|
||||
.then((res) => {
|
||||
if (res.error) {
|
||||
console.error(res);
|
||||
} else {
|
||||
@ -179,15 +148,8 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
});
|
||||
|
||||
// get if liked and update
|
||||
fetch(`/api/buildings/${building.building_id}/like.json`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then((res) => {
|
||||
apiGet(`/api/buildings/${building.building_id}/like.json`)
|
||||
.then((res) => {
|
||||
if (res.error) {
|
||||
console.error(res);
|
||||
} else {
|
||||
@ -225,37 +187,21 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
}
|
||||
|
||||
likeBuilding(buildingId) {
|
||||
fetch(`/api/buildings/${buildingId}/like.json`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ like: true })
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(function (res) {
|
||||
apiPost(`/api/buildings/${buildingId}/like.json`, { like: true })
|
||||
.then(res => {
|
||||
if (res.error) {
|
||||
console.error({ error: res.error });
|
||||
} else {
|
||||
this.increaseRevision(res.revision_id);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
}).catch(
|
||||
(err) => console.error({ error: err })
|
||||
);
|
||||
}
|
||||
|
||||
updateBuilding(buildingId, data) {
|
||||
fetch(`/api/buildings/${buildingId}.json`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(res => {
|
||||
apiPost(`/api/buildings/${buildingId}.json`, data)
|
||||
.then(res => {
|
||||
if (res.error) {
|
||||
console.error({ error: res.error });
|
||||
} else {
|
||||
|
@ -5,6 +5,7 @@ import { AttributionControl, GeoJSON, Map, TileLayer, ZoomControl } from 'react-
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import './map.css';
|
||||
|
||||
import { apiGet } from '../apiHelpers';
|
||||
import { HelpIcon } from '../components/icons';
|
||||
import { Building } from '../models/building';
|
||||
|
||||
@ -58,13 +59,9 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
||||
|
||||
handleClick(e) {
|
||||
const mode = this.props.mode;
|
||||
const lat = e.latlng.lat;
|
||||
const lng = e.latlng.lng;
|
||||
fetch(
|
||||
'/api/buildings/locate?lat='+lat+'&lng='+lng
|
||||
).then(
|
||||
(res) => res.json()
|
||||
).then(function(data){
|
||||
const { lat, lng } = e.latlng;
|
||||
apiGet(`/api/buildings/locate?lat=${lat}&lng=${lng}`)
|
||||
.then(data => {
|
||||
if (data && data.length){
|
||||
const building = data[0];
|
||||
if (mode === 'multi-edit') {
|
||||
@ -82,7 +79,7 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
||||
this.props.selectBuilding(undefined);
|
||||
}
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
}).catch(
|
||||
(err) => console.error(err)
|
||||
);
|
||||
}
|
||||
@ -94,8 +91,8 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
||||
}
|
||||
|
||||
async getBoundary() {
|
||||
const res = await fetch('/geometries/boundary-detailed.geojson');
|
||||
const data = await res.json() as GeoJsonObject;
|
||||
const data = await apiGet('/geometries/boundary-detailed.geojson') as GeoJsonObject;
|
||||
|
||||
this.setState({
|
||||
boundary: data
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
|
||||
import './search-box.css';
|
||||
|
||||
import { apiGet } from '../apiHelpers';
|
||||
import { SearchIcon } from '../components/icons';
|
||||
|
||||
interface SearchResult {
|
||||
@ -96,11 +97,8 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
|
||||
fetching: true
|
||||
});
|
||||
|
||||
fetch(
|
||||
'/api/search?q='+this.state.q
|
||||
).then(
|
||||
(res) => res.json()
|
||||
).then((data) => {
|
||||
apiGet(`/api/search?q=${this.state.q}`)
|
||||
.then((data) => {
|
||||
if (data && data.results){
|
||||
this.setState({
|
||||
results: data.results,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { apiGet } from '../apiHelpers';
|
||||
import { BuildingEditSummary } from '../building/edit-history/building-edit-summary';
|
||||
import InfoBox from '../components/info-box';
|
||||
import { EditHistoryEntry } from '../models/edit-history-entry';
|
||||
@ -9,10 +10,9 @@ const ChangesPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const res = await fetch(`/api/history`);
|
||||
const data = await res.json();
|
||||
const {history} = await apiGet(`/api/history`);
|
||||
|
||||
setHistory(data.history);
|
||||
setHistory(history);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
@ -2,6 +2,7 @@ import React, { FunctionComponent } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { dateReviver } from '../../helpers';
|
||||
import { apiGet } from '../apiHelpers';
|
||||
|
||||
|
||||
interface ExtractViewModel {
|
||||
@ -28,11 +29,9 @@ export default class DataExtracts extends React.Component<{}, DataExtractsState>
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const res = await fetch('/api/extracts');
|
||||
const data = JSON.parse(await res.text(), dateReviver);
|
||||
let data = await apiGet('/api/extracts', { jsonReviver: dateReviver});
|
||||
const extracts = (data.extracts as ExtractViewModel[])
|
||||
.sort((a, b) => a.extracted_on.valueOf() - b.extracted_on.valueOf())
|
||||
.reverse();
|
||||
.sort((a, b) => b.extracted_on.valueOf() - a.extracted_on.valueOf());
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
|
||||
import { apiGet, apiPost } from '../apiHelpers';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import InfoBox from '../components/info-box';
|
||||
import SupporterLogos from '../components/supporter-logos';
|
||||
@ -39,24 +40,13 @@ class Login extends Component<LoginProps, any> {
|
||||
event.preventDefault();
|
||||
this.setState({error: undefined});
|
||||
|
||||
fetch('/api/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(this.state),
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(function(res){
|
||||
apiPost('/api/login', this.state)
|
||||
.then(res => {
|
||||
if (res.error) {
|
||||
this.setState({error: res.error});
|
||||
} else {
|
||||
fetch('/api/users/me', {
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
(res) => res.json()
|
||||
).then(user => {
|
||||
apiGet('/api/users/me')
|
||||
.then(user => {
|
||||
if (user.error) {
|
||||
this.setState({error: user.error});
|
||||
} else {
|
||||
@ -66,7 +56,7 @@ class Login extends Component<LoginProps, any> {
|
||||
(err) => this.setState({error: err})
|
||||
);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
}).catch(
|
||||
(err) => this.setState({error: err})
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component, FormEvent } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
|
||||
import { apiDelete, apiPost } from '../apiHelpers';
|
||||
import ConfirmationModal from '../components/confirmation-modal';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import { User } from '../models/user';
|
||||
@ -32,12 +33,8 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
|
||||
event.preventDefault();
|
||||
this.setState({error: undefined});
|
||||
|
||||
fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(function(res){
|
||||
apiPost('/api/logout')
|
||||
.then(function(res){
|
||||
if (res.error) {
|
||||
this.setState({error: res.error});
|
||||
} else {
|
||||
@ -52,18 +49,14 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
|
||||
event.preventDefault();
|
||||
this.setState({error: undefined});
|
||||
|
||||
fetch('/api/api/key', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(function(res){
|
||||
apiPost('/api/api/key')
|
||||
.then(res => {
|
||||
if (res.error) {
|
||||
this.setState({error: res.error});
|
||||
} else {
|
||||
this.props.updateUser(res);
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
}).catch(
|
||||
(err) => this.setState({error: err})
|
||||
);
|
||||
}
|
||||
@ -81,11 +74,7 @@ class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
|
||||
this.setState({ error: undefined });
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/users/me', {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await apiDelete('/api/users/me');
|
||||
|
||||
if(data.error) {
|
||||
this.setState({ error: data.error });
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
|
||||
import { apiGet, apiPost } from '../apiHelpers';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import InfoBox from '../components/info-box';
|
||||
import SupporterLogos from '../components/supporter-logos';
|
||||
@ -48,36 +49,21 @@ class SignUp extends Component<SignUpProps, SignUpState> {
|
||||
} as Pick<SignUpState, keyof SignUpState>);
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
async handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.setState({error: undefined});
|
||||
|
||||
fetch('/api/users', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(this.state),
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
res => res.json()
|
||||
).then(function(res){
|
||||
if (res.error) {
|
||||
this.setState({error: res.error});
|
||||
try {
|
||||
const res = await apiPost('/api/users', this.state);
|
||||
if(res.error) {
|
||||
this.setState({ error: res.error });
|
||||
} else {
|
||||
fetch('/api/users/me', {
|
||||
credentials: 'same-origin'
|
||||
}).then(
|
||||
(res) => res.json()
|
||||
).then(
|
||||
(user) => this.props.login(user)
|
||||
).catch(
|
||||
(err) => this.setState({error: err})
|
||||
);
|
||||
const user = await apiGet('/api/users/me');
|
||||
this.props.login(user);
|
||||
}
|
||||
} catch(err) {
|
||||
this.setState({error: err});
|
||||
}
|
||||
}.bind(this)).catch(
|
||||
(err) => this.setState({error: err})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
Loading…
Reference in New Issue
Block a user