Redux-Thunk working

This commit is contained in:
Federico Kereki 2018-07-23 07:59:46 -03:00
parent b10d1a9dd8
commit c1b237eb60
22 changed files with 2913 additions and 2294 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,7 @@
"main": "index.js",
"scripts": {
"build": "flow-remove-types src/ -d out/",
"buildWithMaps":
"flow-remove-types src/ -d out/ --pretty --sourcemaps",
"buildWithMaps": "flow-remove-types src/ -d out/ --pretty --sourcemaps",
"start": "npm run build && node out/doroundmath.js",
"start-db": "npm run build && node out/dbaccess.js",
"nodemon": "nodemon --watch src --delay 1 --exec npm start",
@ -20,7 +19,11 @@
"author": "Federico Kereki",
"license": "ISC",
"babel": {
"presets": ["env", "node", "flow"]
"presets": [
"env",
"node",
"flow"
]
},
"eslintConfig": {
"parserOptions": {
@ -32,8 +35,14 @@
"node": true,
"es6": true
},
"extends": ["eslint:recommended", "plugin:flowtype/recommended"],
"plugins": ["babel", "flowtype"],
"extends": [
"eslint:recommended",
"plugin:flowtype/recommended"
],
"plugins": [
"babel",
"flowtype"
],
"rules": {
"no-console": "off",
"no-var": "error",
@ -41,13 +50,23 @@
"flowtype/no-types-missing-file-annotation": 0
}
},
"eslintIgnore": ["**/out/*.js"],
"eslintIgnore": [
"**/out/*.js"
],
"flow-coverage-report": {
"concurrentFiles": 1,
"excludeGlob": ["node_modules/**"],
"includeGlob": ["src/**/*.js"],
"excludeGlob": [
"node_modules/**"
],
"includeGlob": [
"src/**/*.js"
],
"threshold": 90,
"type": ["text", "html", "json"]
"type": [
"text",
"html",
"json"
]
},
"prettier": {
"tabWidth": 4,

View File

@ -0,0 +1,145 @@
/* @flow */
"use strict";
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const dbConn = require("./restful_db.js");
app.get("/", (req, res) => res.send("Secure server!"));
const cors = require("cors");
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
/*
Add here the logic for providing a JWT at /gettoken
and the logic for validating a JWT, as shown earlier
*/
const {
getCountry,
deleteCountry,
putCountry
} = require("./restful_countries.js");
app.get("/countries", (req, res) => getCountry(res, dbConn));
app.get("/countries/:country", (req, res) =>
getCountry(res, dbConn, req.params.country)
);
app.delete("/countries/:country", (req, res) =>
deleteCountry(res, dbConn, req.params.country)
);
// We don't allow a POST to /countries
app.put("/countries/:country", (req, res) =>
putCountry(res, dbConn, req.params.country, req.body.name)
);
const {
getRegion,
deleteRegion,
postRegion,
putRegion
} = require("./restful_regions.js");
app.get("/regions", (req, res) => getRegion(res, dbConn));
app.get("/regions/:country", (req, res) =>
getRegion(res, dbConn, req.params.country)
);
app.get("/regions/:country/:region", (req, res) =>
getRegion(res, dbConn, req.params.country, req.params.region)
);
app.delete("/regions/:country/:region", (req, res) =>
deleteRegion(res, dbConn, req.params.country, req.params.region)
);
app.post("/regions/:country", (req, res) =>
postRegion(res, dbConn, req.params.country, req.body.name)
);
app.put("/regions/:country/:region", (req, res) =>
putRegion(
res,
dbConn,
req.params.country,
req.params.region,
req.body.name
)
);
const {
getCity,
deleteCity,
postCity,
putCity
} = require("./restful_cities.js");
// We do not allow calling /cities to get every city in the world
app.get("/cities/:city", (req, res) =>
getCity(res, dbConn, req.params.city)
);
app.delete("/cities/:city", (req, res) =>
deleteCity(res, dbConn, req.params.city)
);
app.post("/cities/:city", (req, res) =>
postCity(
res,
dbConn,
req.body.name,
req.body.latitude,
req.body.longitude,
req.body.population,
req.body.country,
req.body.region
)
);
app.put("/cities/:city", (req, res) =>
putCity(
res,
dbConn,
req.params.city,
req.body.name,
req.body.latitude,
req.body.longitude,
req.body.population,
req.body.country,
req.body.region
)
);
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error("Error....", err.message);
res.status(500).send("INTERNAL SERVER ERROR");
});
/*
Add here the logic for HTTPS
const https = require("https");
const fs = require("fs");
const path = require("path");
const keysPath = path.join(__dirname, "../../certificates");
const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
https.createServer({ ca, cert, key }, app);
*/
app.listen(8080, () =>
console.log("Routing ready at http://localhost:8080")
);

View File

@ -698,6 +698,15 @@
"integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==",
"dev": true
},
"axios": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"requires": {
"follow-redirects": "^1.3.0",
"is-buffer": "^1.1.5"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -5737,7 +5746,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz",
"integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==",
"dev": true,
"requires": {
"debug": "^3.1.0"
},
@ -5746,7 +5754,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
@ -7356,8 +7363,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-builtin-module": {
"version": "1.0.0",
@ -9166,8 +9172,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"multicast-dns": {
"version": "6.2.3",
@ -11986,6 +11991,11 @@
}
}
},
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
},
"regenerate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",

View File

@ -20,10 +20,12 @@
"react-scripts": "1.1.4"
},
"dependencies": {
"axios": "^0.18.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-redux": "^5.0.7",
"redux": "^4.0.0"
"redux": "^4.0.0",
"redux-thunk": "^2.3.0"
},
"scripts": {
"start": "react-app-rewired start",

View File

@ -0,0 +1,23 @@
/* @flow */
import React, { Component, Fragment } from "react";
import { Provider } from "react-redux";
import { store } from "./counterApp/store";
import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp";
class App extends Component<{}> {
render() {
return (
<Provider store={store}>
<Fragment>
<ConnectedCounter />
<hr />
<ConnectedClicksDisplay />
</Fragment>
</Provider>
);
}
}
export default App;

View File

@ -0,0 +1,32 @@
/* @flow */
import React, { Component, Fragment } from "react";
import { Provider } from "react-redux";
import {
ConnectedCountrySelect,
ConnectedRegionsTable
} from "./regionsApp";
import { getCountries, getRegions } from "./regionsApp/serviceApi.js";
import { store } from "./regionsApp/store.js";
const dispatcher = fn => (...args) => store.dispatch(fn(...args));
class App extends Component<{}> {
render() {
return (
<Provider store={store}>
<Fragment>
<ConnectedCountrySelect
getCountries={dispatcher(getCountries)}
onSelect={dispatcher(getRegions)}
/>
<ConnectedRegionsTable />
</Fragment>
</Provider>
);
}
}
export default App;

View File

@ -1,7 +1,7 @@
/* @flow */
import React from "react";
import { PropTypes } from "prop-types";
import React from "../../../../../.cache/typescript/2.9/node_modules/@types/react";
import { PropTypes } from "../../../../../.cache/typescript/2.9/node_modules/@types/prop-types";
export class ClicksDisplay extends React.PureComponent<{
clicks: number

View File

@ -1,14 +1,14 @@
/* @flow */
import React from "react";
import { PropTypes } from "prop-types";
import React from "../../../../../.cache/typescript/2.9/node_modules/@types/react";
import { PropTypes } from "../../../../../.cache/typescript/2.9/node_modules/@types/prop-types";
import {
increment,
decrement,
reset,
CounterAction
} from "./counter.actions.js";
} from "./counter.actions";
export class Counter extends React.PureComponent<{
count: number,

View File

@ -0,0 +1,5 @@
.bordered {
border: 1px solid blue;
padding: 2px;
margin: 4px;
}

View File

@ -1,19 +1,8 @@
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { ConnectedCounter, ConnectedClicksDisplay } from "./counterApp";
import { store } from "./counterApp/store";
import App from "./App.regions.js";
import registerServiceWorker from "./registerServiceWorker";
ReactDOM.render(
<Provider store={store}>
<React.Fragment>
<ConnectedCounter />
<hr />
<ConnectedClicksDisplay />
</React.Fragment>
</Provider>,
document.getElementById("root")
);
ReactDOM.render(<App />, document.getElementById("root"));
registerServiceWorker();

View File

@ -0,0 +1,51 @@
/* @flow */
import React from "react";
import PropTypes from "prop-types";
import "../general.css";
export class CountrySelect extends React.PureComponent<{
dispatch: ({}) => any
}> {
static propTypes = {
loading: PropTypes.bool.isRequired,
list: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelect: PropTypes.func.isRequired,
getCountries: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getCountries();
}
onSelect = (e: { target: HTMLOptionElement }) =>
this.props.onSelect(e.target.value);
render() {
if (this.props.loading) {
return <div className="bordered">Loading countries...</div>;
} else {
const sortedCountries = [...this.props.list].sort(
(a, b) => (a.countryName < b.countryName ? -1 : 1)
);
return (
<div className="bordered">
Country:&nbsp;
<select onChange={this.onSelect}>
<option value="">Select a country:</option>
{sortedCountries.map(x => (
<option
key={x.countryCode}
value={x.countryCode}
>
{x.countryName}
</option>
))}
</select>
</div>
);
}
}
}

View File

@ -0,0 +1,12 @@
/* @flow */
import { connect } from "react-redux";
import { CountrySelect } from "./countrySelect.component";
const getProps = state => ({
list: state.countries,
loading: state.loadingCountries
});
export const ConnectedCountrySelect = connect(getProps)(CountrySelect);

View File

@ -0,0 +1,6 @@
/* @flow */
import { ConnectedCountrySelect } from "./countrySelect.connected.js";
import { ConnectedRegionsTable } from "./regionsTable.connected.js";
export { ConnectedCountrySelect, ConnectedRegionsTable };

View File

@ -0,0 +1,41 @@
/* @flow */
import React from "react";
import PropTypes from "prop-types";
import "../general.css";
export class RegionsTable extends React.PureComponent<{
list: Array<{
regionCode: string,
regionName: string
}>
}> {
static propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
};
static defaultProps = {
list: []
};
render() {
if (this.props.list.length === 0) {
return <div className="bordered">No regions.</div>;
} else {
const ordered = [...this.props.list].sort(
(a, b) => (a.regionName < b.regionName ? -1 : 1)
);
return (
<div className="bordered">
{ordered.map(x => (
<div key={x.countryCode + "-" + x.regionCode}>
{x.regionName}
</div>
))}
</div>
);
}
}
}

View File

@ -0,0 +1,12 @@
/* @flow */
import { connect } from "react-redux";
import { RegionsTable } from "./regionsTable.component";
const getProps = state => ({
list: state.regions,
loading: state.loadingRegions
});
export const ConnectedRegionsTable = connect(getProps)(RegionsTable);

View File

@ -0,0 +1,38 @@
/* @flow */
import axios from "axios";
import {
countriesRequest,
countriesSuccess,
countriesFailure,
regionsRequest,
regionsSuccess,
regionsFailure
} from "./world.actions";
export const getCountries = () => async dispatch => {
try {
dispatch(countriesRequest());
const result = await axios.get(`http://fk-server:8080/countries`);
dispatch(countriesSuccess(result.data));
} catch (e) {
dispatch(countriesFailure());
}
};
export const getRegions = (country: string) => async dispatch => {
if (country) {
try {
dispatch(regionsRequest());
const result = await axios.get(
`http://fk-server:8080/regions/${country}`
);
dispatch(regionsSuccess(result.data));
} catch (e) {
dispatch(regionsFailure());
}
} else {
dispatch(regionsFailure());
}
};

View File

@ -0,0 +1,8 @@
/* @flow */
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { reducer } from "./worlds.reducer.js";
export const store = createStore(reducer, applyMiddleware(thunk));

View File

@ -0,0 +1,32 @@
/* @flow */
export const COUNTRIES_REQUEST = "countries:request";
export const COUNTRIES_SUCCESS = "countries:success";
export const COUNTRIES_FAILURE = "countries:failure";
export const REGIONS_REQUEST = "regions:request";
export const REGIONS_SUCCESS = "regions:success";
export const REGIONS_FAILURE = "regions:failure";
export type worldAction = {
type: string,
value?: number
};
export const countriesRequest = () => ({ type: COUNTRIES_REQUEST });
export const countriesSuccess = (listOfCountries: []) => ({
type: COUNTRIES_SUCCESS,
listOfCountries
});
export const countriesFailure = () => ({ type: COUNTRIES_FAILURE });
export const regionsRequest = () => ({ type: REGIONS_REQUEST });
export const regionsSuccess = (listOfRegions: []) => ({
type: REGIONS_SUCCESS,
listOfRegions
});
export const regionsFailure = () => ({ type: REGIONS_FAILURE });

View File

@ -0,0 +1,70 @@
/* @flow */
import {
COUNTRIES_REQUEST,
COUNTRIES_SUCCESS,
COUNTRIES_FAILURE,
REGIONS_REQUEST,
REGIONS_SUCCESS,
REGIONS_FAILURE
} from "./world.actions";
// import type { CounterAction } from "./world.actions.js";
export const reducer = (
state: object = {
// initial state
loadingCountries: false,
countries: [],
loadingRegions: false,
regions: []
},
action: object
) => {
switch (action.type) {
case COUNTRIES_REQUEST:
return {
...state,
loadingCountries: true,
countries: []
};
case COUNTRIES_SUCCESS:
return {
...state,
loadingCountries: false,
countries: action.listOfCountries
};
case COUNTRIES_FAILURE:
return {
...state,
loadingCountries: false,
countries: []
};
case REGIONS_REQUEST:
return {
...state,
loadingRegions: true,
regions: []
};
case REGIONS_SUCCESS:
return {
...state,
loadingRegions: false,
regions: action.listOfRegions
};
case REGIONS_FAILURE:
return {
...state,
loadingRegions: false,
regions: []
};
default:
return state;
}
};

110
geonames/world_db_dump.sql Normal file

File diff suppressed because one or more lines are too long