Skip to content

Commit 6b3cee1

Browse files
feat(frontend): add retry strategy for backend calls
1 parent 7b6e51a commit 6b3cee1

File tree

10 files changed

+279
-174
lines changed

10 files changed

+279
-174
lines changed

frontend/src/app/education/hooks/useEducation.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { Formation, Certification } from '../interfaces';
33
import { getBackendEndpoint } from '@/utils/backend_endpoint';
4+
import { retryAsync } from '@/utils/retryAsync';
45

56
interface EducationData {
67
formations: Formation[];
@@ -20,27 +21,31 @@ export function useEducation(): EducationData {
2021
const educationEndpoint = getBackendEndpoint('/education');
2122

2223
try {
23-
const response = await fetch(`${educationEndpoint}`, {
24-
method: 'GET',
25-
headers: {
26-
'Accept': 'application/json',
27-
},
28-
mode: 'cors',
24+
const data = await retryAsync(async () => {
25+
const response = await fetch(`${educationEndpoint}`, {
26+
method: 'GET',
27+
headers: {
28+
'Accept': 'application/json',
29+
},
30+
mode: 'cors',
31+
});
32+
33+
if (!response.ok) {
34+
console.error(`Erro na requisição: Status ${response.status}`);
35+
const responseText = await response.text();
36+
console.error('Resposta do servidor:', responseText);
37+
throw new Error(`Falha ao carregar os dados de educação. Status: ${response.status}`);
38+
}
39+
40+
const jsonData = await response.json();
41+
42+
if (!jsonData.formations || !jsonData.certifications || !Array.isArray(jsonData.formations) || !Array.isArray(jsonData.certifications)) {
43+
throw new Error('Resposta inválida: os dados não estão no formato esperado');
44+
}
45+
46+
return jsonData as { formations: Formation[]; certifications: Certification[] };
2947
});
3048

31-
if (!response.ok) {
32-
console.error(`Erro na requisição: Status ${response.status}`);
33-
const responseText = await response.text();
34-
console.error('Resposta do servidor:', responseText);
35-
throw new Error(`Falha ao carregar os dados de educação. Status: ${response.status}`);
36-
}
37-
38-
const data = await response.json();
39-
40-
if (!data.formations || !data.certifications || !Array.isArray(data.formations) || !Array.isArray(data.certifications)) {
41-
throw new Error('Resposta inválida: os dados não estão no formato esperado');
42-
}
43-
4449
// No need to map formations as the format is already compatible
4550
setFormations(data.formations);
4651
setCertifications(data.certifications);

frontend/src/app/education/hooks/useTotalEducation.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { getBackendEndpoint } from '@/utils/backend_endpoint';
3+
import { retryAsync } from '@/utils/retryAsync';
34

45
interface TotalEducationData {
56
totalEducation: string;
@@ -17,26 +18,30 @@ export function useTotalEducation(): TotalEducationData {
1718
try {
1819
const educationEndpoint = getBackendEndpoint('/education');
1920

20-
const response = await fetch(educationEndpoint, {
21-
method: 'GET',
22-
headers: {
23-
'Accept': 'application/json',
24-
},
25-
mode: 'cors',
26-
});
21+
const data = await retryAsync(async () => {
22+
const response = await fetch(educationEndpoint, {
23+
method: 'GET',
24+
headers: {
25+
'Accept': 'application/json',
26+
},
27+
mode: 'cors',
28+
});
2729

28-
if (!response.ok) {
29-
console.error(`Erro na requisição: Status ${response.status}`);
30-
const responseText = await response.text();
31-
console.error('Resposta do servidor:', responseText);
32-
throw new Error(`Falha ao carregar os dados de educação. Status: ${response.status}`);
33-
}
34-
35-
const data = await response.json();
36-
37-
if (!data.formations || !data.certifications || !Array.isArray(data.formations) || !Array.isArray(data.certifications)) {
38-
throw new Error('Resposta inválida: os dados não estão no formato esperado');
39-
}
30+
if (!response.ok) {
31+
console.error(`Erro na requisição: Status ${response.status}`);
32+
const responseText = await response.text();
33+
console.error('Resposta do servidor:', responseText);
34+
throw new Error(`Falha ao carregar os dados de educação. Status: ${response.status}`);
35+
}
36+
37+
const jsonData = await response.json();
38+
39+
if (!jsonData.formations || !jsonData.certifications || !Array.isArray(jsonData.formations) || !Array.isArray(jsonData.certifications)) {
40+
throw new Error('Resposta inválida: os dados não estão no formato esperado');
41+
}
42+
43+
return jsonData as { formations: unknown[]; certifications: unknown[] };
44+
});
4045

4146
const total = data.formations.length + data.certifications.length;
4247
setTotalEducation(`${total}+`);

frontend/src/app/experience/hooks/useExperience.ts

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { Experience } from '../interfaces';
33
import { getBackendEndpoint } from '@/utils/backend_endpoint';
4+
import { retryAsync } from '@/utils/retryAsync';
45

56
interface CompanyDuration {
67
name: string;
@@ -26,52 +27,60 @@ export function useExperience(): ExperienceData {
2627
try {
2728
// Buscar experiências
2829
const experiencesEndpoint = getBackendEndpoint('/experiences');
29-
const experiencesResponse = await fetch(experiencesEndpoint, {
30-
method: 'GET',
31-
headers: {
32-
'Accept': 'application/json',
33-
},
34-
mode: 'cors',
35-
});
30+
const experiencesData = await retryAsync(async () => {
31+
const experiencesResponse = await fetch(experiencesEndpoint, {
32+
method: 'GET',
33+
headers: {
34+
'Accept': 'application/json',
35+
},
36+
mode: 'cors',
37+
});
3638

37-
if (!experiencesResponse.ok) {
38-
console.error(`Erro na requisição de experiências: Status ${experiencesResponse.status}`);
39-
const responseText = await experiencesResponse.text();
40-
console.error('Resposta do servidor:', responseText);
41-
throw new Error(`Falha ao carregar as experiências. Status: ${experiencesResponse.status}`);
42-
}
43-
44-
const experiencesData = await experiencesResponse.json();
45-
46-
if (!Array.isArray(experiencesData)) {
47-
throw new Error('Resposta inválida: os dados não são um array');
48-
}
39+
if (!experiencesResponse.ok) {
40+
console.error(`Erro na requisição de experiências: Status ${experiencesResponse.status}`);
41+
const responseText = await experiencesResponse.text();
42+
console.error('Resposta do servidor:', responseText);
43+
throw new Error(`Falha ao carregar as experiências. Status: ${experiencesResponse.status}`);
44+
}
45+
46+
const jsonData = await experiencesResponse.json();
47+
48+
if (!Array.isArray(jsonData)) {
49+
throw new Error('Resposta inválida: os dados não são um array');
50+
}
51+
52+
return jsonData as Experience[];
53+
});
4954

5055
setExperiences(experiencesData);
5156

5257
// Buscar duração por empresa
5358
const companyDurationsEndpoint = getBackendEndpoint('/experiences?company_duration=true');
54-
const companyDurationsResponse = await fetch(companyDurationsEndpoint, {
55-
method: 'GET',
56-
headers: {
57-
'Accept': 'application/json',
58-
},
59-
mode: 'cors',
59+
const durationsData = await retryAsync(async () => {
60+
const companyDurationsResponse = await fetch(companyDurationsEndpoint, {
61+
method: 'GET',
62+
headers: {
63+
'Accept': 'application/json',
64+
},
65+
mode: 'cors',
66+
});
67+
68+
if (!companyDurationsResponse.ok) {
69+
console.error(`Erro na requisição de durações por empresa: Status ${companyDurationsResponse.status}`);
70+
const responseText = await companyDurationsResponse.text();
71+
console.error('Resposta do servidor:', responseText);
72+
throw new Error(`Falha ao carregar as durações por empresa. Status: ${companyDurationsResponse.status}`);
73+
}
74+
75+
const jsonData = await companyDurationsResponse.json();
76+
77+
if (!Array.isArray(jsonData)) {
78+
throw new Error('Resposta inválida de durações: os dados não são um array');
79+
}
80+
81+
return jsonData as CompanyDuration[];
6082
});
6183

62-
if (!companyDurationsResponse.ok) {
63-
console.error(`Erro na requisição de durações por empresa: Status ${companyDurationsResponse.status}`);
64-
const responseText = await companyDurationsResponse.text();
65-
console.error('Resposta do servidor:', responseText);
66-
throw new Error(`Falha ao carregar as durações por empresa. Status: ${companyDurationsResponse.status}`);
67-
}
68-
69-
const durationsData = await companyDurationsResponse.json();
70-
71-
if (!Array.isArray(durationsData)) {
72-
throw new Error('Resposta inválida de durações: os dados não são um array');
73-
}
74-
7584
// Mapear durações por empresa
7685
const durationsMap: Record<string, string> = {};
7786
durationsData.forEach((item: CompanyDuration) => {
@@ -82,23 +91,25 @@ export function useExperience(): ExperienceData {
8291

8392
// Buscar tempo total de carreira
8493
const totalDurationEndpoint = getBackendEndpoint('/experiences?total_duration=true');
85-
const totalDurationResponse = await fetch(totalDurationEndpoint, {
86-
method: 'GET',
87-
headers: {
88-
'Accept': 'application/json',
89-
},
90-
mode: 'cors',
94+
const totalData = await retryAsync(async () => {
95+
const totalDurationResponse = await fetch(totalDurationEndpoint, {
96+
method: 'GET',
97+
headers: {
98+
'Accept': 'application/json',
99+
},
100+
mode: 'cors',
101+
});
102+
103+
if (!totalDurationResponse.ok) {
104+
console.error(`Erro na requisição de tempo total: Status ${totalDurationResponse.status}`);
105+
const responseText = await totalDurationResponse.text();
106+
console.error('Resposta do servidor:', responseText);
107+
throw new Error(`Falha ao carregar o tempo total. Status: ${totalDurationResponse.status}`);
108+
}
109+
110+
return totalDurationResponse.json();
91111
});
92112

93-
if (!totalDurationResponse.ok) {
94-
console.error(`Erro na requisição de tempo total: Status ${totalDurationResponse.status}`);
95-
const responseText = await totalDurationResponse.text();
96-
console.error('Resposta do servidor:', responseText);
97-
throw new Error(`Falha ao carregar o tempo total. Status: ${totalDurationResponse.status}`);
98-
}
99-
100-
const totalData = await totalDurationResponse.json();
101-
102113
if (typeof totalData.total_duration === 'string') {
103114
setTempoTotalCarreira(totalData.total_duration);
104115
} else {

frontend/src/app/experience/hooks/useTotalExperience.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { getBackendEndpoint } from '@/utils/backend_endpoint';
3+
import { retryAsync } from '@/utils/retryAsync';
34

45
interface TotalExperienceData {
56
totalExperience: string;
@@ -17,22 +18,24 @@ export function useTotalExperience(): TotalExperienceData {
1718
try {
1819
const experiencesEndpoint = getBackendEndpoint('/experiences?total_duration=true');
1920

20-
const response = await fetch(experiencesEndpoint, {
21-
method: 'GET',
22-
headers: {
23-
'Accept': 'application/json',
24-
},
25-
mode: 'cors',
26-
});
21+
const data = await retryAsync(async () => {
22+
const response = await fetch(experiencesEndpoint, {
23+
method: 'GET',
24+
headers: {
25+
'Accept': 'application/json',
26+
},
27+
mode: 'cors',
28+
});
2729

28-
if (!response.ok) {
29-
console.error(`Erro na requisição: Status ${response.status}`);
30-
const responseText = await response.text();
31-
console.error('Resposta do servidor:', responseText);
32-
throw new Error(`Falha ao carregar o tempo total de experiência. Status: ${response.status}`);
33-
}
34-
35-
const data = await response.json();
30+
if (!response.ok) {
31+
console.error(`Erro na requisição: Status ${response.status}`);
32+
const responseText = await response.text();
33+
console.error('Resposta do servidor:', responseText);
34+
throw new Error(`Falha ao carregar o tempo total de experiência. Status: ${response.status}`);
35+
}
36+
37+
return response.json();
38+
});
3639

3740
if (typeof data.total_duration === 'string') {
3841
// Extrair apenas o número de anos da string (ex: "13 anos e 4 meses" -> "13")

frontend/src/app/layout.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,23 @@ export default function RootLayout({
4848
<script
4949
dangerouslySetInnerHTML={{
5050
__html: `
51-
(async function checkBackendStatus() {
52-
try {
53-
const response = await fetch('/api/v1/ping');
54-
if (!response.ok) throw new Error('API not ready');
55-
56-
const data = await response.json();
57-
if (data.message !== 'pong') throw new Error('Invalid API response');
58-
} catch (error) {
59-
console.log('Backend not ready, retrying in 1s...');
60-
setTimeout(checkBackendStatus, 1000);
61-
}
51+
(function checkBackendStatus(attempt = 1) {
52+
fetch('/api/v1/ping')
53+
.then(response => {
54+
if (!response.ok) throw new Error('API not ready');
55+
return response.json();
56+
})
57+
.then(data => {
58+
if (data.message !== 'pong') throw new Error('Invalid API response');
59+
})
60+
.catch(error => {
61+
if (attempt < 3) {
62+
console.log('Backend not ready, retrying in 1s...');
63+
setTimeout(() => checkBackendStatus(attempt + 1), 1000);
64+
} else {
65+
console.error('Backend unreachable:', error);
66+
}
67+
});
6268
})();
6369
`,
6470
}}

0 commit comments

Comments
 (0)