|
1 | 1 | import axios, { AxiosResponse } from 'axios';
|
2 | 2 | import { isSameDay, addDays } from 'date-fns';
|
3 | 3 |
|
| 4 | +/** An empty object, i.e. `{}` */ |
| 5 | +type EmptyItem = Record<string, undefined>; |
| 6 | + |
4 | 7 | export type WeatherResponse<T> = {
|
5 | 8 | api_info: {
|
6 | 9 | status: string;
|
7 | 10 | };
|
8 |
| - items: T[]; |
| 11 | + // API sometimes returns `[{}]` instead of an array of Ts, no idea why |
| 12 | + items: (T | EmptyItem)[]; |
9 | 13 | };
|
10 | 14 |
|
11 | 15 | type ValidPeriod = {
|
@@ -71,44 +75,39 @@ export type Forecast = Weather & {
|
71 | 75 | const API_PREFIX = 'https://api.data.gov.sg/v1/environment';
|
72 | 76 | const NOWCAST_AREA = 'Queenstown';
|
73 | 77 |
|
74 |
| -function getResponseData<T>(response: AxiosResponse<WeatherResponse<T>>): T { |
| 78 | +function getResponseData<T>(response: AxiosResponse<WeatherResponse<T>>): T | EmptyItem { |
75 | 79 | const { data } = response;
|
76 | 80 | if (data.api_info.status !== 'healthy') {
|
77 |
| - throw new Error(`API returned non-healthy status ${data.api_info.status}`); |
| 81 | + throw new Error(`Weather API returned non-healthy status ${data.api_info.status}`); |
78 | 82 | }
|
79 |
| - |
80 | 83 | return data.items[0];
|
81 | 84 | }
|
82 | 85 |
|
83 | 86 | export function twoHour(): Promise<string | null> {
|
84 | 87 | return axios
|
85 | 88 | .get<WeatherResponse<NowCastItem>>(`${API_PREFIX}/2-hour-weather-forecast`)
|
86 | 89 | .then((response) => {
|
87 |
| - // 2-hour forecast may return an empty object sometimes, no idea why |
88 | 90 | const areaForecast = getResponseData(response).forecasts?.find(
|
89 | 91 | (forecast) => forecast.area === NOWCAST_AREA,
|
90 | 92 | );
|
91 |
| - |
92 |
| - if (!areaForecast) return null; |
93 |
| - return areaForecast.forecast; |
| 93 | + return areaForecast?.forecast ?? null; |
94 | 94 | });
|
95 | 95 | }
|
96 | 96 |
|
97 | 97 | export function tomorrow(): Promise<string | null> {
|
98 | 98 | return axios
|
99 | 99 | .get<WeatherResponse<DayCastItem>>(`${API_PREFIX}/24-hour-weather-forecast`)
|
100 | 100 | .then((response) => {
|
101 |
| - const tomorrowForecast = getResponseData(response).periods.find((period) => |
| 101 | + const tomorrowForecast = getResponseData(response).periods?.find((period) => |
102 | 102 | isSameDay(new Date(period.time.start), addDays(new Date(), 1)),
|
103 | 103 | );
|
104 |
| - |
105 | 104 | // The forecast for tomorrow may not be available, so this can return null
|
106 |
| - return tomorrowForecast ? tomorrowForecast.regions.west : null; |
| 105 | + return tomorrowForecast?.regions?.west ?? null; |
107 | 106 | });
|
108 | 107 | }
|
109 | 108 |
|
110 | 109 | export function fourDay(): Promise<Forecast[]> {
|
111 | 110 | return axios
|
112 | 111 | .get<WeatherResponse<FourDayCastItem>>(`${API_PREFIX}/4-day-weather-forecast`)
|
113 |
| - .then((response) => getResponseData(response).forecasts); |
| 112 | + .then((response) => getResponseData(response).forecasts ?? []); |
114 | 113 | }
|
0 commit comments