Ready to push
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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
|
||||
.env
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
167
README.md
Normal file
167
README.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Weather Monitoring Application with Microservice Architecture
|
||||
|
||||
This is a weather monitoring application with microservice architecture. It is a simple application that allows users to monitor the weather at the user's current location, a location searched by the user, or historical weather data for a location.
|
||||
|
||||
## Design and Architecture
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- The application retrieves weather data from sensors and stores it in a database.
|
||||
- The application shows the current weather at the user's current location.
|
||||
- The application shows the current weather at a location searched by the user.
|
||||
- The application shows historical weather data for a location.
|
||||
- The application alerts the user when the weather is bad.
|
||||
- The application allows the user to set preferences for weather alerts.
|
||||
|
||||
### Microservices
|
||||
|
||||
- Data collection service: This microservice collects weather data from sensors and stores it in a database. The scope of this microservice is limited to data collection and storage. The communication pattern used by this microservice is asynchronous messaging.
|
||||
- Current weather service: This microservice retrieves the current weather from the database and shows it to the user. The scope of this microservice is limited to obtaining the user's current location using [IP-API](https://ipapi.co/) and retrieving the current weather from [OpenWeatherMap](https://openweathermap.org/). The communication pattern used by this microservice is synchronous request-response.
|
||||
- Realtime weather service: This microservice retrieves the current weather of a location based on the search query of the user and shows it to the user. The scope of this microservice is limited to retrieving the current weather of a location from [OpenWeatherMap](https://openweathermap.org/). The communication pattern used by this microservice is synchronous request-response.
|
||||
- Historical weather service: This microservice retrieves historical weather data for a location based on the search query of the user and shows it to the user. The scope of this microservice is limited to retrieving historical weather data for a location from [OpenWeatherMap](https://openweathermap.org/). The communication pattern used by this microservice is synchronous request-response.
|
||||
- Alert service: This microservice retrieves the current weather from the sensors and checks if the weather is bad. If it is, the alert service sends an alert to the user. The scope of this microservice is limited to accepting weather data and sending alerts. The communication pattern used by this microservice is asynchronous messaging.
|
||||
- User preferences service: This microservice allows the user to set preferences for weather alerts. The scope of this microservice is limited to storing user preferences. The communication pattern used by this microservice is asynchronous messaging.
|
||||
|
||||
### Communication Patterns
|
||||
|
||||
The communication pattern used in the weather monitoring system is a mix of synchronous request/response and asynchronous messaging. Synchronous request/response is used when a microservice needs to get data from another microservice in real-time. Asynchronous messaging is used when a microservice needs to send data to another microservice, but the response can be delayed.
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Frontend
|
||||
participant Current weather service
|
||||
participant IP-API
|
||||
participant Realtime weather service
|
||||
participant Historical weather service
|
||||
participant OpenWeatherMap
|
||||
participant Sensors
|
||||
participant Data collection service
|
||||
participant Database
|
||||
participant Alert service
|
||||
participant User preferences service
|
||||
|
||||
Frontend->>Current weather service: GET /current-weather
|
||||
Current weather service->>IP-API: GET /json
|
||||
IP-API-->>Current weather service: Response with user's current location
|
||||
Current weather service->>OpenWeatherMap: GET /weather
|
||||
OpenWeatherMap-->>Current weather service: Response with current weather
|
||||
Current weather service-->>Frontend: Response with current weather
|
||||
|
||||
Frontend->>Realtime weather service: GET /real-time-weather
|
||||
Realtime weather service->>OpenWeatherMap: GET /weather
|
||||
OpenWeatherMap-->>Realtime weather service: Response with current weather
|
||||
Realtime weather service-->>Frontend: Response with current weather
|
||||
|
||||
Frontend->>Historical weather service: GET /historical-weather
|
||||
Historical weather service->>OpenWeatherMap: GET /onecall
|
||||
OpenWeatherMap-->>Historical weather service: Response with historical weather
|
||||
Historical weather service-->>Frontend: Response with historical weather
|
||||
|
||||
Sensors->>Data collection service: Publish weather data
|
||||
Data collection service->>Database: Store weather data
|
||||
|
||||
Sensors->>Alert service: Publish weather data
|
||||
Alert service->>Frontend: Send alert if weather is bad
|
||||
|
||||
Frontend->>User preferences service: POST /user-preferences
|
||||
User preferences service->>Alert service: Publish user preferences
|
||||
```
|
||||
|
||||
### Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph User
|
||||
A((Frontend))
|
||||
end
|
||||
|
||||
subgraph Microservices
|
||||
B(Current weather service)
|
||||
C(Realtime weather service)
|
||||
D(Historical weather service)
|
||||
F(User preferences service)
|
||||
E(Alert service)
|
||||
G(Data collection service)
|
||||
end
|
||||
|
||||
subgraph Databases
|
||||
H[(Database)]
|
||||
end
|
||||
|
||||
subgraph APIs
|
||||
I[IP-API]
|
||||
J[OpenWeatherMap]
|
||||
end
|
||||
|
||||
subgraph Sensors
|
||||
K{Sensors}
|
||||
end
|
||||
|
||||
|
||||
A---B
|
||||
A---C
|
||||
A---D
|
||||
A---F
|
||||
|
||||
B---I
|
||||
B---J
|
||||
C---J
|
||||
D---J
|
||||
|
||||
K-->G
|
||||
G-->H
|
||||
|
||||
K-->E
|
||||
E-->A
|
||||
F-->E
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
The limitations around communication for microservices include increased complexity, potential latency, and the need for error handling. Because each microservice is a separate process, there is a potential for increased latency in communication between microservices. Additionally, error handling must be carefully implemented to ensure that failures in one microservice do not propagate to other microservices. Finally, the increased complexity of microservice-based architectures can make them harder to design, develop, deploy, and maintain than monolithic architectures.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Microservices
|
||||
|
||||
Three microservices were implemented:
|
||||
|
||||
- Current weather service: This microservice obtains the geographic coordinates of the user's current location using [IP-API](https://ipapi.co/) and retrieves the current weather from [OpenWeatherMap](https://openweathermap.org/). The frontend sends requests to this microservice by using the `GET /current-weather` endpoint. The microservice responds with the current weather in JSON format.
|
||||
|
||||
- Realtime weather service: This microservice retrieves the current weather of a location from [OpenWeatherMap](https://openweathermap.org/) based on the given location entered by the user. The frontend sends requests to this microservice by using the `GET /real-time-weather` endpoint. The microservice responds with the current weather in JSON format.
|
||||
|
||||
- Historical weather service: This microservice retrieves historical weather data for a location from [OpenWeatherMap](https://openweathermap.org/) based on the given location and UNIX timestamp entered by the user . The frontend sends requests to this microservice by using the `GET /historical-weather` endpoint. The microservice responds with historical weather data in JSON format.
|
||||
|
||||
### Dependencies
|
||||
|
||||
The following dependencies were used in the implementation of the microservices:
|
||||
|
||||
- Node.js
|
||||
- React
|
||||
- express
|
||||
- axios
|
||||
- dotenv
|
||||
|
||||
### Deployment
|
||||
|
||||
First navigate to the src directory of the microservice you want to deploy.
|
||||
|
||||
```bash
|
||||
cd src
|
||||
```
|
||||
|
||||
Then run the following command in separate terminals to start the microservices.
|
||||
|
||||
```bash
|
||||
node current.js
|
||||
node realtime.js
|
||||
node historical.js
|
||||
```
|
||||
|
||||
And run the following command in the root directory to start the frontend.
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
17274
package-lock.json
generated
Normal file
17274
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "weather-microservice",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4",
|
||||
"express": "^4.17.1",
|
||||
"axios": "^0.24.0",
|
||||
"dotenv": "^10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
public/index.html
Normal file
43
public/index.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/logo192.png
Normal file
BIN
public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
BIN
public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
public/manifest.json
Normal file
25
public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
38
src/App.css
Normal file
38
src/App.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
133
src/App.js
Normal file
133
src/App.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
function App() {
|
||||
const [option, setOption] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
const [timestamp, setTimestamp] = useState("");
|
||||
const [weatherData, setWeatherData] = useState(null);
|
||||
const currentAddress = "http://localhost:8080";
|
||||
const realtimeAddress = "http://localhost:4000";
|
||||
const historicalAddress = "http://localhost:7000";
|
||||
|
||||
const handleOptionChange = (event) => {
|
||||
setOption(event.target.value);
|
||||
};
|
||||
|
||||
const handleLocationChange = (event) => {
|
||||
setLocation(event.target.value);
|
||||
};
|
||||
|
||||
const handleTimestampChange = (event) => {
|
||||
setTimestamp(event.target.value);
|
||||
};
|
||||
|
||||
const handleGetWeatherData = async () => {
|
||||
try {
|
||||
let response;
|
||||
if (option === "realtime") {
|
||||
response = await axios.get(
|
||||
`${realtimeAddress}/real-time-weather?location=${location}`
|
||||
);
|
||||
} else if (option === "current") {
|
||||
response = await axios.get(`${currentAddress}/current-weather?`);
|
||||
} else {
|
||||
response = await axios.get(
|
||||
`${historicalAddress}/historical-weather?location=${location}×tamp=${timestamp}`
|
||||
);
|
||||
}
|
||||
console.log(response.data);
|
||||
setWeatherData(response.data);
|
||||
} catch (error) {
|
||||
console.error("Could not fetch weather data", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="option"
|
||||
value="current"
|
||||
checked={option === "current"}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
Current weather
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="option"
|
||||
value="realtime"
|
||||
checked={option === "realtime"}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
Realtime weather
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="option"
|
||||
value="historical"
|
||||
checked={option === "historical"}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
Historical weather
|
||||
</label>
|
||||
</div>
|
||||
{option === "realtime" && (
|
||||
<div>
|
||||
<label>
|
||||
Location:
|
||||
<input
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={handleLocationChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{option === "historical" && (
|
||||
<div>
|
||||
<label>
|
||||
Location:
|
||||
<input
|
||||
type="text"
|
||||
value={location}
|
||||
onChange={handleLocationChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Timestamp (in UNIX format):
|
||||
<input
|
||||
type="text"
|
||||
value={timestamp}
|
||||
onChange={handleTimestampChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<button onClick={handleGetWeatherData}>Get Weather Data</button>
|
||||
</div>
|
||||
{weatherData && (
|
||||
<div>
|
||||
<p>Location: {weatherData.location}</p>
|
||||
<p>
|
||||
Weather: {weatherData.weather.main} (
|
||||
{weatherData.weather.description})
|
||||
</p>
|
||||
<p>Temperature: {weatherData.temperature}°C</p>
|
||||
<p>Humidity: {weatherData.humidity}%</p>
|
||||
<p>Wind Speed: {weatherData.windSpeed} m/s</p>
|
||||
<p>Wind Direction: {weatherData.windDirection}°</p>
|
||||
<p>Timestamp: {weatherData.timestamp.toString()}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
8
src/App.test.js
Normal file
8
src/App.test.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
55
src/current.js
Normal file
55
src/current.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import express from "express";
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// Define OpenWeatherMap API endpoint and API key
|
||||
const OPENWEATHERMAP_API_ENDPOINT =
|
||||
"https://api.openweathermap.org/data/2.5/weather";
|
||||
const OPENWEATHERMAP_API_KEY = process.env.OPENWEATHERMAP_API_KEY;
|
||||
|
||||
// Define function for fetching weather data from OpenWeatherMap API
|
||||
async function getWeatherData(location) {
|
||||
const url = `${OPENWEATHERMAP_API_ENDPOINT}?lat=${location.lat}&lon=${location.lon}&units=metric&&appid=${OPENWEATHERMAP_API_KEY}`;
|
||||
const response = await axios.get(url);
|
||||
const { name, main, wind, dt, weather } = response.data;
|
||||
return {
|
||||
location: name,
|
||||
weather: weather[0],
|
||||
temperature: main.temp,
|
||||
humidity: main.humidity,
|
||||
windSpeed: wind.speed,
|
||||
windDirection: wind.deg,
|
||||
timestamp: new Date(dt * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
// Define Express server
|
||||
const app = express();
|
||||
|
||||
// Add middleware to set CORS headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
next();
|
||||
});
|
||||
|
||||
// Define API endpoint for getting current weather data
|
||||
app.get("/current-weather", async (req, res) => {
|
||||
try {
|
||||
const ipapiUrl = `https://ipapi.co/json/`;
|
||||
const ipapiResponse = await axios.get(ipapiUrl);
|
||||
const { city, region, country_name } = ipapiResponse.data;
|
||||
const geoUrl = `http://api.openweathermap.org/geo/1.0/direct?q=${city},${region},${country_name}&appid=${OPENWEATHERMAP_API_KEY}`;
|
||||
const geoResponse = await axios.get(geoUrl);
|
||||
const currentWeatherData = await getWeatherData(geoResponse.data[0]);
|
||||
res.json(currentWeatherData);
|
||||
} catch (error) {
|
||||
console.error("Could not fetch historical weather data", error);
|
||||
res.status(500).send("Internal server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Start Express server
|
||||
app.listen(8080, () => console.log("Server listening on port 8080"));
|
||||
62
src/historical.js
Normal file
62
src/historical.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import express from "express";
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// Define OpenWeatherMap API endpoint and API key
|
||||
const OPENWEATHERMAP_API_ENDPOINT =
|
||||
"https://api.openweathermap.org/data/3.0/onecall/timemachine";
|
||||
const OPENWEATHERMAP_API_KEY = process.env.OPENWEATHERMAP_API_KEY;
|
||||
|
||||
// Define function for fetching historical weather data from OpenWeatherMap API
|
||||
async function getHistoricalWeatherData(location, timestamp) {
|
||||
const url = `${OPENWEATHERMAP_API_ENDPOINT}?lat=${location.lat}&lon=${location.lon}&dt=${timestamp}&appid=${OPENWEATHERMAP_API_KEY}&units=metric`;
|
||||
const response = await axios.get(url);
|
||||
const { timezone_offset, data } = response.data;
|
||||
console.log(data[0].weather[0]);
|
||||
return {
|
||||
location: location.name,
|
||||
weather: data[0].weather[0],
|
||||
temperature: data[0].temp,
|
||||
humidity: data[0].humidity,
|
||||
windSpeed: data[0].wind_speed,
|
||||
windDirection: data[0].wind_deg,
|
||||
timestamp: new Date(data[0].dt * 1000 + timezone_offset * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
// Define Express server
|
||||
const app = express();
|
||||
|
||||
// Add middleware to set CORS headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
next();
|
||||
});
|
||||
|
||||
// Define API endpoint for getting historical weather data
|
||||
app.get("/historical-weather", async (req, res) => {
|
||||
const location = req.query.location;
|
||||
const timestamp = req.query.timestamp;
|
||||
// Fetch location data from OpenWeatherMap API
|
||||
try {
|
||||
const locationUrl = `https://api.openweathermap.org/data/2.5/weather?q=${location}&appid=${OPENWEATHERMAP_API_KEY}`;
|
||||
const locationResponse = await axios.get(locationUrl);
|
||||
const { name, coord } = locationResponse.data;
|
||||
|
||||
// Fetch historical weather data from OpenWeatherMap API
|
||||
const historicalWeatherData = await getHistoricalWeatherData(
|
||||
{ name, lat: coord.lat, lon: coord.lon },
|
||||
timestamp
|
||||
);
|
||||
res.json(historicalWeatherData);
|
||||
} catch (error) {
|
||||
console.error("Could not fetch historical weather data", error);
|
||||
res.status(500).send("Internal server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Start Express server
|
||||
app.listen(7000, () => console.log("Server listening on port 7000"));
|
||||
13
src/index.css
Normal file
13
src/index.css
Normal file
@@ -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;
|
||||
}
|
||||
17
src/index.js
Normal file
17
src/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.js";
|
||||
import reportWebVitals from "./reportWebVitals.js";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
1
src/logo.svg
Normal file
1
src/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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 |
53
src/realtime.js
Normal file
53
src/realtime.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import express from "express";
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// Define OpenWeatherMap API endpoint and API key
|
||||
const OPENWEATHERMAP_API_ENDPOINT =
|
||||
"https://api.openweathermap.org/data/2.5/weather";
|
||||
const OPENWEATHERMAP_API_KEY = process.env.OPENWEATHERMAP_API_KEY;
|
||||
|
||||
// Define function for fetching realtime weather data from OpenWeatherMap API
|
||||
async function getRealtimeWeatherData(location) {
|
||||
const url = `${OPENWEATHERMAP_API_ENDPOINT}?q=${location}&appid=${OPENWEATHERMAP_API_KEY}&units=metric`;
|
||||
const response = await axios.get(url);
|
||||
const { name, main, wind, dt, weather } = response.data;
|
||||
return {
|
||||
location: name,
|
||||
weather: weather[0],
|
||||
temperature: main.temp,
|
||||
humidity: main.humidity,
|
||||
windSpeed: wind.speed,
|
||||
windDirection: wind.deg,
|
||||
timestamp: new Date(dt * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
// Define Express server
|
||||
const app = express();
|
||||
|
||||
// Add middleware to set CORS headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
next();
|
||||
});
|
||||
|
||||
// Define API endpoint for getting real-time weather data
|
||||
app.get("/real-time-weather", async (req, res) => {
|
||||
const location = req.query.location;
|
||||
|
||||
// Fetch weather data from OpenWeatherMap API
|
||||
try {
|
||||
const realtimeWeatherData = await getRealtimeWeatherData(location);
|
||||
res.json(realtimeWeatherData);
|
||||
} catch (error) {
|
||||
console.error("Could not fetch weather data", error);
|
||||
res.status(500).send("Internal server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Start Express server
|
||||
app.listen(4000, () => console.log("Server listening on port 4000"));
|
||||
13
src/reportWebVitals.js
Normal file
13
src/reportWebVitals.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
5
src/setupTests.js
Normal file
5
src/setupTests.js
Normal file
@@ -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';
|
||||
Reference in New Issue
Block a user