diff --git a/services/backend/api/api.py b/services/backend/api/api.py index 4a859c8..2b441cc 100644 --- a/services/backend/api/api.py +++ b/services/backend/api/api.py @@ -1,8 +1,40 @@ from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from .models import Condition, Forecast +from typing import List, Optional +from datetime import datetime, timedelta +import random app = FastAPI() +origins = ["http://localhost", "http://localhost:3000"] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + @app.get("/") async def get_root(): return {"data": "I am a WeatherApp"} + + +@app.get("/forecast/{location}", response_model=List[Forecast]) +async def get_forecast_by_location( + location: str, start_date: Optional[datetime] = None, days: int = 5 +): + if not start_date: + start_date = datetime.now().date() + forecasts = [] + for delta in range(days): + forecast_date = start_date + timedelta(days=delta) + # Just get a random condition and temp + condition = random.choice(list(Condition)) + temp = round(random.uniform(23, 105), 1) + forecast = Forecast(date=forecast_date, condition=condition, temp=temp) + forecasts.append(forecast) + return forecasts diff --git a/services/backend/api/models.py b/services/backend/api/models.py new file mode 100644 index 0000000..ebe1ebb --- /dev/null +++ b/services/backend/api/models.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel +from datetime import date +from enum import Enum + + +class Condition(str, Enum): + sunny = "sunny" + cloudy = "cloudy" + partial_cloudy = "partial-cloudy" + partial_sunny = "partial-sunny" + windy = "windy" + lightning = "lightning" + snow = "snow" + rain = "rain" + light_right = "light-rain" + heavy_rain = "heavy-rain" + + +class Forecast(BaseModel): + date: date + condition: Condition + temp: float + + class Config: + use_enum_values = True diff --git a/services/backend/tests/test_api.py b/services/backend/tests/test_api.py index 20ecd29..dbcd1dc 100644 --- a/services/backend/tests/test_api.py +++ b/services/backend/tests/test_api.py @@ -7,3 +7,14 @@ def test_root(): response = test_app.get("/") assert response.status_code == 200 + + +def test_forecast(): + test_location = "San Francisco, CA" + days = 10 + response = test_app.get(f"/forecast/{test_location}?days={days}") + assert ( + response.status_code == 200 + ), f"Expected response 200, got {response.status_code}: {response.reason}." + data = response.json() + assert len(data) == days diff --git a/services/frontend/src/components/WeatherForecast.js b/services/frontend/src/components/WeatherForecast.js index 3a61ef3..11a4a94 100644 --- a/services/frontend/src/components/WeatherForecast.js +++ b/services/frontend/src/components/WeatherForecast.js @@ -1,34 +1,30 @@ import WeatherWidgetContainer from "./WeatherWidgetContainer"; import Header from "./Header"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import PropTypes from "prop-types"; export const WeatherForecast = ({ locationData, defaultExpanded }) => { - // Placeholder weather data - const weatherData = [ - { - temp: 51, - cond: "cloudy", - }, - { - temp: 48, - cond: "rain", - }, - { - temp: 58, - cond: "windy", - }, - { - temp: 62, - cond: "partial-cloudy", - }, - { - temp: 71, - cond: "sunny", - }, - ]; - const [isExpanded, setIsExpanded] = useState(defaultExpanded); + const [weatherData, setWeatherData] = useState(null); + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + fetch( + `http://localhost:8000/forecast/${locationData.city}, ${locationData.state}` + ) + .then((res) => res.json()) + .then( + (result) => { + // console.log(result); + setWeatherData(result); + setIsLoaded(true); + }, + (error) => { + console.log(error); + // TODO: Add isError state handling + } + ); + }, [locationData]); const toggleExpanded = () => { setIsExpanded(!isExpanded); @@ -42,7 +38,11 @@ export const WeatherForecast = ({ locationData, defaultExpanded }) => { onClick={toggleExpanded} >
- + {isLoaded ? ( + + ) : ( +

"Loading..."

+ )} ) : (
{ +const WeatherWidget = ({ temp, condition }) => { const getIconFromString = (conditionString) => { let returnVal; switch (conditionString) { @@ -53,7 +53,7 @@ const WeatherWidget = ({ temp, cond }) => {

- +

@@ -67,7 +67,7 @@ const WeatherWidget = ({ temp, cond }) => { WeatherWidget.propTypes = { temp: PropTypes.number, - cond: PropTypes.string, + condition: PropTypes.string, }; export default WeatherWidget; diff --git a/services/frontend/src/components/WeatherWidgetContainer.js b/services/frontend/src/components/WeatherWidgetContainer.js index 8131db3..0b2fa86 100644 --- a/services/frontend/src/components/WeatherWidgetContainer.js +++ b/services/frontend/src/components/WeatherWidgetContainer.js @@ -1,17 +1,12 @@ import PropTypes from "prop-types"; import WeatherWidget from "./WeatherWidget"; -const WeatherWidgetContainer = ({ weatherObjArray, widgetCount }) => { +const WeatherWidgetContainer = ({ weatherObjArray }) => { const createWeatherWidgets = (objArray) => { const widgetArray = []; - for (let index = 0; index < widgetCount; index++) { - let obj = objArray[index]; - if (!obj) { - console.log(`Object ${index} was missing!`); - obj = { temp: null, cond: null }; - } + for (const [index, obj] of objArray.entries()) { const widget = ( - + ); widgetArray.push(widget); } @@ -29,10 +24,9 @@ WeatherWidgetContainer.propTypes = { weatherObjArray: PropTypes.arrayOf( PropTypes.shape({ temp: PropTypes.number, - cond: PropTypes.string, + condition: PropTypes.string, }) ), - widgetCount: PropTypes.number, }; WeatherWidgetContainer.defaultProps = {