Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend api cleaned #13

Merged
merged 7 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "frontend",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:27017",
"type": "module",
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
Expand Down
119 changes: 119 additions & 0 deletions frontend/src/api/requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Credit to justinyaodu https://github.com/TritonSE/TSE-Fulcrum/blob/main/frontend/src/api.ts
*/

/**
* A custom type defining which HTTP methods we will handle in this file
*/
type Method = "GET" | "POST" | "PUT";

/**
* A wrapper around the built-in `fetch()` function that abstracts away some of
* the low-level details so we can focus on the important parts of each request.
* See https://developer.mozilla.org/en-US/docs/Web/API/fetch for information
* about the Fetch API.
*
* @param method The HTTP method to use
* @param url The URL to request
* @param body The body of the request, or undefined if there is none
* @param headers The headers of the request
* @returns The Response object returned by `fetch()
*/
async function fetchRequest(
method: Method,
url: string,
body: unknown,
headers: Record<string, string>,
): Promise<Response> {
const hasBody = body !== undefined;

const newHeaders = { ...headers };
if (hasBody) {
newHeaders["Content-Type"] = "application/json";
}

const response = await fetch(url, {
method,
headers: newHeaders,
body: hasBody ? JSON.stringify(body) : undefined,
});

return response;
}

/**
* Throws an error if the given response's status code indicates an error
* occurred, else does nothing.
*
* @param response A response returned by `fetch()` or `fetchRequest()`
* @throws An error if the response was not successful (200-299) or a redirect
* (300-399)
*/
async function assertOk(response: Response): Promise<void> {
if (response.ok) {
return;
}

let message = `${response.status} ${response.statusText}`;

try {
const text = await response.text();
if (text) {
message += ": " + text;
}
} catch (e) {
// skip errors
}

throw new Error(message);
}

/**
* Sends a GET request to the provided URL.
*
* @param url The URL to request
* @param headers The headers of the request (optional)
* @returns The Response object returned by `fetch()`
*/
export async function get(url: string, headers: Record<string, string> = {}): Promise<Response> {
// GET requests do not have a body
const response = await fetchRequest("GET", url, undefined, headers);
assertOk(response);
return response;
}

/**
* Sends a POST request to the provided URL.
*
* @param url The URL to request
* @param body The body of the request, or undefined if there is none
* @param headers The headers of the request (optional)
* @returns The Response object returned by `fetch()`
*/
export async function post(
url: string,
body: unknown,
headers: Record<string, string> = {},
): Promise<Response> {
const response = await fetchRequest("POST", url, body, headers);
assertOk(response);
return response;
}

/**
* Sends a PUT request to the provided URL.
*
* @param url The URL to request
* @param body The body of the request, or undefined if there is none
* @param headers The headers of the request (optional)
* @returns The Response object returned by `fetch()`
*/
export async function put(
url: string,
body: unknown,
headers: Record<string, string> = {},
): Promise<Response> {
const response = await fetchRequest("GET", url, body, headers);
assertOk(response);
return response;
}
69 changes: 69 additions & 0 deletions frontend/src/api/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { post } from "src/api/requests";

/**
* Defines the "shape" of a Task object (what fields are present and their types) for
* frontend components to use. This will be the return type of most functions in this
* file.
*/
export interface Task {
_id: string;
title: string;
description?: string;
isChecked: boolean;
dateCreated: Date;
}

/**
* Defines the shape of JSON that we'll receive from the backend when we ask the API
* for a Task object. That is, when the backend sends us a JSON object representing a
* Task, we expect it to match these fields and types.
*
* The difference between this type and `Task` above is that `dateCreated` is a string
* instead of a Date object. This is because JSON doesn't support Dates, so we use a
* date-formatted string in requests and responses.
*/
interface TaskJSON {
_id: string;
title: string;
description?: string;
isChecked: boolean;
dateCreated: string;
}

/**
* Converts a Task from JSON that only contains primitive types to our custom
* Task interface.
*
* @param task The JSON representation of the task
* @returns The parsed Task object
*/
function parseTask(task: TaskJSON): Task {
return {
_id: task._id,
title: task.title,
description: task.description,
isChecked: task.isChecked,
dateCreated: new Date(task.dateCreated),
};
}

/**
* The expected inputs when we want to create a new Task object. In the MVP, we only
* need to provide the title and optionally the description, but in the course of
* this tutorial you'll likely want to add more fields here.
*/
export interface CreateTaskRequest {
title: string;
description?: string;
}

export async function createTask(task: CreateTaskRequest): Promise<Task> {
const response = await post("/api/task", task);
const json = (await response.json()) as TaskJSON;

return parseTask(json);
}

export async function getAllTasks(): Promise<Task[]> {
return [];
}