Skip to content

Commit 4c465a9

Browse files
authored
Merge pull request #134 from AnthonyGress/dev
use toast feedback for media request, exponential backoff for weather
2 parents c25765a + 5d7a697 commit 4c465a9

File tree

3 files changed

+56
-15
lines changed

3 files changed

+56
-15
lines changed

backend/src/routes/weather.route.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ import StatusCodes from 'http-status-codes';
55

66
export const weatherRoute = Router();
77

8+
// Helper function for retry logic with exponential backoff
9+
const retryWithBackoff = async <T>(
10+
fn: () => Promise<T>,
11+
maxRetries: number = 3,
12+
baseDelay: number = 1000
13+
): Promise<T> => {
14+
let lastError: Error | unknown;
15+
16+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
17+
try {
18+
return await fn();
19+
} catch (error) {
20+
lastError = error;
21+
22+
// Don't retry on client errors (4xx) or if it's the last attempt
23+
if (axios.isAxiosError(error) && error.response?.status && error.response.status < 500) {
24+
throw error;
25+
}
26+
27+
if (attempt === maxRetries) {
28+
throw error;
29+
}
30+
31+
// Exponential backoff: 1s, 2s, 4s
32+
const delay = baseDelay * Math.pow(2, attempt);
33+
console.log(`Weather API attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
34+
await new Promise(resolve => setTimeout(resolve, delay));
35+
}
36+
}
37+
38+
throw lastError;
39+
};
40+
841
/**
942
* GET /weather
1043
* Requires query parameters `latitude` and `longitude`.
@@ -22,17 +55,19 @@ weatherRoute.get('/', async (req: Request, res: Response): Promise<void> => {
2255
const latitude = req.query.latitude;
2356
const longitude = req.query.longitude;
2457

25-
// Fetch weather data from Open-Meteo with timeout
26-
const weatherResponse = await axios.get('https://api.open-meteo.com/v1/forecast', {
27-
params: {
28-
latitude: latitude,
29-
longitude: longitude,
30-
current: 'temperature_2m,weathercode,windspeed_10m',
31-
daily: 'temperature_2m_max,temperature_2m_min,weathercode,sunrise,sunset',
32-
timezone: 'auto'
33-
},
34-
timeout: 5000 // 5 second timeout
35-
});
58+
// Fetch weather data from Open-Meteo with retry logic
59+
const weatherResponse = await retryWithBackoff(async () => {
60+
return await axios.get('https://api.open-meteo.com/v1/forecast', {
61+
params: {
62+
latitude: latitude,
63+
longitude: longitude,
64+
current: 'temperature_2m,weathercode,windspeed_10m',
65+
daily: 'temperature_2m_max,temperature_2m_min,weathercode,sunrise,sunset',
66+
timezone: 'auto'
67+
},
68+
timeout: 5000 // 5 second timeout
69+
});
70+
}, 3, 1000); // 3 retries with 1 second base delay
3671

3772
res.json(weatherResponse.data);
3873

@@ -43,20 +78,20 @@ weatherRoute.get('/', async (req: Request, res: Response): Promise<void> => {
4378
if (axios.isAxiosError(error)) {
4479
if (error.code === 'ECONNABORTED') {
4580
statusCode = StatusCodes.GATEWAY_TIMEOUT;
46-
errorMessage = 'Weather API timeout';
81+
errorMessage = 'Weather API timeout after retries';
4782
} else if (error.response) {
4883
statusCode = error.response.status;
4984
errorMessage = `Weather API error: ${error.response.statusText}`;
5085
}
5186

52-
console.error(`Weather API error: ${errorMessage}`, {
87+
console.error(`Weather API error after retries: ${errorMessage}`, {
5388
status: statusCode,
5489
message: error.message,
5590
url: error.config?.url,
5691
params: error.config?.params
5792
});
5893
} else {
59-
console.error('Unknown error fetching weather:', error);
94+
console.error('Unknown error fetching weather after retries:', error);
6095
}
6196

6297
res.status(statusCode).json({ error: errorMessage });

frontend/src/components/dashboard/base-items/widgets/MediaRequestManagerWidget.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { COLORS } from '../../../../theme/styles';
4242
import { theme } from '../../../../theme/theme';
4343
import { CenteredModal } from '../../../modals/CenteredModal';
4444
import { PopupManager } from '../../../modals/PopupManager';
45+
import { ToastManager } from '../../../toast/ToastManager';
4546

4647
export interface MediaRequestManagerWidgetProps {
4748
id: string;
@@ -273,6 +274,7 @@ export const MediaRequestManagerWidget: React.FC<MediaRequestManagerWidgetProps>
273274
try {
274275
// Filter out already available seasons before sending the request
275276
let seasonsToRequest = seasons;
277+
const title = item.title || item.name;
276278
if (item.mediaType === 'tv' && seasons && tvShowDetails) {
277279
seasonsToRequest = seasons.filter(seasonNumber => {
278280
const season = tvShowDetails.seasons?.find((s: any) => s.seasonNumber === seasonNumber);
@@ -286,17 +288,21 @@ export const MediaRequestManagerWidget: React.FC<MediaRequestManagerWidgetProps>
286288
item.id.toString(),
287289
seasonsToRequest
288290
);
291+
289292
if (response.success) {
290293
// Refresh requests after creating one
291294
fetchRequests();
292295
// Clear search results and query
293296
setSearchResults([]);
294297
setSearchQuery('');
298+
ToastManager.success(`Successfully requested ${title}`);
295299
} else {
296300
console.error('Request creation failed:', response.error);
301+
ToastManager.error(`Failed to request ${title}`);
297302
}
298303
} catch (requestError) {
299304
console.error('Request creation error:', requestError);
305+
ToastManager.error('An error occured while requesting');
300306
}
301307
};
302308

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lab-dash",
3-
"version": "1.2.5",
3+
"version": "1.2.6",
44
"description": "This is an open-source user interface designed to manage your server and homelab",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)