Initial commit.
commit
927c10947d
@ -0,0 +1,29 @@
|
|||||||
|
src/server/node_modules/
|
||||||
|
src/server/public/
|
||||||
|
public/
|
||||||
|
src/server/routes/data/
|
||||||
|
*~
|
||||||
|
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "tider",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/core": "^0.5.2",
|
||||||
|
"@emotion/core": "^10.0.27",
|
||||||
|
"@emotion/styled": "^10.0.27",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.4.0",
|
||||||
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
"emotion-theming": "^10.0.27",
|
||||||
|
"express-generator": "^4.16.1",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"nedb": "^1.8.0",
|
||||||
|
"nedb-promise": "^2.0.1",
|
||||||
|
"react": "^16.12.0",
|
||||||
|
"react-day-picker": "^7.4.0",
|
||||||
|
"react-dom": "^16.12.0",
|
||||||
|
"react-router-dom": "^5.1.2",
|
||||||
|
"react-scripts": "3.4.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"proxy": "http://localhost:3001",
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
body {
|
||||||
|
background: #E6FFFA; }
|
||||||
|
|
||||||
|
.DayPicker-Day--goodtides {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DayPicker-Day--oktides {
|
||||||
|
color: #803000;
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ThemeProvider, CSSReset, Box, Flex } from '@chakra-ui/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Switch,
|
||||||
|
Route,
|
||||||
|
Link
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
// import logo from './logo.svg';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
import LocationControl from './components/LocationControl.jsx';
|
||||||
|
import Sources from './components/Sources.jsx';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<CSSReset />
|
||||||
|
<Router>
|
||||||
|
<Flex justify='center'>
|
||||||
|
<Box maxW={['md', 'lg']} w={'100%'}>
|
||||||
|
<Box>
|
||||||
|
<Box bg='blue.400' p={2} color='white'>
|
||||||
|
<Flex justify='center'>
|
||||||
|
<nav><Link to='/'>Tideable</Link></nav>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
<Switch>
|
||||||
|
<Route path='/location'>
|
||||||
|
<LocationControl />
|
||||||
|
</Route>
|
||||||
|
<Route path='/sources'>
|
||||||
|
<Sources />
|
||||||
|
</Route>
|
||||||
|
<Route path='/'>
|
||||||
|
<Link to='/location'>Locations</Link>
|
||||||
|
<br /><br />
|
||||||
|
<Link to='/sources'>Sources</Link>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Router>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
const { getByText } = render(<App />);
|
||||||
|
const linkElement = getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
@ -0,0 +1,266 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Heading, Box, Flex, Icon, Link as CLink, Button } from '@chakra-ui/core';
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import DayPicker from "react-day-picker";
|
||||||
|
import "react-day-picker/lib/style.css";
|
||||||
|
|
||||||
|
import { sources } from '../sources.json';
|
||||||
|
|
||||||
|
function addDays(date, days) {
|
||||||
|
var result = new Date(date);
|
||||||
|
result.setDate(result.getDate() + days);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Location extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { tides: [{ date: '', time: '', distance: 0 }],
|
||||||
|
waves: false,
|
||||||
|
weather: false,
|
||||||
|
locationIndex: 0,
|
||||||
|
loaded: false,
|
||||||
|
date: new Date(new Date().toLocaleDateString()),
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
nextTideable: '',
|
||||||
|
urls: { marine: '', weather: '' },
|
||||||
|
redirectToDay: false,
|
||||||
|
showDayPicker: false,
|
||||||
|
goodTides: [],
|
||||||
|
okTides: [],
|
||||||
|
tidesDate: new Date(2000, 1, 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
update = () => {
|
||||||
|
fetch(`/api/location/${this.state.id}/date/` + this.state.date.toISOString())
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(obj => {
|
||||||
|
const waves = obj.waves.length > 0 ? obj.waves
|
||||||
|
.sort((a, b) => new Date(a.date) - new Date(b.date))[0] : false;
|
||||||
|
const highestSwell = waves ? waves.swellDetails
|
||||||
|
.reduce((r, x) => x.height > r.height ? x : r, { height: -1 }) :
|
||||||
|
false;
|
||||||
|
const windHigher = waves ? waves.windHeight > highestSwell.height : false;
|
||||||
|
const w = obj.weather;
|
||||||
|
|
||||||
|
const nextTideable = new Date(obj.nextTideable.date);
|
||||||
|
|
||||||
|
this.setState({ tides: obj.tides,
|
||||||
|
waves: waves ?
|
||||||
|
{ height: windHigher ? waves.windHeight : highestSwell.height,
|
||||||
|
direction: windHigher ? '' : highestSwell.direction,
|
||||||
|
swell: obj.waves.map(w => w.swellText),
|
||||||
|
wind: obj.waves.map(w => w.windText)
|
||||||
|
} : false,
|
||||||
|
weather: w ?
|
||||||
|
{ wind: { speed: w.windSpeed,
|
||||||
|
direction: w.windDirection },
|
||||||
|
conditions: w.shortForecast,
|
||||||
|
high: w.temperature
|
||||||
|
} : false,
|
||||||
|
name: obj.name,
|
||||||
|
loaded: true,
|
||||||
|
nextTideable: nextTideable.getFullYear() + '/' +
|
||||||
|
(nextTideable.getMonth() + 1) + '/' +
|
||||||
|
nextTideable.getDate(),
|
||||||
|
urls: obj.urls,
|
||||||
|
redirectToDay: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTideability = () => {
|
||||||
|
fetch(`/api/location/${this.state.id}/tide/` + this.state.date.toISOString())
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(obj => this.setState({
|
||||||
|
goodTides: obj.goodTides.map(x => new Date(x))
|
||||||
|
.filter(x => x.getHours() < 22 && x.getHours() > 5),
|
||||||
|
okTides: obj.okTides.map(x => new Date(x))
|
||||||
|
.concat(obj.goodTides.map(x => new Date(x))
|
||||||
|
.filter(x => x.getHours() >= 22 && x.getHours() <= 5)),
|
||||||
|
tidesDate: this.state.date
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { match: { params } } = this.props;
|
||||||
|
this.setState({ id: params.id,
|
||||||
|
date: params.y ? new Date(parseInt(params.y), parseInt(params.m) - 1, parseInt(params.d)) :
|
||||||
|
this.state.date },
|
||||||
|
() => {
|
||||||
|
this.update();
|
||||||
|
this.fetchTideability();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(oldProps) {
|
||||||
|
const { match: { params } } = this.props;
|
||||||
|
const oldParams = oldProps.match.params;
|
||||||
|
const date = '' + params.y + params.m + params.d;
|
||||||
|
const oldDate = '' + oldParams.y + oldParams.m + oldParams.d;
|
||||||
|
if (params.id !== oldProps.match.params.id ||
|
||||||
|
date !== oldDate) {
|
||||||
|
this.setState({ id: params.id, loaded: false,
|
||||||
|
date: params.y ? new Date(parseInt(params.y), parseInt(params.m) - 1, parseInt(params.d)) : this.state.date
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.update();
|
||||||
|
// if its been a month since we fetched good/okTides
|
||||||
|
if (params.id !== oldProps.match.params.id ||
|
||||||
|
Math.abs(this.state.date - this.state.tidesDate) >= 2588400000) {
|
||||||
|
this.fetchTideability();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dayClick = day => {
|
||||||
|
this.setState({ redirectToDay: day });
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDayPicker = () => {
|
||||||
|
this.setState({ showDayPicker: !this.state.showDayPicker });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.loaded && this.state.redirectToDay) {
|
||||||
|
return (<Redirect to={`/location/${this.state.id}/` +
|
||||||
|
this.state.redirectToDay.getFullYear() + '/' +
|
||||||
|
(this.state.redirectToDay.getMonth() + 1) + '/' +
|
||||||
|
this.state.redirectToDay.getDate()} />);
|
||||||
|
} else if (this.state.loaded) {
|
||||||
|
const sourceIndex = sources.map(source => source.id).indexOf(this.state.id);
|
||||||
|
const prevLocationObj = sources[sourceIndex === 0 ?
|
||||||
|
sources.length - 1 : sourceIndex - 1];
|
||||||
|
const nextLocationObj = sources[sourceIndex === sources.length - 1 ?
|
||||||
|
0 : sourceIndex + 1];
|
||||||
|
const prevLocation =
|
||||||
|
(
|
||||||
|
<Link to={`/location/${prevLocationObj.id}/` +
|
||||||
|
this.state.date.getFullYear() + '/' +
|
||||||
|
(this.state.date.getMonth() + 1) + '/' +
|
||||||
|
this.state.date.getDate()}>
|
||||||
|
<Icon name='arrow-left' />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
const nextLocation =
|
||||||
|
(
|
||||||
|
<Link to={`/location/${nextLocationObj.id}/` +
|
||||||
|
this.state.date.getFullYear() + '/' +
|
||||||
|
(this.state.date.getMonth() + 1) + '/' +
|
||||||
|
this.state.date.getDate()}>
|
||||||
|
<Icon name='arrow-right' />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
const nextDayDate = addDays(this.state.date, 1);
|
||||||
|
const nextDay = '/location/' + this.state.id + '/' +
|
||||||
|
nextDayDate.getFullYear() + '/' +
|
||||||
|
(nextDayDate.getMonth() + 1) + '/' +
|
||||||
|
nextDayDate.getDate();
|
||||||
|
const prevDayDate = addDays(this.state.date, -1);
|
||||||
|
// const today = new Date(new Date().toLocaleDateString());
|
||||||
|
const prevDay =
|
||||||
|
'/location/' + this.state.id + '/' +
|
||||||
|
prevDayDate.getFullYear() + '/' +
|
||||||
|
(prevDayDate.getMonth() + 1) + '/' +
|
||||||
|
prevDayDate.getDate();
|
||||||
|
const { weather } = this.state;
|
||||||
|
const tideMin = Math.min.apply(Math, this.state.tides.map(x => x.distance)),
|
||||||
|
tideColor = tideMin < -0.3 ? 'green' : tideMin < 0 ? 'orange' : 'black';
|
||||||
|
const waveColor = this.state.waves.height < 5 ? 'green' :
|
||||||
|
this.state.waves.height < 10 ? 'orange' : 'red';
|
||||||
|
const conditionsScore = RegExp('rain', 'g').test(weather.conditions) ? 5 : 0;
|
||||||
|
const tempScore = weather.high >= 60 ? 0 : weather.high >= 50 ? 5 : 10;
|
||||||
|
const windSpeed = weather.wind ? weather.wind.speed : "0";
|
||||||
|
const maxWind = Math.max.apply(Math, windSpeed.match(/[0-9]+/g).map(x => parseInt(x)));
|
||||||
|
const windScore = maxWind <= 10 ? 0 : maxWind <= 20 ? 5 : 10;
|
||||||
|
const weatherScore = Math.max(conditionsScore, tempScore, windScore);
|
||||||
|
const weatherColor = weatherScore < 5 ? 'green' : weatherScore === 5 ? 'orange' : 'red';
|
||||||
|
return (
|
||||||
|
<Box bg='blue.100' p={2}>
|
||||||
|
<Heading size='2xl' as='h1' marginBottom='0.5em'>
|
||||||
|
<Flex justify='space-between' align='center'>
|
||||||
|
<Box color='gray.400'>{prevLocation}</Box>
|
||||||
|
<Box color='red.800'>{this.state.name}</Box>
|
||||||
|
<Box color='gray.400'>{nextLocation}</Box>
|
||||||
|
</Flex>
|
||||||
|
</Heading>
|
||||||
|
<Heading size='m'>
|
||||||
|
<Flex justify='space-between' align='center' marginBottom='0.5em'>
|
||||||
|
<Box color='gray.400'>
|
||||||
|
<Link to={prevDay}><Icon name='arrow-left' /></Link>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={this.toggleDayPicker}
|
||||||
|
variant='outline'>
|
||||||
|
{this.state.date.toLocaleDateString()}
|
||||||
|
</Button>
|
||||||
|
<Box color='gray.400'>
|
||||||
|
<Link to={nextDay}><Icon name='arrow-right' /></Link>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
{this.state.showDayPicker ? (
|
||||||
|
<Flex justify='center'>
|
||||||
|
<DayPicker month={this.state.date}
|
||||||
|
selectedDays={[this.state.date]}
|
||||||
|
onDayClick={this.dayClick}
|
||||||
|
modifiers={{ goodtides: this.state.goodTides,
|
||||||
|
oktides: this.state.okTides }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
) : (<></>)}
|
||||||
|
<Flex w='100%' justify='center' marginBottom='0.5em'>
|
||||||
|
<Button variant='outline'>
|
||||||
|
<Link to={`/location/${this.state.id}/${this.state.nextTideable}`}>Next Tideable <Icon name='arrow-right' /></Link>
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Heading>
|
||||||
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
||||||
|
<Heading as='h3' size='lg' color={tideColor + '.500'}>
|
||||||
|
<CLink href={this.state.urls.tide} isExternal>
|
||||||
|
Tide <Icon name='external-link' />
|
||||||
|
</CLink>
|
||||||
|
</Heading>
|
||||||
|
{this.state.tides.map((tide, i) => (
|
||||||
|
<Box key={i}>
|
||||||
|
Level: {tide.distance} ft
|
||||||
|
Time: {new Date(tide.date).toLocaleTimeString()}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
{this.state.waves ? (
|
||||||
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
||||||
|
<Heading as='h3' size='lg' color={waveColor + '.500'}>
|
||||||
|
<CLink href={this.state.urls.marine} isExternal>
|
||||||
|
Waves <Icon name='external-link' />
|
||||||
|
</CLink>
|
||||||
|
</Heading>
|
||||||
|
<Flex direction='column'>
|
||||||
|
<Box>
|
||||||
|
Max waves: {this.state.waves.height}ft {this.state.waves.direction}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (<></>)}
|
||||||
|
{this.state.weather ? (
|
||||||
|
<Box shadow='md' p={5} borderWidth='1px' bg='white' mb={4}>
|
||||||
|
<Heading as='h3' size='lg' color={weatherColor + '.500'}>
|
||||||
|
<CLink href={this.state.urls.weather} isExternal>
|
||||||
|
Weather <Icon name='external-link' />
|
||||||
|
</CLink>
|
||||||
|
</Heading>
|
||||||
|
<Flex direction='column'>
|
||||||
|
<Box>Conditions: {weather.conditions}</Box>
|
||||||
|
<Box>High: {weather.high}</Box>
|
||||||
|
<Box>Wind: {weather.wind.speed} {weather.wind.direction}</Box>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
) : (<></>)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ('Loading...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
Route,
|
||||||
|
Link
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
import Location from './Location.jsx';
|
||||||
|
|
||||||
|
import { sources } from '../sources.json';
|
||||||
|
const sourcesSorted = sources
|
||||||
|
.sort((a, b) => parseInt(b.marine.lat) - parseInt(a.marine.lat))
|
||||||
|
|
||||||
|
export default class LocationControl extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route path='/location/:id/:y/:m/:d' component={Location} />
|
||||||
|
<Route path='/location/:id' component={Location} />
|
||||||
|
<Route path='/location'>
|
||||||
|
<h3>Locations</h3>
|
||||||
|
<ul>
|
||||||
|
{sourcesSorted.map((source, i) => (<li key={i}><Link to={'/location/' + source.id}>{source.name}</Link></li>))}
|
||||||
|
</ul>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Heading, Button } from '@chakra-ui/core';
|
||||||
|
|
||||||
|
export default class Sources extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { loading: false,
|
||||||
|
fetched: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh = () => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
fetch('/api/refresh-sources', { method: 'POST' })
|
||||||
|
.then(x => this.setState({ loading: false, fetched: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTide = () => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
fetch('/api/clear-tide', { method: 'POST' })
|
||||||
|
.then(x => this.setState({ loading: false }));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading>Sources</Heading>
|
||||||
|
<Button onClick={this.refresh}>Refresh Sources</Button>
|
||||||
|
<br /><br />
|
||||||
|
<Button onClick={this.clearTide}>Clear Tide Data</Button>
|
||||||
|
<br /><br />
|
||||||
|
{this.state.loading ? 'Refreshing...' : <></>}
|
||||||
|
{this.state.fetched ? 'Finished refreshing' : <></>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
|
||||||
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
|
serviceWorker.unregister();
|
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||||
|
<g fill="#61DAFB">
|
||||||
|
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||||
|
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||||
|
<path d="M520.5 78.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
.PHONY: run
|
||||||
|
|
||||||
|
run:
|
||||||
|
PORT=3001 DEBUG=server:* npm start
|
@ -0,0 +1,47 @@
|
|||||||
|
var createError = require('http-errors');
|
||||||
|
var express = require('express');
|
||||||
|
var path = require('path');
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
|
var logger = require('morgan');
|
||||||
|
|
||||||
|
var indexRouter = require('./routes/index');
|
||||||
|
var usersRouter = require('./routes/users');
|
||||||
|
var apiRouter = require('./routes/api');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
app.set('view engine', 'jade');
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
// app.use('/', indexRouter);
|
||||||
|
// app.use('/location/:location', indexRouter);
|
||||||
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
|
app.get('*', (req,res) =>{
|
||||||
|
res.sendFile(path.join(__dirname + '/public/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')('server:server');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === 'string'
|
||||||
|
? 'pipe ' + addr
|
||||||
|
: 'port ' + addr.port;
|
||||||
|
debug('Listening on ' + bind);
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{"hello":"world","n":5,"date":{"$$date":1583175600000},"nedbIsAwesome":true,"notthere":null,"fruits":["apple","orange","pear"],"infos":{"name":"nedb"},"_id":"yL73UBWPNujFr6pd"}
|
||||||
|
{"hello":"world","n":5,"date":{"$$date":1583175780000},"nedbIsAwesome":true,"notthere":null,"fruits":["apple","orange","pear"],"infos":{"name":"nedb"},"_id":"JFAuU8ZLOYj7eLxM"}
|
||||||
|
{"$$deleted":true,"_id":"JFAuU8ZLOYj7eLxM"}
|
@ -0,0 +1,757 @@
|
|||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||||
|
"requires": {
|
||||||
|
"mime-types": "~2.1.24",
|
||||||
|
"negotiator": "0.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acorn": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
|
||||||
|
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc="
|
||||||
|
},
|
||||||
|
"acorn-globals": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
|
||||||
|
"integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=",
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"align-text": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
|
||||||
|
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
|
||||||
|
"requires": {
|
||||||
|
"kind-of": "^3.0.2",
|
||||||
|
"longest": "^1.0.1",
|
||||||
|
"repeat-string": "^1.5.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"amdefine": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
|
||||||
|
},
|
||||||
|
"array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
|
},
|
||||||
|
"asap": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0="
|
||||||
|
},
|
||||||
|
"basic-auth": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"body-parser": {
|
||||||
|
"version": "1.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||||
|
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"bytes": "3.0.0",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"iconv-lite": "0.4.23",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"qs": "6.5.2",
|
||||||
|
"raw-body": "2.3.3",
|
||||||
|
"type-is": "~1.6.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bytes": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||||
|
},
|
||||||
|
"camelcase": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
|
||||||
|
},
|
||||||
|
"center-align": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
|
||||||
|
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
|
||||||
|
"requires": {
|
||||||
|
"align-text": "^0.1.3",
|
||||||
|
"lazy-cache": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"character-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY="
|
||||||
|
},
|
||||||
|
"clean-css": {
|
||||||
|
"version": "3.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz",
|
||||||
|
"integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=",
|
||||||
|
"requires": {
|
||||||
|
"commander": "2.8.x",
|
||||||
|
"source-map": "0.4.x"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
|
||||||
|
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
|
||||||
|
"requires": {
|
||||||
|
"graceful-readlink": ">= 1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cliui": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
|
||||||
|
"requires": {
|
||||||
|
"center-align": "^0.1.1",
|
||||||
|
"right-align": "^0.1.1",
|
||||||
|
"wordwrap": "0.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
|
||||||
|
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commander": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz",
|
||||||
|
"integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0="
|
||||||
|
},
|
||||||
|
"constantinople": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz",
|
||||||
|
"integrity": "sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE=",
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content-disposition": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||||
|
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||||
|
},
|
||||||
|
"content-type": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||||
|
},
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||||
|
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||||
|
},
|
||||||
|
"cookie-parser": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.3.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||||
|
},
|
||||||
|
"css": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz",
|
||||||
|
"integrity": "sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c=",
|
||||||
|
"requires": {
|
||||||
|
"css-parse": "1.0.4",
|
||||||
|
"css-stringify": "1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"css-parse": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90="
|
||||||
|
},
|
||||||
|
"css-stringify": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE="
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"decamelize": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||||
|
},
|
||||||
|
"depd": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||||
|
},
|
||||||
|
"destroy": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||||
|
},
|
||||||
|
"ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||||
|
},
|
||||||
|
"encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||||
|
},
|
||||||
|
"escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||||
|
},
|
||||||
|
"etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||||
|
},
|
||||||
|
"express": {
|
||||||
|
"version": "4.16.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||||
|
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||||
|
"requires": {
|
||||||
|
"accepts": "~1.3.5",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "1.18.3",
|
||||||
|
"content-disposition": "0.5.2",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.3.1",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "1.1.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"merge-descriptors": "1.0.1",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"parseurl": "~1.3.2",
|
||||||
|
"path-to-regexp": "0.1.7",
|
||||||
|
"proxy-addr": "~2.0.4",
|
||||||
|
"qs": "6.5.2",
|
||||||
|
"range-parser": "~1.2.0",
|
||||||
|
"safe-buffer": "5.1.2",
|
||||||
|
"send": "0.16.2",
|
||||||
|
"serve-static": "1.13.2",
|
||||||
|
"setprototypeof": "1.1.0",
|
||||||
|
"statuses": "~1.4.0",
|
||||||
|
"type-is": "~1.6.16",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"finalhandler": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"parseurl": "~1.3.2",
|
||||||
|
"statuses": "~1.4.0",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forwarded": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||||
|
},
|
||||||
|
"fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||||
|
},
|
||||||
|
"graceful-readlink": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
|
||||||
|
},
|
||||||
|
"http-errors": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||||
|
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||||
|
"requires": {
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"inherits": "2.0.3",
|
||||||
|
"setprototypeof": "1.1.0",
|
||||||
|
"statuses": ">= 1.4.0 < 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iconv-lite": {
|
||||||
|
"version": "0.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||||
|
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||||
|
"requires": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
|
},
|
||||||
|
"ipaddr.js": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||||
|
},
|
||||||
|
"is-buffer": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||||
|
},
|
||||||
|
"is-promise": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
|
||||||
|
},
|
||||||
|
"jade": {
|
||||||
|
"version": "1.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz",
|
||||||
|
"integrity": "sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0=",
|
||||||
|
"requires": {
|
||||||
|
"character-parser": "1.2.1",
|
||||||
|
"clean-css": "^3.1.9",
|
||||||
|
"commander": "~2.6.0",
|
||||||
|
"constantinople": "~3.0.1",
|
||||||
|
"jstransformer": "0.0.2",
|
||||||
|
"mkdirp": "~0.5.0",
|
||||||
|
"transformers": "2.1.0",
|
||||||
|
"uglify-js": "^2.4.19",
|
||||||
|
"void-elements": "~2.0.1",
|
||||||
|
"with": "~4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jstransformer": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz",
|
||||||
|
"integrity": "sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs=",
|
||||||
|
"requires": {
|
||||||
|
"is-promise": "^2.0.0",
|
||||||
|
"promise": "^6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kind-of": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||||
|
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||||
|
"requires": {
|
||||||
|
"is-buffer": "^1.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lazy-cache": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
|
||||||
|
},
|
||||||
|
"longest": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
|
||||||
|
},
|
||||||
|
"media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||||
|
},
|
||||||
|
"merge-descriptors": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||||
|
},
|
||||||
|
"methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||||
|
},
|
||||||
|
"mime": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||||
|
},
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.43.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
|
||||||
|
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
|
||||||
|
},
|
||||||
|
"mime-types": {
|
||||||
|
"version": "2.1.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
|
||||||
|
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "1.43.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||||
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "0.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"morgan": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
|
||||||
|
"requires": {
|
||||||
|
"basic-auth": "~2.0.0",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"on-headers": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
},
|
||||||
|
"negotiator": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||||
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||||
|
},
|
||||||
|
"on-finished": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||||
|
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||||
|
"requires": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"on-headers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
|
||||||
|
},
|
||||||
|
"optimist": {
|
||||||
|
"version": "0.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
|
||||||
|
"integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=",
|
||||||
|
"requires": {
|
||||||
|
"wordwrap": "~0.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||||
|
},
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||||
|
},
|
||||||
|
"promise": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz",
|
||||||
|
"integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=",
|
||||||
|
"requires": {
|
||||||
|
"asap": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"proxy-addr": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
|
||||||
|
"requires": {
|
||||||
|
"forwarded": "~0.1.2",
|
||||||
|
"ipaddr.js": "1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qs": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||||
|
},
|
||||||
|
"range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||||
|
},
|
||||||
|
"raw-body": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||||
|
"requires": {
|
||||||
|
"bytes": "3.0.0",
|
||||||
|
"http-errors": "1.6.3",
|
||||||
|
"iconv-lite": "0.4.23",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeat-string": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||||
|
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
||||||
|
},
|
||||||
|
"right-align": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
|
||||||
|
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
|
||||||
|
"requires": {
|
||||||
|
"align-text": "^0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"sax": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||||
|
},
|
||||||
|
"send": {
|
||||||
|
"version": "0.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||||
|
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"destroy": "~1.0.4",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "~1.6.2",
|
||||||
|
"mime": "1.4.1",
|
||||||
|
"ms": "2.0.0",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"range-parser": "~1.2.0",
|
||||||
|
"statuses": "~1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve-static": {
|
||||||
|
"version": "1.13.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||||
|
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||||
|
"requires": {
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.2",
|
||||||
|
"send": "0.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"setprototypeof": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||||
|
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
||||||
|
"requires": {
|
||||||
|
"amdefine": ">=0.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||||
|
},
|
||||||
|
"transformers": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac=",
|
||||||
|
"requires": {
|
||||||
|
"css": "~1.0.8",
|
||||||
|
"promise": "~2.0",
|
||||||
|
"uglify-js": "~2.2.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"is-promise": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU="
|
||||||
|
},
|
||||||
|
"promise": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4=",
|
||||||
|
"requires": {
|
||||||
|
"is-promise": "~1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.1.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
|
||||||
|
"integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
|
||||||
|
"requires": {
|
||||||
|
"amdefine": ">=0.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uglify-js": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz",
|
||||||
|
"integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=",
|
||||||
|
"requires": {
|
||||||
|
"optimist": "~0.3.5",
|
||||||
|
"source-map": "~0.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"requires": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uglify-js": {
|
||||||
|
"version": "2.8.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
|
||||||
|
"integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
|
||||||
|
"requires": {
|
||||||
|
"source-map": "~0.5.1",
|
||||||
|
"uglify-to-browserify": "~1.0.0",
|
||||||
|
"yargs": "~3.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uglify-to-browserify": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||||
|
},
|
||||||
|
"utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||||
|
},
|
||||||
|
"vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||||
|
},
|
||||||
|
"void-elements": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
|
||||||
|
},
|
||||||
|
"window-size": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
|
||||||
|
},
|
||||||
|
"with": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz",
|
||||||
|
"integrity": "sha1-7v0VTp550sjTQXtkeo8U2f7M4U4=",
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^1.0.1",
|
||||||
|
"acorn-globals": "^1.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
|
||||||
|
"integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
|
||||||
|
},
|
||||||
|
"xml2js": {
|
||||||
|
"version": "0.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
|
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||||
|
"requires": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "3.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
|
||||||
|
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
|
||||||
|
"requires": {
|
||||||
|
"camelcase": "^1.0.2",
|
||||||
|
"cliui": "^2.1.0",
|
||||||
|
"decamelize": "^1.0.0",
|
||||||
|
"window-size": "0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./bin/www"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"express": "~4.16.1",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"jade": "~1.11.0",
|
||||||
|
"morgan": "~1.9.1",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"xml2js": "^0.4.23"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
const Datastore = require('nedb-promise'),
|
||||||
|
db = new Datastore({ filename: '/home/tjhintz/tideable-db', autoload: true });
|
||||||
|
|
||||||
|
const { processSources } = require('./sourceData.js');
|
||||||
|
const sources = JSON.parse(fs.readFileSync('../sources.json')).sources
|
||||||
|
.sort((a, b) => parseInt(b.marine.lat) - parseInt(a.marine.lat));
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
sources.forEach(source => {
|
||||||
|
const id = source.id;
|
||||||
|
data[id] = {
|
||||||
|
// tides: JSON.parse(fs.readFileSync(`./routes/data/${id}-tide.json`)).data,
|
||||||
|
// waves: JSON.parse(fs.readFileSync(`./routes/data/${id}-marine-forecast.json`)).data,
|
||||||
|
// weather: JSON.parse(fs.readFileSync(`./routes/data/${id}-weather.json`)).data,
|
||||||
|
name: source.name,
|
||||||
|
urls: source.urls
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function addDays(date, days) {
|
||||||
|
var result = new Date(date);
|
||||||
|
result.setDate(result.getDate() + days);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/location/:location/date/:date', function(req, res, next) {
|
||||||
|
let date = new Date(req.params.date),
|
||||||
|
toDate = addDays(date, 1),
|
||||||
|
dateMonth = new Date(date.getFullYear(), 1, 1),
|
||||||
|
target = data[req.params.location];
|
||||||
|
Promise.all([db.cfind({ date: { $gte: toDate },
|
||||||
|
distance: { $lt: -0.3 },
|
||||||
|
location: req.params.location, type: 'tide' })
|
||||||
|
.sort({ date: 1 })
|
||||||
|
.limit(1)
|
||||||
|
.exec(),
|
||||||
|
db
|
||||||
|
.cfind({ $and: [{ date: { $gte: date }},
|
||||||
|
{ date: { $lt: toDate }}],
|
||||||
|
location: req.params.location })
|
||||||
|
.exec(),
|
||||||
|
db
|
||||||
|
.cfind({ $and: [{ date: { $gte: dateMonth }},
|
||||||
|
{ date: { $lte: addDays(dateMonth, 365) }}],
|
||||||
|
distance: { $lt: -0.3 },
|
||||||
|
location: req.params.location, type: 'tide' })
|
||||||
|
.exec()])
|
||||||
|
.then(([nextTideable, items, tideableDays]) =>
|
||||||
|
res.json({ tides: items.filter(x => x.type === 'tide' && x.low)
|
||||||
|
.sort((a, b) => a.date - b.date),
|
||||||
|
waves: items.filter(x => x.type === 'marine'),
|
||||||
|
weather: items.filter(x => x.type === 'weather')
|
||||||
|
.sort((a, b) => a.date - b.date)[0],
|
||||||
|
name: target.name,
|
||||||
|
nextTideable: nextTideable[0],
|
||||||
|
urls: target.urls,
|
||||||
|
tideableDays: tideableDays.map(x => x.date)
|
||||||
|
.filter(x => x.getHours() < 22 && x.getHours() > 5)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/location/:location/tide/:date', function(req, res, next) {
|
||||||
|
let date = addDays(new Date(req.params.date), -64),
|
||||||
|
toDate = addDays(date, 365),
|
||||||
|
target = data[req.params.location];
|
||||||
|
Promise.all([db
|
||||||
|
.cfind({ $and: [{ date: { $gte: date }},
|
||||||
|
{ date: { $lte: toDate }}],
|
||||||
|
distance: { $lt: -0.3 },
|
||||||
|
location: req.params.location, type: 'tide' })
|
||||||
|
.exec(),
|
||||||
|
db
|
||||||
|
.cfind({ $and: [{ date: { $gte: date }},
|
||||||
|
{ date: { $lte: toDate }}],
|
||||||
|
$and: [{ distance: { $lte: 0 }},
|
||||||
|
{ distance: { $gte: -0.3 }}],
|
||||||
|
location: req.params.location, type: 'tide' })
|
||||||
|
.exec()])
|
||||||
|
.then(([goodTides, okTides]) =>
|
||||||
|
res.json({ goodTides: goodTides.map(x => x.date),
|
||||||
|
okTides: okTides.map(x => x.date)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/refresh-sources', function(req, res, next) {
|
||||||
|
Promise.all(processSources(db)).then(() => res.json({}));
|
||||||
|
})
|
||||||
|
|
||||||
|
router.post('/clear-tide', (req, res, next) =>
|
||||||
|
db.remove({ type: 'tide' }, { multi: true }).then(() => res.json({})));
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
|
// const moment = require('moment');
|
||||||
|
// const doc = { hello: 'world'
|
||||||
|
// , n: 5
|
||||||
|
// , date: moment("2020/03/02" + ' ' + "11:03 AM", 'YYYY-MM-DD LT').toDate()
|
||||||
|
// , nedbIsAwesome: true
|
||||||
|
// , notthere: null
|
||||||
|
// , notToBeSaved: undefined // Will not be saved
|
||||||
|
// , fruits: [ 'apple', 'orange', 'pear' ]
|
||||||
|
// , infos: { name: 'nedb' }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let date = new Date('2020-03-15T00:00:00.000Z'),
|
||||||
|
// toDate = addDays(date, 1);
|
||||||
|
// db
|
||||||
|
// .cfind({ date: { $gt: date },
|
||||||
|
// distance: { $lt: -0.3 },
|
||||||
|
// location: 'asilomar', type: 'tide' })
|
||||||
|
// .sort({ date: 1 })
|
||||||
|
// .limit(1)
|
||||||
|
// .exec()
|
||||||
|
// .then(x => console.log(x));
|
||||||
|
|
||||||
|
// db.insert(doc)
|
||||||
|
// .then(db.findOne({ date: { $lt: new Date('2020-03-04T08:00:00.000Z') }}))
|
||||||
|
// .then(x => console.log(x.date.toLocaleTimeString()))
|
||||||
|
// .then(db.remove({}));
|
||||||
|
|
||||||
|
// db.cfind({ $and: [{ date: { $lt: new Date('2020-03-05T00:00:00.000Z') }},
|
||||||
|
// { date: { $gt: new Date('2020-03-04T00:00:00.000Z') }}] })
|
||||||
|
// .sort({ location: -1, date: -1 })
|
||||||
|
// .exec()
|
||||||
|
// .then(x => console.log(x));
|
||||||
|
// .then(db.remove({}, { multi: true }));
|
||||||
|
|
||||||
|
// db.cfind({ type: 'tide', location: 'asilomar', date: new Date('2020-03-04T06:43:00.000Z') })
|
||||||
|
// .exec()
|
||||||
|
// .then(x => console.log(x));
|
||||||
|
|
||||||
|
// db.remove({ _id: 'BJAkPl8Y9G4rn6GM' }, {})
|
||||||
|
// .then(db.findOne({ $and: [{ date: { $lt: new Date('2020-03-04T00:00:00.000Z') }},
|
||||||
|
// { date: { $gt: new Date('2020-03-03T00:00:00.000Z') }}] }))
|
||||||
|
// .then(x => console.log(x));
|
||||||
|
|
||||||
|
// db.findOne({ n: { $lt: 4 }})
|
||||||
|
// .then(x => console.log(x));
|
@ -0,0 +1,9 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
// /* GET home page. */
|
||||||
|
// router.get('/', function(req, res, next) {
|
||||||
|
// res.render('index', { title: 'Tideable' });
|
||||||
|
// });
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -0,0 +1,215 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// tides: https://tidesandcurrents.noaa.gov/map/index.html
|
||||||
|
|
||||||
|
// marine forecast: https://marine.weather.gov/MapClick.php?lon=-123.11327&lat=38.36586
|
||||||
|
|
||||||
|
// weather: https://api.weather.gov/points/38.3659,-123.1133
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const fsp = fs.promises;
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const xml2js = require('xml2js');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
let { sources } = JSON.parse(fs.readFileSync('../sources.json'));
|
||||||
|
|
||||||
|
function getMarineForecast(lat, lon) {
|
||||||
|
return fetch(`https://marine.weather.gov/MapClick.php?lat=${lat}&lon=${lon}&unit=0&lg=english&FcstType=json`).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarineAndWriteToFile(lat, lon, file) {
|
||||||
|
getMarineForecast(lat, lon)
|
||||||
|
.then(data => fs.writeFile(file,
|
||||||
|
JSON.stringify(data),
|
||||||
|
x => console.log(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMarineForecast(obj) {
|
||||||
|
const { time, data } = obj;
|
||||||
|
return time.startValidTime.map((t, i) => {
|
||||||
|
const sentences = data.text[i].split(/\. /);
|
||||||
|
let swellDetails = sentences[2].match(/\b[NESW]+ [a-z]*[ ]*\d+ t?o?f?t? ?\d*/g);
|
||||||
|
if (swellDetails === null) {
|
||||||
|
swellDetails = sentences[3].match(/\b[NESW]+ [a-z]*[ ]*\d+ t?o?f?t? ?\d*/g);
|
||||||
|
}
|
||||||
|
if (swellDetails !== null) {
|
||||||
|
swellDetails = swellDetails
|
||||||
|
.map(x => {
|
||||||
|
return {
|
||||||
|
direction: x.match(/\b[NESW]+/g)[0],
|
||||||
|
height: Math.max.apply(Math, x.match(/\d+/g).map(z => parseInt(z)))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
swellDetails = [];
|
||||||
|
}
|
||||||
|
const windHeight = Math.max.apply(Math, sentences[3].match(/\d+/g).map(z => parseInt(z)));
|
||||||
|
return {
|
||||||
|
date: new Date(t),
|
||||||
|
text: data.text[i],
|
||||||
|
swellText: sentences[2],
|
||||||
|
windText: sentences[3],
|
||||||
|
swellDetails,
|
||||||
|
windHeight
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeMarineForecast(db, location, objs) {
|
||||||
|
return objs
|
||||||
|
.map(x => {
|
||||||
|
return db.update({
|
||||||
|
type: 'marine',
|
||||||
|
location,
|
||||||
|
date: x.date
|
||||||
|
}, {
|
||||||
|
type: 'marine',
|
||||||
|
location,
|
||||||
|
...x
|
||||||
|
}, { upsert: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertXMLTide(file) {
|
||||||
|
const parser = new xml2js.Parser();
|
||||||
|
return parser.parseStringPromise(
|
||||||
|
fs.readFileSync(file + '.xml'))
|
||||||
|
.then(result => fs.writeFileSync(file + '.json',
|
||||||
|
JSON.stringify(result)));
|
||||||
|
// fs.readFileSync(file + '.xml', function(err, data) {
|
||||||
|
// parser.parseString(data, function (err, result) {
|
||||||
|
// fs.writeFile(file + '.json',
|
||||||
|
// JSON.stringify(result),
|
||||||
|
// x => console.log(x));
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTideData(input) {
|
||||||
|
return input.datainfo.data[0].item
|
||||||
|
.map(item => {
|
||||||
|
return {
|
||||||
|
date: item.date[0],
|
||||||
|
time: item.time[0],
|
||||||
|
low: item.highlow[0] === 'L',
|
||||||
|
distance: parseFloat(item.pred_in_ft[0])
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractWeatherData(input) {
|
||||||
|
return input.properties.periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeWeatherForecast(db, location, objs) {
|
||||||
|
return objs
|
||||||
|
.map(x => {
|
||||||
|
return db.update({
|
||||||
|
type: 'weather',
|
||||||
|
location,
|
||||||
|
date: new Date(x.startTime)
|
||||||
|
}, {
|
||||||
|
type: 'weather',
|
||||||
|
location,
|
||||||
|
...x,
|
||||||
|
date: new Date(x.startTime),
|
||||||
|
startTime: new Date(x.startTime),
|
||||||
|
endTime: new Date(x.endTime)
|
||||||
|
}, { upsert: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWeatherForecast(id) {
|
||||||
|
return fetch(`https://api.weather.gov/gridpoints/MTR/${id}/forecast`)
|
||||||
|
.then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWeatherAndWriteToFile(id, file) {
|
||||||
|
getWeatherForecast(id)
|
||||||
|
.then(data => fs.writeFile(file,
|
||||||
|
JSON.stringify(data),
|
||||||
|
x => console.log(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExtractWriteTideData(fileIn, fileOut) {
|
||||||
|
fs.writeFileSync(fileOut,
|
||||||
|
JSON.stringify({ data: extractTideData(JSON.parse(fs.readFileSync(fileIn))) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExtractStoreTideData(db, location, fileIn) {
|
||||||
|
return extractTideData(JSON.parse(fs.readFileSync(fileIn)))
|
||||||
|
.map(x => {
|
||||||
|
const d = moment(x.date + ' ' + x.time + '+0000', 'YYYY-MM-DD LT Z').toDate();
|
||||||
|
return db.update({
|
||||||
|
type: 'tide',
|
||||||
|
location,
|
||||||
|
date: d
|
||||||
|
}, {
|
||||||
|
type: 'tide',
|
||||||
|
location,
|
||||||
|
low: x.low,
|
||||||
|
date: d,
|
||||||
|
distance: x.distance
|
||||||
|
}, { upsert: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExtractWriteMarineForecast(fileIn, fileOut) {
|
||||||
|
fs.writeFileSync(fileOut,
|
||||||
|
JSON.stringify({ data: extractMarineForecast(JSON.parse(fs.readFileSync(fileIn))) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadExtractWriteWeatherData(fileIn, fileOut) {
|
||||||
|
fs.writeFileSync(fileOut,
|
||||||
|
JSON.stringify({ data: extractWeatherData(JSON.parse(fs.readFileSync(fileIn))) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function processSource(db, source) {
|
||||||
|
sources = JSON.parse(fs.readFileSync('../sources.json')).sources;
|
||||||
|
console.log('processing ' + source.name);
|
||||||
|
return db.findOne({ location: source.id, type: 'tide' })
|
||||||
|
.then(data => {
|
||||||
|
if (data === null) {
|
||||||
|
return convertXMLTide(`routes/data/${source.id}-tide-raw`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => db.findOne({ location: source.id, type: 'tide' }))
|
||||||
|
.then(data => {
|
||||||
|
if (data === null) {
|
||||||
|
loadExtractWriteTideData(`routes/data/${source.id}-tide-raw.json`,
|
||||||
|
`routes/data/${source.id}-tide.json`);
|
||||||
|
return Promise.all(loadExtractStoreTideData(db, source.id, `routes/data/${source.id}-tide-raw.json`));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => getMarineForecast(source.marine.lat, source.marine.lon))
|
||||||
|
.then(data => storeMarineForecast(db, source.id, extractMarineForecast(data)))
|
||||||
|
.then(() => getWeatherForecast(source.weather))
|
||||||
|
.then(data => storeWeatherForecast(db, source.id, extractWeatherData(data)))
|
||||||
|
.then(() => console.log('done processing ' + source.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.processSources = function(db) {
|
||||||
|
return sources.map(s => processSource(db, s));
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSource(sources['sonoma']);
|
||||||
|
|
||||||
|
// getMarineAndWriteToFile('37.4695', '-122.521', 'pillar-point-marine-forecast-raw.json');
|
||||||
|
// https://tidesandcurrents.noaa.gov/cgi-bin/predictiondownload.cgi?&stnid=9414131&threshold=&thresholdDirection=greaterThan&bdate=2020&timezone=LST/LDT&datum=MLLW&clock=12hour&type=xml&annual=true
|
||||||
|
// convertXMLTide('/data/pillar-point-tide-raw');
|
||||||
|
// loadExtractWriteTideData('pillar-point-tide-raw.json', 'pillar-point-tide.json');
|
||||||
|
// loadExtractWriteMarineForecast('pillar-point-marine-forecast-raw.json',
|
||||||
|
// 'pillar-point-marine-forecast.json');
|
||||||
|
// getWeatherAndWriteToFile('81,113', 'pillar-point-weather-raw.json');
|
||||||
|
// loadExtractWriteWeatherData('pillar-point-weather-raw.json',
|
||||||
|
// 'pillar-point-weather.json');
|
||||||
|
|
||||||
|
// getMarineAndWriteToFile('38.36586', '-123.11327', 'sonoma-marine-forecast-raw.json');
|
||||||
|
// loadExtractWriteMarineForecast('sonoma-marine-forecast-raw.json',
|
||||||
|
// 'sonoma-marine-forecast.json');
|
||||||
|
// convertXMLTide('/sonoma-tide-raw');
|
||||||
|
// loadExtractWriteTideData('sonoma-tide-raw.json', 'sonoma-tide.json');
|
||||||
|
// getWeatherAndWriteToFile('69,157', 'sonoma-weather-raw.json');
|
||||||
|
// loadExtractWriteWeatherData('sonoma-weather-raw.json',
|
||||||
|
// 'sonoma-weather.json');
|
@ -0,0 +1,16 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
/* GET users listing. */
|
||||||
|
router.get('/', function(req, res, next) {
|
||||||
|
// res.send('respond with a resource');
|
||||||
|
res.json([{
|
||||||
|
id: 1,
|
||||||
|
username: "samsepi0l"
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
username: "D0loresH4ze"
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{ "id": "pillar-point",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-122.521&lat=37.4695",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-122.49670028686525&lat=37.495928238526275" },
|
||||||
|
"name": "Pillar Point",
|
||||||
|
"marine": { "lat": "37.4695", "lon": "-122.521" },
|
||||||
|
"weather": "81,113" },
|
||||||
|
{ "id": "sonoma",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-123.11327&lat=38.36586",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-123.07047843933105&lat=38.36539466372582" },
|
||||||
|
"name": "Sonoma Coast",
|
||||||
|
"marine": { "lat": "38.36586", "lon": "-123.11327" },
|
||||||
|
"weather": "69,157" },
|
||||||
|
{ "id": "asilomar",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-121.96279&lat=36.65467",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-121.93244934082033&lat=36.632491687858646" },
|
||||||
|
"name": "Asilomar",
|
||||||
|
"marine": { "lat": "36.65467", "lon": "-121.96279" },
|
||||||
|
"weather": "95,73" },
|
||||||
|
{ "id": "pescadero",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-122.46918&lat=37.24657",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-122.41722106933592&lat=37.24176523193883" },
|
||||||
|
"name": "Pescadero",
|
||||||
|
"marine": { "lat": "37.24657", "lon": "-122.46918" },
|
||||||
|
"weather": "83,103" }
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= message
|
||||||
|
h2= error.status
|
||||||
|
pre #{error.stack}
|
@ -0,0 +1,5 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= title
|
||||||
|
p Welcome to #{title}
|
@ -0,0 +1,7 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title= title
|
||||||
|
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||||
|
body
|
||||||
|
block content
|
@ -0,0 +1,141 @@
|
|||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function register(config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl, config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl, {
|
||||||
|
headers: { 'Service-Worker': 'script' }
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready
|
||||||
|
.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{ "id": "pillar-point",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-122.521&lat=37.4695",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-122.49670028686525&lat=37.495928238526275",
|
||||||
|
"tide": "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=9414131" },
|
||||||
|
"name": "Pillar Point",
|
||||||
|
"marine": { "lat": "37.4695", "lon": "-122.521" },
|
||||||
|
"weather": "81,113" },
|
||||||
|
{ "id": "sonoma",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-123.11327&lat=38.36586",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-123.07047843933105&lat=38.36539466372582",
|
||||||
|
"tide": "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=9415625" },
|
||||||
|
"name": "Sonoma Coast",
|
||||||
|
"marine": { "lat": "38.36586", "lon": "-123.11327" },
|
||||||
|
"weather": "69,157" },
|
||||||
|
{ "id": "asilomar",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-121.96279&lat=36.65467",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-121.93244934082033&lat=36.632491687858646",
|
||||||
|
"tide": "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=9413450" },
|
||||||
|
"name": "Asilomar",
|
||||||
|
"marine": { "lat": "36.65467", "lon": "-121.96279" },
|
||||||
|
"weather": "95,73" },
|
||||||
|
{ "id": "pescadero",
|
||||||
|
"urls": { "marine": "https://marine.weather.gov/MapClick.php?lon=-122.46918&lat=37.24657",
|
||||||
|
"weather": "https://forecast.weather.gov/MapClick.php?lon=-122.41722106933592&lat=37.24176523193883",
|
||||||
|
"tide": "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=9413878" },
|
||||||
|
"name": "Pescadero",
|
||||||
|
"marine": { "lat": "37.24657", "lon": "-122.46918" },
|
||||||
|
"weather": "83,103" }
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue