Backend - liferesearchapp/life-research-members-portal GitHub Wiki

Overview

This document relates to the files within src/pages/api which contain the functions that handle HTTP requests and execute queries on the database.

See Prisma for details on making database queries.

See Authentication for an explanation of getAccountFromRequest and details on authorizing users.

Next.js API Routes

This is a Next.js application, which creates a Node.js web server.

That server will serve the frontend React application to clients, and can also handle HTTP requests using Next.js API Routes

Next.js allows creating API endpoints by simply exporting a default function within the src/pages/api directory.

The endpoint is then created according to the file name.

For example, exporting a default function from src/pages/api/register-account.ts creates an endpoint at <domain root>/api/register-account which calls that function when it receives an HTTP request, passing the request and response objects as a parameter.

The frontend will be making calls to these URLs repeatedly, so to avoid hardcoding the URLs in multiple places, they are housed in an object at src/routing/api-routes.ts. This object needs to be updated if file names are modified, added, or deleted within src/pages/api.

API Route Examples

The request body is accessible through req.body.

Example (Type-checking and error catching has been removed for brevity):

src/pages/api/register-account.ts

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<RegisterAccountRes | string>
) {
  const params: RegisterAccountParams = req.body;
  const { login_email, first_name, last_name, is_admin } = params;

  const currentUser = await getAccountFromRequest(req, res);
  if (!currentUser) return;

  if (!currentUser.is_admin)
    return res.status(401).send("You are not authorized to register accounts.");

  const newUser = await registerAccount(params);
  return res.status(200).send(newUser);
}

Parameterized routes can be created using square brackets in the file name. For example exporting a function from src/pages/api/account/[id].ts allows calling an endpoint like <domain root>/api/account/123 where the parameter id is 123.

The parameter can then be accessed using req.query.<paramName>.

Example (Type-checking and error catching has been removed for brevity):

src/pages/api/account/[id].ts

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<AccountDBRes | string>
) {
  const id = parseInt(req.query.id);

  const currentAccount = await getAccountFromRequest(req, res);
  if (!currentAccount) return;

  const authorized = currentAccount.is_admin || currentAccount.id === id;
  if (!authorized)
    return res
      .status(401)
      .send("You are not authorized to view this account information.");

  const account = await getAccountById(id);
  if (!account) return res.status(400).send("Account not found. ID: " + id);

  return res.status(200).send(account);
}

Member API Reference Examples

Here is a table of all API routes, their function, the expected contents of the request body, whether or not the access token needs to be passed in an authorization header, and what users are authorized to call the endpoint:

Notes:

  • These API routes were designed with a small database in mind, they do not have limits to the number of rows they return.
  • Admins are prevented from performing actions that may make their own account inaccessible. This ensures there is always at least one admin account.
Name Route Function Expected Body Auth Header Authorized Users
All Members /api/all-members Retrieves all active members and their public info None No Admins, Members
All Accounts /api/all-accounts Retrieves all accounts and all connected data None Yes Admins
Register Account /api/register-account Registers an account { login_email: string; first_name: string; last_name: string; is_admin?: boolean; is_member?: boolean; } Yes Admins
Active Account /api/active-account Retrieves an account and all connected data using an access token None Yes Admins, Members
Active Account Update Last Login /api/active-account Like Active Account but also updates the last login of the account to the current time None Yes Admins, Members
Account /api/account/[id] Retrieves an account and all connected data given the account id None Yes Admins, Account with matching account id
Public Member Info /api/member/[id]/public Retrieves public member information given a member id None No Admins, Members
Private Member Info /api/member/[id]/private Retrieves private member information given a member id None Yes Admins, Account with matching member id
Delete Account /api/delete-account/[id] Deletes an account given the account id None Yes Admins with account id NOT matching given id
Update Account Name /api/update-account/[id]/name Updates first and last name of an account given the account id { first_name?: string; last_name?: string } Yes Admins
Update Account Email /api/update-account/[id]/email Updates email of an account given the account id { login_email: string } Yes Admins with account id NOT matching given id
Update Account Grant Admin /api/update-account/[id]/grant-admin Grants admin privileges to an account given the account id None Yes Admins
Update Account Remove Admin /api/update-account/[id]/remove-admin Removes admin privileges of an account given the account id None Yes Admins with account id NOT matching given id
Update Account Register Member /api/update-account/[id]/register-member Registers this account as a member (creates entry in member table and assigns member id) None Yes Admins, Accounts with matching account id
Update Account Delete Member /api/update-account/[id]/delete-member Deletes an account's information from the member table given an account id None Yes Admins
Update Member Public /api/update-member/[id]/public Updates a member's public information given a member id {first_name?: string; last_name?: string; about_me_en: string; about_me_fr: string; faculty_id?: number | null; type_id?: number | null; work_email?: string; work_phone?: string; website_link?: string; twitter_link?: string; linkedin_link?: string; cv_link?: string; facebook_link?: string; tiktok_link?: string; deleteProblems?: number[]; addProblems?: {name_en: string | null; name_fr: string | null;}[]; deleteKeywords?: number[]; addKeywords?: number[];} Yes Admins, Account with matching member id
Update Member Private /api/update-member/[id]/private Updates a member's private information given a member id { address?: string; city?: string; province?: string; country?: string; postal_code?: string; mobile_phone?: string; date_joined?: string | null; activate?: boolean; deactivate?: boolean;} Yes Admins, Account with matching member id
Update Member Insight /api/update-member/[id]/insight Updates a member's insight information given a member id {interview_date?: string | null; about_member?: string; about_promotions?: string; dream?: string; how_can_we_help?: string; admin_notes?: string; other_notes?: string;} Yes Admins, Account with matching member id
Update Keyword /api/update-keyword/[id] Updates a keyword given a keyword id {name_en: string | null; name_fr: string | null;} Yes Admins, Members
Register Keyword /api/register-keyword Registers a new keyword { name_en: string; name_fr: string } Yes Admins, Members
All Keywords /api/all-keywords Gets all keywords None No Admins, Members
All Faculties /api/all-faculties Gets all faculties None No Admins, Members
All Member Types /api/all-member-types Gets all member types None No Admins, Members
All Institutes /api/all-institutes Retrieves all institutes and their details None No Admins, Super Admins
Register Institute /api/register-institute Registers a new institute { name: string; urlIdentifier: string; description_en?: string; description_fr?: string; } Yes Super Admins
Member Institute /api/member-institute Retrieves all institutes associated with a member None Yes Admins, Super Admins, Members
Add Institute /api/update-account/{id}/add-institute Adds an institute to a specific member/account { instituteId: number; } Yes Super Admins, Admins
Remove Institute /api/update-account/{id}/remove-institute Removes an institute from a specific member/account { instituteId: number; } Yes Super Admins, Admins

Event journey

First of all, the code has been modified to add the "Journey of Event" button at /src/components/events/event-private-profile.tsx as shown in the figure below:

image

We added a few lines of code to add the button at /src/components/events/event-private-profile.tsx shown in the figure below:

image image

components/journey

In this folder, we developed our coding for the event's journey, which performs the hierarchical view of an event at /src/components/journey/event-privat-profile.tsx

In this part of coding, we have created several components to get the tree view. Details of the components with code have been described below:

ExpandableMainEvent

This part is responsible for rendering and managing the display of event details, along with a hierarchy of child events. Let's break down the code step by step:

The component is accepting a single prop named event, which is expected to be of type EventPrivateInfo.

Inside the component function, two pieces of state are defined using the useState hook:

expandedEventDetails: Tracks whether the detailed information about the event is expanded or collapsed.

expandedEvent: Tracks whether the hierarchy of child events is expanded or collapsed.

Two event handler functions are defined:

handleEventDetailsToggle: Toggles the value of expandedEventDetails when the event details toggle is clicked.

handleEventToggle: Toggles the value of expandedEvent when the event toggle is clicked.

The component renders a main container that holds the event-related content. Inside this container, there are multiple nested elements:

A row container for the expand/collapse icons and event name. The first icon, or , toggles the visibility of child events based on the expandedEvent state. The second icon, or , toggles the visibility of detailed event information based on the expandedEventDetails state. The event name, event type, start date, and event length are displayed. Within the main container, an if condition checks whether expandedEventDetails is true. If it is, detailed event information is displayed.

The ExpandableMember, ExpandablePartner, ExpandableGrant, and ExpandableProduct components are conditionally rendered, displaying information about involved members, partners, grants, and products if they exist for the event.

   {expandedEventDetails && (
          <div style={{ marginLeft: "50px" }}>
            {event.event_member_involved &&
              event.event_member_involved.length > 0 && (
                <ExpandableMember eventId={event.id} />
              )}
            {event.event_partner_involved &&
              event.event_partner_involved.length > 0 && (
                <ExpandablePartner eventId={event.id} />
              )}
            {event.event_grant_resulted &&
              event.event_grant_resulted.length > 0 && (
                <ExpandableGrant eventId={event.id} />
              )}
            {event.event_product_resulted &&
              event.event_product_resulted.length > 0 && (
                <ExpandableProduct eventId={event.id} />
              )}

Overall, the ExpandableMainEvent component encapsulates the display and interaction logic for event details and their hierarchical child events, allowing users to expand or collapse different sections to gain insights into event-related information.

ExpandableNextMainEvent

Let's break down the code for the ExpandableNextMainEvent component step by step:

Component Props:

The component ExpandableNextMainEvent is designed to accept a single prop named eventId, which is expected to be of type number or null.

Local State Management:

expandedNextEvents: This state variable tracks whether the hierarchy of child events for the current event is expanded or collapsed. expandedNextEventDetails: This state variable tracks whether the detailed information about the current event is expanded or collapsed. eventData: This state variable holds the fetched event data. It starts with an initial value of null.

Event Handlers:

handleNextEventToggle: This function toggles the value of expandedNextEvents when the user clicks on the expand/collapse icon for child events. handleNextEventDetailToggle: This function toggles the value of expandedNextEventDetails when the user clicks on the expand/collapse icon for event details.

UseEffect for Fetching Event Data:

A useEffect hook is used to fetch the event data using the fetchEventId function. It triggers whenever the eventId changes. If eventId is not null, the event data is fetched, and the eventData state is updated with the fetched data. If there's an error during the fetch, it's logged to the console.

Conditional Rendering of Loading State:

If eventData is null, meaning the data is still being fetched, the component returns null, indicating that nothing should be rendered yet.

Conditional Rendering of Event Details:

If expandedNextEventDetails is true, detailed event information is displayed. Similar to the previous component, the ExpandableMember, ExpandablePartner, ExpandableGrant, and ExpandableProduct components are conditionally rendered.

Recursive Rendering for Nested Events:

If expandedNextEvents is true, a recursive rendering loop iterates over the nested child events of the current event. For each child event, an ExpandableNextMainEvent component is rendered, allowing for the hierarchical display of nested events. The style with marginLeft is used to differentiate nested levels of events visually.

      {expandedNextEvents &&
          eventData.event_next_event_event_next_event_event_idToevent.map(
            (nextEvent) => (
              <div key={nextEvent.next_event_id} style={{ marginLeft: "70px" }}>
                <ExpandableNextMainEvent eventId={nextEvent.next_event_id} />
              </div>
            )
          )}

ExpandableMember

ExpandableMember handles the rendering of members involved in an event, with an option to expand or collapse the list of members. Here's a concise explanation:

Component Props:

The component ExpandableMember takes a single prop eventId of type number, representing the unique identifier of the event.

Local State Management:

eventMember: Holds an array of event member data or null if not yet fetched. expanded: Tracks whether the list of event members is expanded or collapsed.

Fetching Event Member Data:

The useEffect hook fetches event member data using the fetchEventId function. Upon successful fetch, the event members are stored in the eventMember state. If there's an error during the fetch, it's logged to the console.

Conditional Rendering of Event Members:

An expand/collapse icon, or , is displayed next to the text "Member Involved." The list of event members is rendered, displaying up to two members if not expanded, or all members if expanded. Each member's name is linked to their profile page and separated by commas. If there are no members involved, a message is displayed accordingly.

View More / View Less Toggle:

If the total count of event members is more than two, a "View more..." or "View less..." link is shown, allowing users to toggle between displaying all members or a limited number.

In summary, the ExpandableMember component handles the display of event members and provides an interactive way for users to expand or collapse the list of members, ensuring a user-friendly and informative experience.

ExpandablePartner

ExpandablePartner is designed to manage the display of event partners' information with the option to expand or collapse the partner list. Here are the key points of the code:

Component Props:

The ExpandablePartner component accepts a single prop named eventId, which should be a number representing the unique identifier of the event.

Local State Management:

eventPartner: This state variable holds an array of event partner data or null if data hasn't been fetched yet. expanded: This state variable tracks whether the partner list is expanded or collapsed. Event Handler:

handleToggle: This function is triggered when the user clicks on the container. It toggles the value of expanded, controlling whether the full partner list is displayed or a shortened version.

Fetching Event Partner Data:

The useEffect hook fetches event partner data using the fetchEventId function (assuming it's available and retrieves data based on eventId). After fetching, the partner data is stored in the eventPartner state. If there's an error during the fetch, an error message is logged to the console. Logging Partner Data:

The code logs the eventPartner data to the console. This could help during development and debugging.

Conditional Rendering of Event Partners:

An expand/collapse icon, or , is displayed next to the text "Event Partner." The list of event partners is rendered, showing all partners if expanded, or a limited number (up to two) if collapsed. Each partner's name is linked to their profile page and separated by commas.

View More / View Less Toggle:

If the total count of event partners is more than two, a "View more..." or "View less..." link is shown. Users can toggle between displaying all partners or a limited number.

In summary, the ExpandablePartner component manages the presentation of event partners' information, giving users the ability to expand or collapse the partner list. This approach makes it easy for users to access a summarized version of the partner list or explore more details when needed.

ExpandableGrant

ExpandableGrant is responsible for displaying information about grants related to an event. The component offers the ability to expand or collapse the list of grants along with their details. Here's the main point of the code:

Component Props:

The ExpandableGrant component takes a single prop named eventId, which is expected to be a number representing the unique identifier of the event.

Local State Management:

eventGrant: Holds an array of grant-related data or null if data hasn't been fetched yet. eventStatus: Holds an array of grant status data or null. expanded: Tracks whether the grant list is expanded or collapsed.

Event Handler:

handleToggle: Toggles the value of expanded when the user clicks on the container. This controls whether the full grant list is displayed or a limited version.

Fetching Event Grant Data:

The first useEffect hook fetches grant-related data using the fetchEventId function based on the provided eventId. The fetched grant data is stored in the eventGrant state. If there's an error during the fetch, an error message is logged to the console.

Conditional Rendering of Event Grants:

An expand/collapse icon, or , is displayed next to the text "Grant." The list of event grants is rendered, showing all grants if expanded or a limited number (up to two) if collapsed. Grant details are displayed, including grant status, amount, and title, with appropriate formatting and links.

ExpandableProduct

ExpandableProduct that handles the display of products related to an event. It provides the option to expand or collapse the list of products along with their details. Here's the main point of the code:

Component Props: The ExpandableProduct component takes a single prop named eventId, which is expected to be a number representing the unique identifier of the event.

Local State Management:

product: Holds an array of product-related data or null if data hasn't been fetched yet. expanded: Tracks whether the product list is expanded or collapsed. productType: Holds an array of product type data or null.

Event Handler:

handleToggle: Toggles the value of expanded when the user clicks on the container. This controls whether the full product list is displayed or a limited version.

Fetching Event Product Data:

The first useEffect hook fetches product-related data using the fetchEventId function based on the provided eventId. The fetched product data is stored in the product state. If there's an error during the fetch, an error message is logged to the console.

Fetching Product Type Data:

The second useEffect hook fetches product type data based on the data retrieved in the previous step. If product is not empty and contains at least one product, it fetches the type of the first product (assuming there's a .product.product_type_id field in the data). The fetched product type data is stored in the productType state. If there's an error during the fetch, an error message is logged to the console.

Conditional Rendering of Event Products:

An expand/collapse icon, or , is displayed next to the text "Product." The list of event products is rendered, showing the product type and title of the first product, with a link to its profile if available. If there's more than one product, a "View more..." link is displayed when collapsed, allowing users to expand the list.

In summary, the ExpandableProduct component effectively handles the presentation of products related to an event. It allows users to either get a concise overview or delve into the details of the products based on their preferences, thereby enhancing the user experience.

API Call

We have made a few API calls as follows:

fetchEventTypeById

async function fetchEventTypeById(eventTypeId: number)

fetchEventTypeById that fetches event type data from an API endpoint based on the given eventTypeId. Here's a concise description of what the code does:

Function Purpose:

This function is used to fetch and retrieve the name of an event type using its unique identifier (eventTypeId).

Function Execution:

The function initiates an HTTP request to the API endpoint /api/all-event-types. If the response from the API is not successful (HTTP status code other than 200), it throws an error indicating the failure to fetch event type data.

Data Processing:

If the API call is successful, the response data is parsed as JSON (eventTypeData). The function searches for an event type in the eventTypeData array that matches the provided eventTypeId. If the event type is not found, it throws an error indicating that the event type was not found for the given eventTypeId.

Data Return:

If an event type matching the eventTypeId is found, the function returns the name of the event type, based on the language preference (en for English and fr for French). The appropriate name is extracted from the found event type object.

fetchEventId

async function fetchEventId(eventId: number)

fetchEventId that fetches event data from an API endpoint based on the given eventId. Here's a brief description of its functionality:

Function Purpose:

This function is used to fetch event data using a provided event identifier (eventId).

Function Execution:

The function initiates an HTTP request to the API endpoint /api/all-events/. If the API response is not successful (HTTP status code other than 200), it throws an error indicating the failure to fetch event data.

Data Processing:

Upon a successful API response, the response data is parsed as JSON (eventData). The function searches through the eventData array to find an event object that matches the provided eventId.

Data Return:

If an event with the matching eventId is found, the function returns the complete event object.

fetchGrantStatus

async function fetchGrantStatus(eventId: number) fetchGrantStatus that fetches grant status data from an API endpoint based on the given eventId. Here's a concise explanation of its functionality:

Function Purpose:

This function is used to fetch and retrieve the name of a grant status using the provided eventId.

Function Execution:

The function initiates an HTTP request to the API endpoint /api/all-statuses. If the response from the API is not successful (HTTP status code other than 200), it throws an error indicating the failure to fetch grant status data.

Data Processing:

Upon a successful API response, the response data is parsed as JSON (eventTypeData). The function searches for a grant status in the eventTypeData array that matches the provided eventId.

Data Return:

If a grant status with the matching eventId is found, the function returns the name of the grant status based on the language preference (en for English and fr for French). The appropriate name is extracted from the found grant status object.

fetchProductType

async function fetchProductType(productTypeId: number) fetchProductType that fetches product type data from an API endpoint based on the given productTypeId. Here's a brief explanation of its functionality:

Function Purpose:

This function is used to fetch and retrieve the name of a product type using the provided productTypeId.

Function Execution:

The function initiates an HTTP request to the API endpoint /api/all-product-types. If the response from the API is not successful (HTTP status code other than 200), it throws an error indicating the failure to fetch product type data.

Data Processing:

Upon a successful API response, the response data is parsed as JSON (productType). The function searches for a product type in the productType array that matches the provided productTypeId.

Data Return:

If a product type with the matching productTypeId is found, the function returns the name of the product type, based on the language preference (en for English and fr for French). The appropriate name is extracted from the found product type object.

⚠️ **GitHub.com Fallback** ⚠️