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

Shiksha 2.0 Auth login + content list. #46

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

sagarkoshti1990
Copy link

@sagarkoshti1990 sagarkoshti1990 commented Mar 7, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced the login flow with improved error reporting and additional authentication options.
    • Introduced a new Login component for user authentication, featuring a responsive design and improved error handling.
    • Implemented environment-based configuration for asset and registration paths, offering greater deployment flexibility.
  • Refactor

    • Streamlined component logic by removing unnecessary state management and updating API endpoints across modules to align with newer versions.
  • Style

    • Adjusted visual elements such as typography and layout for a cleaner, more consistent user interface.

Copy link

coderabbitai bot commented Mar 7, 2025

📝 Walkthrough

Walkthrough

The changes remove an unnecessary loading state in the Shiksha app’s authentication component and introduce dynamic configuration for base paths in both content and registration modules by leveraging environment variables. Several API endpoints are updated to new versions and now include dynamic headers for tenant IDs and authorization tokens. The content page adjusts UI elements and replaces hardcoded constants with imported ones. Furthermore, the registration module undergoes significant refactoring with enhanced login logic, improved error handling, and the addition of comprehensive authentication services.

Changes

File(s) Change Summary
apps/shiksha-app/src/app/page.tsx Removed useState for loading state in AuthCheck; kept redirection logic intact.
mfes/content/next.config.js, mfes/registration/next.config.js Introduced dynamic basePath constants using environment variables instead of hardcoded strings.
mfes/content/src/pages/content.tsx Imported AppConst; commented out contentReadAPI; updated image source, text color, and API URL in fetchFramework.
mfes/content/src/utils/AppConst/AppConst.ts, mfes/registration/src/utils/AppConst/AppConst.ts Added new AppConst objects deriving BASEPATH from environment variables.
mfes/content/src/services/Read.ts, mfes/content/src/services/Search.ts, mfes/players/src/services/PlayerService.ts, mfes/players/src/utils/url.config.ts Updated API endpoints to version 3; added dynamic headers (tenantId, Authorization) in Axios requests.
mfes/registration/src/app/login/page.tsx, mfes/registration/src/app/shiksha-login/page.tsx, mfes/registration/src/services/LoginService.tsx Refactored login flow by replacing token fetching with a new login call, enhanced error handling (now using an array of messages), and added multiple authentication methods (login, refresh, logout, password resets).

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant LC as Login Component
    participant LS as Login Service
    participant API as Auth API

    U->>LC: Enter credentials and submit
    LC->>LS: Invoke login({username, password})
    LS->>API: Send POST request with credentials
    API-->>LS: Return access token and user details
    LS-->>LC: Return {result, authUser}
    alt Valid Token & Tenant
        LC->>U: Redirect to destination
    else Error Occurred
        LC->>U: Display error messages
    end
Loading

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error code ERESOLVE
npm error ERESOLVE could not resolve
npm error
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/eslint
npm error dev eslint@"^9.8.0" from the root project
npm error peer eslint@"^6.0.0 || ^7.0.0 || >=8.0.0" from @eslint-community/[email protected]
npm error node_modules/@eslint-community/eslint-utils
npm error @eslint-community/eslint-utils@"^4.4.0" from @typescript-eslint/[email protected]
npm error node_modules/@typescript-eslint/utils
npm error @typescript-eslint/utils@"^8.0.0" from @nx/[email protected]
npm error node_modules/@nx/eslint-plugin
npm error dev @nx/eslint-plugin@"20.2.2" from the root project
npm error 3 more (@typescript-eslint/eslint-plugin, ...)
npm error @eslint-community/eslint-utils@"^4.2.0" from [email protected]
npm error 1 more (@typescript-eslint/utils)
npm error 18 more (@eslint/compat, @nx/eslint, @nx/eslint, @nx/eslint, ...)
npm error
npm error Could not resolve dependency:
npm error peer eslint@"^7.23.0 || ^8.0.0" from [email protected]
npm error node_modules/eslint-config-next
npm error dev eslint-config-next@"14.2.16" from the root project
npm error
npm error Conflicting peer dependency: [email protected]
npm error node_modules/eslint
npm error peer eslint@"^7.23.0 || ^8.0.0" from [email protected]
npm error node_modules/eslint-config-next
npm error dev eslint-config-next@"14.2.16" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /.npm/_logs/2025-03-07T14_07_38_630Z-eresolve-report.txt
npm error A complete log of this run can be found in: /.npm/_logs/2025-03-07T14_07_38_630Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81002da and 67dec19.

📒 Files selected for processing (3)
  • mfes/content/src/pages/content.tsx (6 hunks)
  • mfes/content/src/services/Search.ts (2 hunks)
  • mfes/registration/src/app/login/page.tsx (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • mfes/content/src/services/Search.ts
  • mfes/registration/src/app/login/page.tsx
  • mfes/content/src/pages/content.tsx

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/shiksha-app/src/app/page.tsx (1)

18-22: 🛠️ Refactor suggestion

Remove TypeScript ignore comments

The code uses @ts-ignore to suppress TypeScript errors. Consider properly typing the environment variables or handling potential undefined values instead of ignoring type errors.

if (accToken && refToken) {
-  //@ts-ignore
-  router.replace(URL_CONTENT); // Redirect if tokens are present
+  router.replace(URL_CONTENT || '/content'); // Redirect if tokens are present
} else {
-  //@ts-ignore
-  router.replace(URL_LOGIN); // Redirect to login if missing
+  router.replace(URL_LOGIN || '/login'); // Redirect to login if missing
}
🧹 Nitpick comments (13)
mfes/content/src/services/Search.ts (1)

155-160: Consider adding authentication headers

Other services in this PR (Read.ts, PlayerService.ts) now include authentication headers with tenantId and access token. For consistency, consider adding similar headers to this service as well.

const config: AxiosRequestConfig = {
  method: 'post',
  maxBodyLength: Infinity,
  url: `${searchApiUrl}/interface/v1/action/composite/v3/search`,
  data: data,
+  headers: {
+    tenantId: localStorage.getItem('tenantId') || '',
+    Authorization: `Bearer ${localStorage.getItem('accToken') || ''}`,
+  },
};
mfes/content/src/services/Read.ts (1)

126-127: Improve error handling for missing authentication

The code currently defaults to an empty string when tokens aren't available. Consider adding explicit error handling when authentication tokens are missing, especially since this is a protected endpoint.

const config: AxiosRequestConfig = {
  method: 'get',
  maxBodyLength: Infinity,
  url: `${searchApiUrl}/interface/v1/action/content/v3/read/` + doId,
  headers: {
-    tenantId: localStorage.getItem('tenantId') || '',
-    Authorization: `Bearer ${localStorage.getItem('accToken') || ''}`,
+    tenantId: localStorage.getItem('tenantId') || (() => { throw new Error('Missing tenantId in localStorage'); })(),
+    Authorization: `Bearer ${localStorage.getItem('accToken') || (() => { throw new Error('Missing accToken in localStorage'); })()}`,
  },
};
mfes/players/src/services/PlayerService.ts (1)

15-16: Consider handling browser environment safely

The localStorage API is only available in browser environments. While this code likely runs in the browser, it's a good practice to add a safety check, especially if there's any possibility of this being executed in a server context.

headers: {
-  tenantId: localStorage.getItem('tenantId') || '',
-  Authorization: `Bearer ${localStorage.getItem('accToken') || ''}`,
+  tenantId: (typeof window !== 'undefined' ? localStorage.getItem('tenantId') : null) || '',
+  Authorization: `Bearer ${(typeof window !== 'undefined' ? localStorage.getItem('accToken') : null) || ''}`,
},
apps/shiksha-app/src/app/page.tsx (1)

12-25: Consider adding state to handle router transition

Even though the loading state was removed, the component still shows a loading indicator unconditionally. Consider adding a state to manage the loading status based on the router transition or add a timeout to ensure the indicator doesn't show indefinitely.

mfes/registration/src/app/shiksha-login/page.tsx (1)

233-237: Error alert implementation

The error alert implementation provides clear feedback to users when login fails. Consider adding a timeout to automatically dismiss the error after a few seconds.

mfes/registration/src/services/LoginService.tsx (8)

38-60: Consider using consistent HTTP client methods.

The function uses both the shared library post method and direct axios.get within the same workflow. Consider using the shared library's get method (which is imported but not used) for the second request to maintain consistency.

Also, consider adding a specific return type instead of Promise<any> for better type safety.

- export const login = async ({
+ export const login = async ({
  username,
  password,
}: LoginParams): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/auth/login`;

  try {
    const response = await post(apiUrl, { username, password });
-   const response2 = await axios.get(
+   const response2 = await get(
      `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/auth`,
-     {
-       headers: {
-         Authorization: `Bearer ${response?.data?.result?.access_token}`,
-       },
-       withCredentials: true,
-     }
+     { 
+       headers: {
+         Authorization: `Bearer ${response?.data?.result?.access_token}`,
+       },
+       withCredentials: true,
+     }
    );
    return { ...response?.data, authUser: response2?.data?.result };
  } catch (error) {
    console.error('error in login', error);
    throw error;
  }
};

62-73: Fix error message in the refresh function.

The error message incorrectly refers to "login" instead of "refresh". Update it to accurately reflect the function context.

export const refresh = async ({
  refresh_token,
}: RefreshParams): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/auth/refresh`;
  try {
    const response = await post(apiUrl, { refresh_token });
    return response?.data;
  } catch (error) {
-   console.error('error in login', error);
+   console.error('error in refresh', error);
    throw error;
  }
};

75-84: Inconsistency in return value from logout function.

Unlike other functions that return response?.data, this function returns the entire response object. Consider updating for consistency.

export const logout = async (refreshToken: string): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/auth/logout`;
  try {
    const response = await post(apiUrl, { refresh_token: refreshToken });
-   return response;
+   return response?.data;
  } catch (error) {
    console.error('error in logout', error);
    throw error;
  }
};

86-95: Improve type safety in resetPassword function.

The parameter newPassword is typed as any, which reduces type safety. Consider defining a more specific type.

-export const resetPassword = async (newPassword: any): Promise<any> => {
+export const resetPassword = async (newPassword: string): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/reset-password`;
  try {
    const response = await post(apiUrl, { newPassword });
    return response?.data;
  } catch (error) {
    console.error('error in reset', error);
    throw error;
  }
};

97-109: Improve type safety in forgotPasswordAPI function.

Both parameters newPassword and token are typed as any, which reduces type safety. Consider defining more specific types.

-export const forgotPasswordAPI = async (
-  newPassword: any,
-  token: any
+export const forgotPasswordAPI = async (
+  newPassword: string,
+  token: string
): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/forgot-password`;
  try {
    const response = await post(apiUrl, { newPassword, token });
    return response?.data;
  } catch (error) {
    console.error('error in reset', error);
    throw error;
  }
};

111-124: Improve type safety in resetPasswordLink function.

The parameter username is typed as any, which reduces type safety. Consider defining a more specific type.

-export const resetPasswordLink = async (username: any): Promise<any> => {
+export const resetPasswordLink = async (username: string): Promise<any> => {
  const apiUrl = `${process.env.NEXT_PUBLIC_MIDDLEWARE_URL}/user/v1/password-reset-link`;
  try {
    let redirectUrl = process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || '';
    if (redirectUrl === '' && typeof window !== 'undefined') {
      redirectUrl = window.location.origin;
    }
    const response = await post(apiUrl, { username, redirectUrl });
    return response?.data;
  } catch (error) {
    console.error('error in reset', error);
    throw error;
  }
};

126-141: Consider removing or properly documenting commented-out code.

If the successfulNotification function is intended for future use, consider adding a TODO comment explaining its purpose and when it will be implemented. If it's no longer needed, consider removing it entirely.

Additionally, the commented code contains a console.log(email) statement which would log potentially sensitive information. This should be removed if the function is implemented.


1-142: Consider API versioning consistency across endpoints.

There's inconsistency in API versioning across the different endpoints. Some include /v1/ in the path while others do not. Consider standardizing the API versioning approach for better maintainability.

Examples:

  • /user/auth/login (no version)
  • /user/auth (no version)
  • /user/v1/auth/refresh (includes version)
  • /user/v1/auth/logout (includes version)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f849adc and 81002da.

📒 Files selected for processing (13)
  • apps/shiksha-app/src/app/page.tsx (1 hunks)
  • mfes/content/next.config.js (2 hunks)
  • mfes/content/src/pages/content.tsx (6 hunks)
  • mfes/content/src/services/Read.ts (1 hunks)
  • mfes/content/src/services/Search.ts (1 hunks)
  • mfes/content/src/utils/AppConst/AppConst.ts (1 hunks)
  • mfes/players/src/services/PlayerService.ts (1 hunks)
  • mfes/players/src/utils/url.config.ts (1 hunks)
  • mfes/registration/next.config.js (2 hunks)
  • mfes/registration/src/app/login/page.tsx (8 hunks)
  • mfes/registration/src/app/shiksha-login/page.tsx (1 hunks)
  • mfes/registration/src/services/LoginService.tsx (2 hunks)
  • mfes/registration/src/utils/AppConst/AppConst.ts (1 hunks)
🔇 Additional comments (26)
mfes/content/src/utils/AppConst/AppConst.ts (1)

1-5: Well-structured constant for configuration management

Creating a centralized constant object for configuration values is a good practice. Using environment variables with fallbacks improves deployment flexibility across different environments.

mfes/registration/src/utils/AppConst/AppConst.ts (1)

1-5: Good approach to consistent configuration management

This follows the same pattern as the content module, which ensures consistency across modules. The use of environment variables with fallbacks is a good practice for deployment flexibility.

mfes/content/next.config.js (2)

7-7: Proper environment variable usage for basePath configuration

Using the same environment variable as in AppConst ensures consistency across the application. The fallback value provides a sensible default when the environment variable is not set.


29-29: Good refactoring to use the dynamic basePath constant

Replacing the hardcoded string with the dynamic basePath variable improves maintainability and configuration flexibility.

mfes/registration/next.config.js (2)

5-6: Well-structured constant for basePath configuration

Using a constant with environment variable support is good practice. The variable name is clear and descriptive.


18-18: Good implementation of dynamic basePath configuration

Using the constant instead of a hardcoded string improves maintainability and deployment flexibility.

mfes/content/src/services/Read.ts (1)

124-128: API endpoint updated with proper authentication

Good changes:

  1. Updated API endpoint from v1 to v3
  2. Added dynamic authentication using tokens from localStorage
  3. Added tenantId header for proper request context
mfes/players/src/services/PlayerService.ts (1)

11-19: Authentication headers added to API request

Good improvement adding authentication headers to the content fetch request. This makes the API call more secure and follows the same pattern as other services.

apps/shiksha-app/src/app/page.tsx (1)

3-3: Removed useState import

Removing the unused useState import is good for keeping imports clean.

mfes/players/src/utils/url.config.ts (1)

9-9: API endpoint updated to new version

The CONTENT_READ URL has been updated from v1 to v3, with a change in path structure. This update aligns with newer API versioning and likely includes updated functionality.

mfes/content/src/pages/content.tsx (5)

41-41: Good use of imported constants

Adding the AppConst import is a good practice for maintaining configuration values in a central location.


298-298: Improved path management with constants

Using AppConst.BASEPATH instead of hardcoded paths improves maintainability and ensures consistent path handling across environments.


324-324: UI enhancement: Typography color changed

Changed color from textSecondary to black for better readability.


415-415: Updated framework API endpoint

The framework API URL has been updated to include an additional path segment. Ensure this matches the expected API structure.


252-252:

❓ Verification inconclusive

Verify if commenting out contentReadAPI is intentional

The API call to contentReadAPI(identifier) has been commented out. Ensure this is intentional and won't affect the content loading functionality.


🏁 Script executed:

#!/bin/bash
# Check if contentReadAPI is used elsewhere in the codebase
rg "contentReadAPI\(" --type ts

Length of output: 113


Attention: Confirm Intent of Disabled API Call

  • The search shows that the only occurrence of contentReadAPI(identifier) is the commented-out call in mfes/content/src/pages/content.tsx (line 252).
  • Since it isn’t referenced anywhere else, please verify that commenting it out is intentional. If disabling it is desired, consider removing the commented code for clarity; if not, please reinstate the call to ensure proper content loading.
mfes/registration/src/app/shiksha-login/page.tsx (4)

1-15: Well-structured imports for the login component

The imports are well organized, including Material UI components, routing utilities, and authentication services.


16-43: Solid state management for login form

The component implements proper state management for form data, errors, and UI states like password visibility and loading indicators.


56-92: Comprehensive login flow with error handling

The login flow includes proper validation, loading state management, and error handling. The JWT token is properly stored in localStorage and the subscription ID is extracted and set as a cookie.


130-134: Dynamic image path using AppConst

Using AppConst.BASEPATH for the image source ensures the path works correctly across different deployment environments.

mfes/registration/src/app/login/page.tsx (6)

13-14: Updated authentication service import

Changed from getToken to login, which likely provides more comprehensive authentication functionality.


28-28: Improved error handling with array-based messages

Changed error message type from string to string array to support multiple error messages, which enhances the user experience.


65-73: Enhanced authentication with tenant validation

The updated login process now includes tenant validation and stores the tenant ID in localStorage, which improves multi-tenant support.


82-87: More specific error messages for authentication failures

Added specific error messages for different authentication failure scenarios (invalid token vs. invalid tenant ID), improving the user experience.


132-132: Consistent path management using AppConst

Using AppConst.BASEPATH for image paths ensures consistency across different deployment environments.


235-243: Enhanced error display with structured formatting

The updated error display presents multiple error messages in a formatted list with an "Error" title, making them more readable for users.

mfes/registration/src/services/LoginService.tsx (1)

34-36: Clean interface definition for RefreshParams.

The interface is well-defined with the appropriate typing for the refresh token.

@@ -155,7 +155,7 @@ export const ContentSearch = async (
const config: AxiosRequestConfig = {
method: 'post',
maxBodyLength: Infinity,
url: `${searchApiUrl}/api/content/v1/search`,
url: `${searchApiUrl}/interface/v1/action/composite/v3/search`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

API endpoint updated to v3

The URL has been updated from /api/content/v1/search to /interface/v1/action/composite/v3/search, indicating a version upgrade. This change aligns with similar updates in other files.


🏁 Script executed:

#!/bin/bash
# Verify if this new API endpoint is used consistently across the codebase

# Check for any remaining references to the old API endpoint pattern
echo "Checking for any references to old API endpoint pattern..."
rg -i "/api/content/v1/search" --type ts

# Check for consistent usage of the new endpoint pattern
echo "Checking usage of new endpoint pattern..."
rg -i "/interface/v1/action/composite/v3/search" --type ts

Length of output: 634


Attention: Inconsistent API Endpoint Versions
The update in mfes/content/src/services/Search.ts correctly changes the endpoint to /interface/v1/action/composite/v3/search. However, our verification shows that the following files still reference the old endpoint (/api/content/v1/search):

  • libs/shared-lib/src/lib/Services/Content/Hierarchy.ts
  • libs/shared-lib/src/lib/Services/Content/Search.ts

Please update these files to use the new API endpoint for consistency across the codebase.

Copy link

sonarqubecloud bot commented Mar 7, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
42.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant