Ready to push

This commit is contained in:
AndrewTrieu
2023-04-22 19:36:28 +03:00
commit 526ad408ee
21 changed files with 17976 additions and 0 deletions

24
.gitignore vendored Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
public/index.html Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json Normal file
View 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
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
src/App.css Normal file
View 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
View 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}&timestamp=${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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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';