Skip to content

Commit bcaf97c

Browse files
authored
Fix fetching error and add safe fetch (#126)
1 parent 05a1df7 commit bcaf97c

File tree

10 files changed

+148
-43
lines changed

10 files changed

+148
-43
lines changed

api/client.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,23 @@ export default class APIClient {
1212

1313
private async request<T>(url: string, options: RequestInit): Promise<T> {
1414
const response = await fetch(`${this.baseURL}${url}`, options);
15+
16+
const contentType = response.headers.get("content-type");
17+
let responseBody;
18+
if (contentType && contentType.includes("application/json")) {
19+
responseBody = await response.json();
20+
} else {
21+
responseBody = await response.text();
22+
}
23+
1524
if (!response.ok) {
1625
const error = new Error("HTTP Error") as any;
1726
error.status = response.status;
18-
error.response = await response.json();
27+
error.response = responseBody;
1928
throw error;
2029
}
21-
return (await response.json()) as T;
30+
31+
return responseBody as T;
2232
}
2333

2434
public get<T>(url: string, options: Options = {}): Promise<T> {

api/core/_client.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { prepareUrl, sanitizeUrl } from "@/utils/url";
21
import APIClient from "@/api/client";
2+
import { prepareUrl, sanitizeUrl } from "@/utils/url";
33

44
const CORE_API_URL = sanitizeUrl(process.env.NEXT_PUBLIC_API_URL || "");
55

@@ -8,12 +8,20 @@ export async function fetchFromApi<T>(
88
queryParams: Record<string, any>,
99
tag?: string,
1010
): Promise<T> {
11+
const url = prepareUrl(endpoint, queryParams);
12+
const config = tag ? { tag } : { noStoreCache: true };
13+
1114
try {
12-
const url = prepareUrl(endpoint, queryParams);
13-
const config = tag ? { tag } : { noStoreCache: true };
1415
return await coreApiClient.get<T>(url, config);
15-
} catch (error) {
16-
console.error(`Error fetching data from ${endpoint}:`, error);
16+
} catch (error: any) {
17+
if (error?.response) {
18+
console.error(`[fetchFromApi] Error in ${endpoint}:`, {
19+
status: error.status,
20+
response: error.response,
21+
});
22+
} else {
23+
console.error(`[fetchFromApi] Unknown error in ${endpoint}:`, error);
24+
}
1725
throw error;
1826
}
1927
}

app/explore/[slug]/layout.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { container } from "@/components/primitives";
44
import { FiltersProvider } from "@/contexts/filters";
55
import { decodingSlug } from "@/utils/url";
66
import { getFilterOptions } from "@/lib/filters";
7+
import { safeFetch } from "@/utils/error";
8+
import { initFilterOptions } from "@/utils/filters";
79

810
export default async function ExploreLayout(
911
props: {
@@ -17,7 +19,12 @@ export default async function ExploreLayout(
1719
children
1820
} = props;
1921

20-
const filterOptions = await getFilterOptions();
22+
const filterOptions = await safeFetch(
23+
() => getFilterOptions(),
24+
"ExploreLayout: getFilterOptions",
25+
initFilterOptions(),
26+
{ params },
27+
);
2128
const decodedSlug = decodeURIComponent(params.slug);
2229
const filters = decodingSlug(decodedSlug, filterOptions);
2330

app/explore/[slug]/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import InfiniteTable from "@/components/table/infinite-table";
33
import { filtersToTasksQuery, getFilterOptions } from "@/lib/filters";
44
import { decodingSlug } from "@/utils/url";
55
import { fetchTasks } from "@/lib/api/tasks";
6+
import { initFilterOptions } from "@/utils/filters";
7+
import { safeFetch } from "@/utils/error";
68

79
interface IProps {
810
params: Promise<{ slug: string }>;
@@ -27,7 +29,12 @@ export async function generateMetadata(props: IProps): Promise<Metadata> {
2729

2830
export default async function ExplorePage(props: IProps) {
2931
const params = await props.params;
30-
const filterOptions = await getFilterOptions();
32+
const filterOptions = await safeFetch(
33+
() => getFilterOptions(),
34+
"ExplorePage: getFilterOptions",
35+
initFilterOptions(),
36+
{ params },
37+
);
3138
const decodedSlug = decodeURIComponent(params.slug);
3239
const filters = decodingSlug(decodedSlug, filterOptions);
3340
const query = filtersToTasksQuery(filters);

data/fetch.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,23 @@ export const DEFAULT_PROJECT_OPTIONS = {
3636
stack_levels: [],
3737
technologies: [],
3838
};
39+
40+
export const DEFAULT_ALL_LANGUAGES = [
41+
"javascript",
42+
"haskell",
43+
"c",
44+
"go",
45+
"markdown",
46+
"rust",
47+
"c#",
48+
"python",
49+
"css",
50+
"typescript",
51+
"java",
52+
"swift",
53+
"kotlin",
54+
"vue",
55+
"solidity",
56+
"c++",
57+
"tex",
58+
];

lib/api/languages.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import LanguagesApi from "@/api/core/languages";
22
import { LanguageQueryParams } from "@/types/repository";
3+
import { safeFetch } from "@/utils/error";
4+
import { DEFAULT_ALL_LANGUAGES } from "@/data/fetch";
35

46
export async function fetchLanguages(
57
query: LanguageQueryParams,
68
): Promise<string[]> {
7-
let values: string[] = [];
8-
return LanguagesApi.getAllLanguages(query).catch((error) => {
9-
console.error("Error fetching LANGUAGE values:", error);
10-
return values;
11-
});
9+
return safeFetch(
10+
() => LanguagesApi.getAllLanguages(query),
11+
"fetchLanguages",
12+
DEFAULT_ALL_LANGUAGES,
13+
{ query },
14+
);
1215
}

lib/api/projects.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@/types/pagination";
1313
import { Project, ProjectOptions, ProjectQueryParams } from "@/types/project";
1414
import tags from "@/utils/tags";
15+
import { safeFetch } from "@/utils/error";
1516

1617
export async function fetchProjectInfo(slug: string) {
1718
return ConfigApi.getProjectInfos(slug).catch((error) => {
@@ -30,7 +31,16 @@ async function fetchAllProjects(tag?: string): Promise<Project[]> {
3031
offset,
3132
limit: DEFAULT_BIG_PAGE_SIZE,
3233
};
33-
const response = await ProjectApi.getProjects(paginationParams, tag);
34+
const response = await safeFetch(
35+
() => ProjectApi.getProjects(paginationParams, tag),
36+
"fetchAllProjects",
37+
null,
38+
{ paginationParams }
39+
);
40+
41+
if (response === null) {
42+
break;
43+
}
3444

3545
projects = projects.concat(response.data);
3646
hasMore = response.hasNextPage;
@@ -41,32 +51,36 @@ async function fetchAllProjects(tag?: string): Promise<Project[]> {
4151
}
4252

4353
export async function fetchProject(slug: string): Promise<Project | undefined> {
44-
const res = await ProjectApi.getProjects({
45-
slugs: [slug],
46-
}).catch((error) => {
47-
console.error(`Error fetching PROJECT "${slug}":`, error);
48-
return undefined;
49-
});
50-
51-
return res?.data[0];
54+
return safeFetch(
55+
() => ProjectApi.getProjects({
56+
slugs: [slug],
57+
}).then(res => res.data[0]),
58+
"fetchProject",
59+
undefined,
60+
{ slug }
61+
);
5262
}
5363

5464
export async function fetchProjects(
5565
query: ProjectQueryParams & PaginationQueryParams,
5666
): Promise<PaginatedCustomResponse<Project>> {
57-
return await ProjectApi.getProjects(query).catch((error) => {
58-
console.error("Error fetching PROJECTS", error);
59-
return DEFAULT_PAGINATED_RESPONSE;
60-
});
67+
return safeFetch(
68+
() => ProjectApi.getProjects(query),
69+
"fetchProjects",
70+
DEFAULT_PAGINATED_RESPONSE,
71+
{ query }
72+
);
6173
}
6274

6375
export async function fetchProjectOptions(
6476
query: ProjectQueryParams,
6577
): Promise<ProjectOptions> {
66-
return await ProjectApi.getProjectOptions(query).catch((error) => {
67-
console.error("Error fetching PROJECT OPTIONS", error);
68-
return DEFAULT_PROJECT_OPTIONS;
69-
});
78+
return safeFetch(
79+
() => ProjectApi.getProjectOptions(query),
80+
"fetchProjectOptions",
81+
DEFAULT_PROJECT_OPTIONS,
82+
{ query }
83+
);
7084
}
7185

7286
export async function getAllProjects(): Promise<Project[]> {

lib/api/tasks.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,30 @@ import {
66
PaginationQueryParams,
77
} from "@/types/pagination";
88
import tags from "@/utils/tags";
9+
import { safeFetch } from "@/utils/error";
910

1011
export async function fetchTasks(
1112
query: TaskQueryParams & PaginationQueryParams,
1213
): Promise<PaginatedCustomResponse<Task>> {
13-
return TasksApi.getTasks(query, tags.latestTasks).catch((error) => {
14-
console.error("Error fetching TASKS:", error);
15-
return DEFAULT_PAGINATED_RESPONSE;
16-
});
14+
return safeFetch(
15+
() => TasksApi.getTasks(query, tags.latestTasks),
16+
"fetchTasks",
17+
DEFAULT_PAGINATED_RESPONSE,
18+
{ query }
19+
);
1720
}
1821

1922
export async function fetchProjectTasks(
2023
slug: string,
2124
query?: TaskQueryParams,
2225
): Promise<PaginatedCustomResponse<Task>> {
23-
return TasksApi.getTasks({
24-
projects: [slug],
25-
...query,
26-
}).catch((error) => {
27-
console.error(`Error fetching TASKS for PROJECT "${slug}":`, error);
28-
return DEFAULT_PAGINATED_RESPONSE;
29-
});
26+
return safeFetch(
27+
() => TasksApi.getTasks({
28+
projects: [slug],
29+
...query,
30+
}),
31+
"fetchProjectTasks",
32+
DEFAULT_PAGINATED_RESPONSE,
33+
{ slug, query }
34+
);
3035
}

utils/error.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export async function safeFetch<T>(
2+
fetchFn: () => Promise<T>,
3+
context: string,
4+
fallback: T ,
5+
additionalInfo?: any
6+
): Promise<T> {
7+
try {
8+
return await fetchFn();
9+
} catch (error) {
10+
logError(context, error, additionalInfo);
11+
return fallback;
12+
}
13+
}
14+
15+
// Utility function to improve error logging
16+
export function logError(context: string, error: any, additionalInfo?: any) {
17+
console.error(`[Error in ${context}]`, error);
18+
if (additionalInfo) {
19+
console.error("Additional Info:", additionalInfo);
20+
}
21+
}

utils/filters.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
STACK_LEVEL_KEY,
77
TECHNOLOGY_KEY,
88
} from "@/data/filters";
9-
import { FilterKeys, IFilterOption, Filters } from "@/types/filters";
9+
import { FilterKeys, IFilterOption, Filters, FilterOptions } from "@/types/filters";
1010

1111
// Fisher-Yates (or Knuth) shuffle algorithm
1212
export const shuffleArray = <T>(array: T[]): T[] => {
@@ -29,6 +29,16 @@ export const shuffleArray = <T>(array: T[]): T[] => {
2929
return array;
3030
};
3131

32+
export const initFilterOptions = (): FilterOptions => {
33+
return {
34+
[PURPOSE_KEY]: [],
35+
[PROJECT_TYPE_KEY]: [],
36+
[TECHNOLOGY_KEY]: [],
37+
[STACK_LEVEL_KEY]: [],
38+
[PROJECTS_KEY]: [],
39+
};
40+
};
41+
3242
export const initFilters = (): Filters => {
3343
return {
3444
[PURPOSE_KEY]: [],

0 commit comments

Comments
 (0)