-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Issue #1282]: Setup architecture for API calls with real and mock da…
- Loading branch information
Showing
13 changed files
with
473 additions
and
9 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { compact } from "lodash"; | ||
|
||
export type ApiMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT"; | ||
export interface JSONRequestBody { | ||
[key: string]: unknown; | ||
} | ||
|
||
export interface ApiResponseBody<TResponseData> { | ||
message: string; | ||
data: TResponseData; | ||
status_code: number; | ||
errors?: unknown[]; // TODO: define error and warning Issue type | ||
warnings?: unknown[]; | ||
} | ||
|
||
export interface HeadersDict { | ||
[header: string]: string; | ||
} | ||
|
||
export default abstract class BaseApi { | ||
/** | ||
* Root path of API resource without leading slash. | ||
*/ | ||
abstract get basePath(): string; | ||
|
||
/** | ||
* Namespace representing the API resource. | ||
*/ | ||
abstract get namespace(): string; | ||
|
||
/** | ||
* Configuration of headers to send with all requests | ||
* Can include feature flags in child classes | ||
*/ | ||
get headers() { | ||
return {}; | ||
} | ||
|
||
/** | ||
* Send an API request. | ||
*/ | ||
async request<TResponseData>( | ||
method: ApiMethod, | ||
subPath = "", | ||
body?: JSONRequestBody, | ||
options: { | ||
additionalHeaders?: HeadersDict; | ||
} = {} | ||
) { | ||
const { additionalHeaders = {} } = options; | ||
const url = createRequestUrl(method, this.basePath, subPath, body); | ||
const headers: HeadersDict = { | ||
...additionalHeaders, | ||
...this.headers, | ||
}; | ||
|
||
headers["Content-Type"] = "application/json"; | ||
|
||
const response = await this.sendRequest<TResponseData>(url, { | ||
body: method === "GET" || !body ? null : createRequestBody(body), | ||
headers, | ||
method, | ||
}); | ||
|
||
return response; | ||
} | ||
|
||
/** | ||
* Send a request and handle the response | ||
*/ | ||
private async sendRequest<TResponseData>( | ||
url: string, | ||
fetchOptions: RequestInit | ||
) { | ||
let response: Response; | ||
let responseBody: ApiResponseBody<TResponseData>; | ||
|
||
try { | ||
response = await fetch(url, fetchOptions); | ||
responseBody = (await response.json()) as ApiResponseBody<TResponseData>; | ||
} catch (error) { | ||
console.log("Network Error encountered => ", error); | ||
throw new Error("Network request failed"); | ||
// TODO: Error management | ||
// throw fetchErrorToNetworkError(error); | ||
} | ||
|
||
const { data, errors, warnings } = responseBody; | ||
if (!response.ok) { | ||
console.log( | ||
"Not OK Response => ", | ||
response, | ||
errors, | ||
this.namespace, | ||
data | ||
); | ||
|
||
throw new Error("Not OK response received"); | ||
// TODO: Error management | ||
// handleNotOkResponse(response, errors, this.namespace, data); | ||
} | ||
|
||
return { | ||
data, | ||
warnings, | ||
}; | ||
} | ||
} | ||
|
||
export function createRequestUrl( | ||
method: ApiMethod, | ||
basePath: string, | ||
subPath: string, | ||
body?: JSONRequestBody | ||
) { | ||
// Remove leading slash from apiPath if it has one | ||
const cleanedPaths = compact([basePath, subPath]).map(removeLeadingSlash); | ||
let url = [process.env.apiUrl, ...cleanedPaths].join("/"); | ||
|
||
if (method === "GET" && body && !(body instanceof FormData)) { | ||
// Append query string to URL | ||
const searchBody: { [key: string]: string } = {}; | ||
Object.entries(body).forEach(([key, value]) => { | ||
const stringValue = | ||
typeof value === "string" ? value : JSON.stringify(value); | ||
searchBody[key] = stringValue; | ||
}); | ||
|
||
const params = new URLSearchParams(searchBody).toString(); | ||
url = `${url}?${params}`; | ||
} | ||
return url; | ||
} | ||
|
||
/** | ||
* Remove leading slash | ||
*/ | ||
function removeLeadingSlash(path: string) { | ||
return path.replace(/^\//, ""); | ||
} | ||
|
||
/** | ||
* Transform the request body into a format that fetch expects | ||
*/ | ||
function createRequestBody(payload?: JSONRequestBody): XMLHttpRequestBodyInit { | ||
if (payload instanceof FormData) { | ||
return payload; | ||
} | ||
|
||
return JSON.stringify(payload); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import BaseApi, { JSONRequestBody } from "./BaseApi"; | ||
|
||
export interface SearchResponseData { | ||
opportunities: unknown[]; | ||
} | ||
|
||
export default class SearchOpportunityAPI extends BaseApi { | ||
get basePath(): string { | ||
return "search/opportunities"; | ||
} | ||
|
||
get namespace(): string { | ||
return "searchOpportunities"; | ||
} | ||
|
||
get headers() { | ||
return {}; | ||
} | ||
|
||
async getSearchOpportunities(queryParams?: JSONRequestBody) { | ||
const subPath = ""; | ||
|
||
const response = await this.request<SearchResponseData>( | ||
"GET", | ||
subPath, | ||
queryParams | ||
); | ||
|
||
return response; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Opportunity } from "../../types/searchTypes"; | ||
import { SearchFetcher } from "./SearchFetcher"; | ||
|
||
// TODO: Just a placeholder URL to display some data while we build search | ||
const URL = "https://jsonplaceholder.typicode.com/posts"; | ||
|
||
// TODO: call BaseApi or extension to make the actual call | ||
export class APISearchFetcher extends SearchFetcher { | ||
async fetchOpportunities(): Promise<Opportunity[]> { | ||
try { | ||
const response = await fetch(URL); | ||
if (!response.ok) { | ||
throw new Error(`HTTP error! status: ${response.status}`); | ||
} | ||
const data: Opportunity[] = (await response.json()) as Opportunity[]; | ||
return data; | ||
} catch (error) { | ||
console.error("Error fetching opportunities:", error); | ||
throw error; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Opportunity } from "../../types/searchTypes"; | ||
import { SearchFetcher } from "./SearchFetcher"; | ||
|
||
export const MOCKOPPORTUNITIES: Opportunity[] = [ | ||
{ | ||
userId: 1, | ||
id: 1, | ||
title: | ||
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit", | ||
body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto", | ||
}, | ||
{ | ||
userId: 1, | ||
id: 2, | ||
title: "qui est esse", | ||
body: "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla", | ||
}, | ||
{ | ||
userId: 1, | ||
id: 3, | ||
title: "ea molestias quasi exercitationem repellat qui ipsa sit aut", | ||
body: "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut", | ||
}, | ||
{ | ||
userId: 1, | ||
id: 4, | ||
title: "eum et est occaecati", | ||
body: "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit", | ||
}, | ||
]; | ||
|
||
export class MockSearchFetcher extends SearchFetcher { | ||
async fetchOpportunities(): Promise<Opportunity[]> { | ||
return await new Promise((resolve) => { | ||
// Resolve mock data file with simulated delay | ||
setTimeout(() => { | ||
resolve(MOCKOPPORTUNITIES); | ||
}, 500); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Opportunity } from "../../types/searchTypes"; | ||
|
||
export abstract class SearchFetcher { | ||
abstract fetchOpportunities(): Promise<Opportunity[]>; | ||
} | ||
|
||
export async function fetchSearchOpportunities( | ||
searchFetcher: SearchFetcher | ||
): Promise<Opportunity[]> { | ||
try { | ||
return await searchFetcher.fetchOpportunities(); | ||
} catch (error) { | ||
console.error("Failed to fetch opportunities:", error); | ||
return []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export interface Opportunity { | ||
userId: number; | ||
id: number; | ||
title: string; | ||
body: string; | ||
} |
Oops, something went wrong.