Skip to content

Commit

Permalink
feat(access)!: add useAccess hook and accessApi layer with tests (
Browse files Browse the repository at this point in the history
#32)

* feat(`access`)!: add `useAccess` hook and `accessApi` layer with tests
- Define the main API interface with methods in the `accessApi` layer.
- Add request bundle handlers for each endpoint and implement `useAccess` to make API calls dynamically.
- Remove `API_BASE_URL` environment variable from `apiRequest` and add `url` as a parameter for the function.
- Update the location of the hooks folder in the test suite to match the app directory structure.
- Add `TypeDoc` to each file.
- Add tests for the `accessApi` layer and `useAccess` hook.

* test(`apiRequest`): update tests to reflect removal of `API_BASE_URL`
-  Adjust mock expectation to use direct URLs.

* fix: remove `/journal` prefix from `journal` request bundle

* Apply suggestions from code review
Co-authored-by: Ryan James Meneses <[email protected]>

* style: add `*/` to close `TypeDoc` comments

* docs: fix format
- Add newline to `register` docs.
- Reduce character count on `JournalBody`.

---------

Co-authored-by: Ryan James Meneses <[email protected]>
Co-authored-by: Ryan Meneses <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2025
1 parent 20c9c14 commit 5ca3dce
Show file tree
Hide file tree
Showing 6 changed files with 595 additions and 8 deletions.
353 changes: 353 additions & 0 deletions src/hooks/api/accessApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
import { Method } from '../apiRequest';
import { RequestBundle } from '../useAccess';

/**
* Main API interface defining all available endpoint methods
*
* @interface Api
*/
interface Api {
/**
* Manages journal operations
*
* @param method - HTTP method to be used
* @param journalId - Unique identifier for the journal
* @param body - Optional journal data
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
journal: (
method: Method,
journalId: string,
body?: JournalBody
) => RequestBundle;

/**
* Manages account operations
*
* @param method - HTTP method to be used
* @param journalId - Unique identifier for the journal context
* @param body - Account data to be processed
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
account: (
method: Method,
journalId: string,
body: AccountBody
) => RequestBundle;

/**
* Handles user login
*
* @param body - Login credentials
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
login: (body: LoginBody) => RequestBundle;

/**
* Handles token-based login
*
* @param body - Token login data
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
'token-login': (body: TokenLoginBody) => RequestBundle;

/**
* Initiates password recovery process
*
* @param body - Email information for password recovery
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
'forgot-password': (body: ForgotPasswordBody) => RequestBundle;

/**
* Handles password reset
*
* @param body - New password and reset token
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
'reset-password': (body: ResetPasswordBody) => RequestBundle;

/**
* Handles user logout
*
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
logout: () => RequestBundle;

/**
* Handles user registration
*
* @param body - Registration information
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
register: (body: RegisterBody) => RequestBundle;
}

/**
* Represents the body of a login request.
*
* @property email - The email address of the user.
* @property password - The password of the user.
* @property remember - Whether the user's session should be remembered.
*/
export interface LoginBody extends Record<string, unknown> {
email: string;
password: string;
remember: boolean;
}

/**
* Represents the body of a token-based login request.
*
* @property token - The token used for authentication.
*/
export interface TokenLoginBody extends Record<string, unknown> {
token: string;
}

/**
* Represents the body of an account update request.
*
* @property profile - Profile details of the user.
* @property profile.fname - First name of the user.
* @property profile.lname - Last name of the user.
* @property profile.email - Email address of the user.
* @property password - Password details of the user.
* @property password.oldPassword - The current password of the user.
* @property password.newPassword - The new password of the user.
* @property config - Configuration settings.
* @property config.model.chat - Chat model configuration.
* @property config.model.analysis - Analysis model configuration.
*/
interface AccountBody extends Record<string, unknown> {
profile: {
fname: string;
lname: string;
email: string;
};
password: {
oldPassword: string;
newPassword: string;
};
config: {
model: {
chat: string;
analysis: string;
};
};
}

/**
* Represents the body of a user registration request.
*
* @property fname - First name of the user.
* @property lname - Last name of the user.
* @property email - Email address of the user.
* @property password - The password for the new account.
*/
export interface RegisterBody extends Record<string, unknown> {
fname: string;
lname: string;
email: string;
password: string;
}

/**
* Represents the body of a forgot password request.
*
* @property email - The email address of the user requesting password recovery.
*/
export interface ForgotPasswordBody extends Record<string, unknown> {
email: string;
}

/**
* Represents the body of a reset password request.
*
* @property newPassword - The new password for the user's account.
* @property token - The token used to authorize the password reset.
*/
export interface ResetPasswordBody extends Record<string, unknown> {
newPassword: string;
token: string;
}

/**
* Represents the body of a journal entry.
*
* @property title - The title of the journal entry (optional).
* @property description - The description or content of the journal entry
* (optional).
*/
export interface JournalBody extends Record<string, unknown> {
title?: string;
description?: string;
}

/**
* Creates a request bundle for user login
*
* @param body - Login credentials and preferences
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const login = (body: LoginBody): RequestBundle => {
return {
endpoint: '/login',
options: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for token-based login
*
* @param body - Token login data
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const tokenLogin = (body: TokenLoginBody): RequestBundle => {
return {
endpoint: '/token-login',
options: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for user registration
*
* @param body - User registration information
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const register = (body: RegisterBody): RequestBundle => {
return {
endpoint: '/register',
options: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for journal operations
*
* @param method - HTTP method to be used
* @param journalId - Unique identifier for the journal
* @param body - Optional journal data
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const journal = (
method: Method,
journalId: string,
body?: JournalBody
): RequestBundle => {
const endpoint = `/${journalId}`;
return {
endpoint,
options: {
method,
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for account operations
*
* @param method - HTTP method to be used
* @param journalId - Unique identifier for the journal context
* @param body - Account data to be processed
* @throws {Error} When journal ID is not provided
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const account = (
method: Method,
journalId: string,
body: AccountBody
): RequestBundle => {
if (!journalId) {
throw new Error('Journal ID is required but not provided.');
}

return {
endpoint: `/${journalId}/account`,
options: {
method,
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for password recovery initiation
*
* @param body - User's email information
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const forgotPassword = (body: ForgotPasswordBody): RequestBundle => {
return {
endpoint: '/forgot-password',
options: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for password reset
*
* @param body - New password and reset token
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const resetPassword = (body: ResetPasswordBody): RequestBundle => {
return {
endpoint: '/reset-password',
options: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
},
};
};

/**
* Creates a request bundle for user logout
*
* @returns {RequestBundle} Bundle containing endpoint and request options
*/
const logout = (): RequestBundle => {
return {
endpoint: '/logout',
options: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
},
};
};

/**
* Object containing all available API endpoints for the components to
* reference.
*/
export const endpoints: Api = {
journal,
account,
login,
'token-login': tokenLogin,
'forgot-password': forgotPassword,
'reset-password': resetPassword,
logout,
register,
};
6 changes: 2 additions & 4 deletions src/hooks/apiRequest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

const API_BASE_URL = process.env.NEXT_PUBLIC_BACKEND_URL;
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

const DEFAULT_OPTIONS: ApiOptions = {
method: 'GET',
Expand Down Expand Up @@ -48,7 +46,7 @@ export const apiRequest = async <T>(
url: string,
options: ApiOptions = DEFAULT_OPTIONS
): Promise<T> => {
const response = await fetch(`${API_BASE_URL}${url}`, {
const response = await fetch(url, {
...(options as RequestInit),
body: options.body && JSON.stringify(options.body),
});
Expand Down
Loading

0 comments on commit 5ca3dce

Please sign in to comment.