Skip to content

feat: support short-lived tokens endpoint #517

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

Merged
merged 4 commits into from
Apr 14, 2025
Merged

feat: support short-lived tokens endpoint #517

merged 4 commits into from
Apr 14, 2025

Conversation

naomi-lgbt
Copy link
Contributor

@naomi-lgbt naomi-lgbt commented Apr 10, 2025

Proposed changes

Types of changes

What types of changes does your code introduce to the community Python SDK?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update or tests (if none of the other choices apply)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I have read the CONTRIBUTING doc
  • I have lint'ed all of my code using repo standards
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Further comments

Summary by CodeRabbit

  • New Features
    • Integrated robust authentication management, enabling the generation of temporary tokens.
    • Introduced both synchronous and asynchronous token retrieval options for enhanced flexibility.
    • Added demonstration examples to help users get started with the new authentication features.

Copy link
Contributor

coderabbitai bot commented Apr 10, 2025

Walkthrough

This pull request introduces authentication management capabilities into the Deepgram SDK. New properties in the DeepgramClient class return versioned instances of both synchronous and asynchronous authentication REST clients. Additional modules and files have been created to organize authentication-related classes and response types, including implementations for token generation with a short TTL. Two example scripts demonstrate the use of synchronous and asynchronous token requests via the updated SDK structure.

Changes

File(s) Change Summary
deepgram/client.py Added auth and asyncauth properties in DeepgramClient and updated the v method to handle "auth" and "asyncauth" versions.
deepgram/clients/__init__.py Introduced new imports for AuthRESTClient, AsyncAuthRESTClient, and GrantTokenResponse from the authentication module.
deepgram/clients/auth/__init__.py, deepgram/clients/auth/client.py Added new initializer and re-export file for authentication clients and grant token response types.
deepgram/clients/auth/v1/... New versioned files including:
__init__.py for module initialization
async_client.py with AsyncAuthRESTClient and its async grant_token method
client.py with AuthRESTClient and its grant_token method
response.py defining the GrantTokenResponse data class.
examples/auth/async_token/main.py, examples/auth/token/main.py Introduced example scripts demonstrating asynchronous and synchronous token generation using the new authentication clients.

Sequence Diagram(s)

Synchronous Token Generation

sequenceDiagram
    participant App as Application/Main
    participant Client as DeepgramClient
    participant AuthSync as AuthRESTClient
    participant AuthServer as Authentication Service

    App->>Client: Instantiate DeepgramClient (with API key)
    Client->>AuthSync: Retrieve auth property (v1 instance)
    AuthSync->>AuthServer: POST /grant_token (with API key)
    AuthServer-->>AuthSync: Return GrantTokenResponse (JWT and TTL)
    AuthSync-->>Client: Return token
    Client-->>App: Provide token result
Loading

Asynchronous Token Generation

sequenceDiagram
    participant App as Async Application/Main
    participant Client as DeepgramClient
    participant AuthAsync as AsyncAuthRESTClient
    participant AuthServer as Authentication Service

    App->>Client: Instantiate DeepgramClient (with API key)
    Client->>AuthAsync: Retrieve asyncauth property (v1 instance)
    AuthAsync->>AuthServer: Async POST /grant_token (with API key)
    AuthServer-->>AuthAsync: Return GrantTokenResponse (JWT and TTL)
    AuthAsync-->>Client: Return token asynchronously
    Client-->>App: Provide token result asynchronously
Loading

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 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 eb03638 and ffbf356.

📒 Files selected for processing (2)
  • examples/auth/async_token/main.py (1 hunks)
  • examples/auth/token/main.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/auth/token/main.py
  • examples/auth/async_token/main.py

🪧 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 plan to trigger planning for file edits and PR creation.
  • @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
Contributor

@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: 10

🧹 Nitpick comments (12)
examples/auth/token/main.py (3)

18-20: Remove unused AUDIO_URL constant

The AUDIO_URL constant is defined but never used in the script. Since this example demonstrates the authentication flow rather than audio processing, this constant appears to be unnecessary.

-AUDIO_URL = {
-    "url": "https://static.deepgram.com/examples/Bueller-Life-moves-pretty-fast.wav"
-}

10-14: Remove unused import

The PrerecordedOptions import is not used in this example script and should be removed.

from deepgram import (
    DeepgramClient,
-   PrerecordedOptions,
    DeepgramClientOptions
)

23-36: Add docstring to main function

Adding a docstring to the main function would improve code documentation and provide clarity about the example's purpose.

def main():
+    """
+    Demonstrates how to generate a short-lived authentication token using the Deepgram SDK.
+    
+    This example initializes a Deepgram client and calls the grant_token method
+    to generate a temporary JWT with a 30-second TTL.
+    """
    try:
        # STEP 1 Create a Deepgram client using the DEEPGRAM_API_KEY from your environment variables
        config = DeepgramClientOptions(
            verbose=verboselogs.SPAM,
        )
deepgram/clients/auth/v1/async_client.py (2)

41-42: Update log message to use correct class name.

The log message incorrectly uses "AuthRestClient" instead of "AsyncAuthRestClient".

-   self._logger.debug("AuthRestClient.grant_token ENTER")
+   self._logger.debug("AsyncAuthRestClient.grant_token ENTER")

50-51: Update log message to use correct class name.

The log message incorrectly uses "AuthRestClient" instead of "AsyncAuthRestClient".

-   self._logger.debug("AuthRestClient.grant_token LEAVE")
+   self._logger.debug("AsyncAuthRestClient.grant_token LEAVE")
deepgram/clients/auth/v1/client.py (2)

35-36: Fix docstring return type description

The docstring incorrectly states the return type contains "transcription result" when it should reference a token or authentication result.

        Returns:
-            GrantTokenResponse: An object containing the transcription result.
+            GrantTokenResponse: An object containing the authentication token and expiry information.

41-51: Consider adding error handling for network failures

The current implementation doesn't have explicit error handling for network failures or API errors. Consider adding try/except blocks to handle potential errors and provide meaningful error messages.

        self._logger.debug("AuthRestClient.grant_token ENTER")

        url = f"{self._config.url}/{self._endpoint}"
        self._logger.info("url: %s", url)
-        result = self.post(url, headers={"auth": self._config.api_key})
-        self._logger.info("json: %s", result)
-        res = GrantTokenResponse.from_json(result)
+        try:
+            result = self.post(url, headers={"auth": self._config.api_key})
+            self._logger.info("json: %s", result)
+            res = GrantTokenResponse.from_json(result)
+        except Exception as e:
+            self._logger.error("Failed to grant token: %s", str(e))
+            raise
        self._logger.verbose("result: %s", res)
        self._logger.notice("grant_token succeeded")
        self._logger.debug("AuthRestClient.grant_token LEAVE")
        return res
examples/auth/async_token/main.py (5)

19-21: Remove unused AUDIO_URL constant

The AUDIO_URL constant is defined but never used in this example. Since this example demonstrates token generation, not audio transcription, this constant should be removed to avoid confusion.

-AUDIO_URL = {
-    "url": "https://static.deepgram.com/examples/Bueller-Life-moves-pretty-fast.wav"
-}

11-15: Remove unused imports

The PrerecordedOptions is imported but never used in this example. Consider removing unused imports to keep the code clean.

from deepgram import (
    DeepgramClient,
-    PrerecordedOptions,
    DeepgramClientOptions
)

33-33: Add comment explaining version selection syntax

The .v("1") method call isn't immediately intuitive. Consider adding a comment to explain that this selects the API version.

        # STEP 2 Call the grant_token method on the auth rest class
+        # Use v("1") to select API version 1
        response = await deepgram.asyncauth.v("1").grant_token()

35-36: Improve error handling with specific exception types

The current exception handling catches all exceptions with a generic handler. Consider catching specific exception types to provide more helpful error messages.

        response = await deepgram.asyncauth.v("1").grant_token()
        print(f"response: {response}\n\n")
-    except Exception as e:
-        print(f"Exception: {e}")
+    except ConnectionError as e:
+        print(f"Connection error: {e}")
+    except ValueError as e:
+        print(f"Value error: {e}")
+    except Exception as e:
+        print(f"Unexpected error: {e}")

24-37: Add return statement to main function

The main function could return the response or a status code to indicate success or failure, which would be useful if this example is used as a reference for other applications.

async def main():
    try:
        # STEP 1 Create a Deepgram client using the DEEPGRAM_API_KEY from your environment variables
        config = DeepgramClientOptions(
            verbose=verboselogs.SPAM,
        )
        deepgram: DeepgramClient = DeepgramClient(os.environ.get("DEEPGRAM_API_KEY"), config)

        # STEP 2 Call the grant_token method on the auth rest class
        response = await deepgram.asyncauth.v("1").grant_token()
        print(f"response: {response}\n\n")
+        return 0  # Success
    except Exception as e:
        print(f"Exception: {e}")
+        return 1  # Error
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b9561e4 and 3474a88.

📒 Files selected for processing (10)
  • deepgram/client.py (3 hunks)
  • deepgram/clients/__init__.py (1 hunks)
  • deepgram/clients/auth/__init__.py (1 hunks)
  • deepgram/clients/auth/client.py (1 hunks)
  • deepgram/clients/auth/v1/__init__.py (1 hunks)
  • deepgram/clients/auth/v1/async_client.py (1 hunks)
  • deepgram/clients/auth/v1/client.py (1 hunks)
  • deepgram/clients/auth/v1/response.py (1 hunks)
  • examples/auth/async_token/main.py (1 hunks)
  • examples/auth/token/main.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
examples/auth/token/main.py (4)
deepgram/client.py (3)
  • DeepgramClient (408-668)
  • auth (507-511)
  • v (597-666)
deepgram/options.py (1)
  • DeepgramClientOptions (17-136)
deepgram/clients/auth/v1/async_client.py (1)
  • grant_token (31-51)
deepgram/clients/auth/v1/client.py (1)
  • grant_token (31-51)
deepgram/clients/auth/v1/response.py (1)
deepgram/clients/common/v1/shared_response.py (1)
  • BaseResponse (16-44)
deepgram/clients/auth/v1/__init__.py (3)
deepgram/clients/auth/v1/client.py (1)
  • AuthRESTClient (13-51)
deepgram/clients/auth/v1/async_client.py (1)
  • AsyncAuthRESTClient (13-51)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
deepgram/clients/auth/v1/async_client.py (3)
deepgram/options.py (1)
  • DeepgramClientOptions (17-136)
deepgram/clients/common/v1/abstract_async_rest.py (1)
  • AbstractAsyncRestClient (16-383)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
deepgram/clients/auth/__init__.py (3)
deepgram/clients/auth/v1/client.py (1)
  • AuthRESTClient (13-51)
deepgram/clients/auth/v1/async_client.py (1)
  • AsyncAuthRESTClient (13-51)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
deepgram/clients/auth/client.py (3)
deepgram/clients/auth/v1/client.py (1)
  • AuthRESTClient (13-51)
deepgram/clients/auth/v1/async_client.py (1)
  • AsyncAuthRESTClient (13-51)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
examples/auth/async_token/main.py (3)
deepgram/client.py (3)
  • DeepgramClient (408-668)
  • asyncauth (514-518)
  • v (597-666)
deepgram/options.py (1)
  • DeepgramClientOptions (17-136)
deepgram/clients/auth/v1/async_client.py (1)
  • grant_token (31-51)
deepgram/clients/auth/v1/client.py (5)
deepgram/options.py (1)
  • DeepgramClientOptions (17-136)
deepgram/clients/common/v1/abstract_sync_rest.py (1)
  • AbstractSyncRestClient (16-375)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
deepgram/utils/verboselogs/__init__.py (3)
  • VerboseLogger (119-168)
  • verbose (165-168)
  • notice (150-153)
deepgram/clients/auth/v1/async_client.py (1)
  • grant_token (31-51)
deepgram/clients/__init__.py (3)
deepgram/clients/auth/v1/client.py (1)
  • AuthRESTClient (13-51)
deepgram/clients/auth/v1/async_client.py (1)
  • AsyncAuthRESTClient (13-51)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
🪛 GitHub Actions: Check - static
deepgram/clients/auth/v1/response.py

[error] 17-17: mypy: Incompatible types in assignment (expression has type 'None', variable has type 'str')

🔇 Additional comments (9)
deepgram/clients/auth/v1/response.py (1)

12-24: Response structure looks good

The GrantTokenResponse class properly inherits from BaseResponse and includes appropriate fields for an authentication token response with JSON field name mappings. The default expiration of 30 seconds for short-lived tokens is a common practice.

🧰 Tools
🪛 GitHub Actions: Check - static

[error] 17-17: mypy: Incompatible types in assignment (expression has type 'None', variable has type 'str')

examples/auth/token/main.py (1)

1-40: Good example implementation

Overall, this example effectively demonstrates the new short-lived token functionality. It properly initializes the Deepgram client, calls the grant_token method, and handles potential exceptions. The step-by-step comments are helpful for users learning how to use this feature.

deepgram/clients/auth/client.py (1)

5-11: Versioning pattern implemented correctly.

The file sets up a clean versioning facade for the authentication clients, following the SDK's established pattern. This approach allows for future version updates while maintaining backward compatibility.

deepgram/client.py (5)

251-257: Auth client imports added correctly.

The imports for the authentication-related classes are appropriately added, following the import organization pattern used throughout the file.


506-512: Auth property added correctly.

The auth property is well-implemented and follows the same pattern as other client properties in the class. The docstring clearly describes its purpose.


513-519: AsyncAuth property added correctly.

The asyncauth property is well-implemented and follows the same pattern as other async client properties in the class. The docstring clearly describes its purpose.


631-635: Auth versioning handler added correctly.

The "auth" case is properly added to the version handling logic in the v method, making it consistent with how other client types are versioned.


635-639: AsyncAuth versioning handler added correctly.

The "asyncauth" case is properly added to the version handling logic in the v method, making it consistent with how other async client types are versioned.

deepgram/clients/__init__.py (1)

315-320: Auth module imports added correctly.

The imports for the authentication-related classes and response types are appropriately added, following the organizational pattern used throughout the file.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
deepgram/clients/auth/v1/response.py (2)

21-24: Clarify the time unit for expires_in.

The expires_in field has a default value of 30, but the code doesn't specify what time unit this represents (seconds, minutes, hours). Consider enhancing the docstring to clarify this.

    expires_in: int = field(
        metadata=dataclass_config(field_name='expires_in'),
        default=30,
+       # Default token expiration time in seconds
    )

Also, consider adding field-level docstrings for better API documentation:

@dataclass
class GrantTokenResponse(BaseResponse):
    """
    The response object for the authentication grant token endpoint.
+
+    Attributes:
+        access_token: The token string used for authentication
+        expires_in: Number of seconds until the token expires
    """

12-24: Consider validation for expires_in field.

For authentication tokens, the expiration time should always be a positive value. Consider adding validation to ensure that expires_in is greater than zero, either through a post-init check or a property setter.

@dataclass
class GrantTokenResponse(BaseResponse):
    """
    The response object for the authentication grant token endpoint.
    """
    access_token: str = field(
        metadata=dataclass_config(field_name='access_token'),
        default="",
    )
    expires_in: int = field(
        metadata=dataclass_config(field_name='expires_in'),
        default=30,
    )
+
+    def __post_init__(self):
+        """Validate that expires_in is a positive integer."""
+        if self.expires_in <= 0:
+            raise ValueError("expires_in must be a positive integer")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3474a88 and 72e61a2.

📒 Files selected for processing (1)
  • deepgram/clients/auth/v1/response.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
deepgram/clients/auth/v1/response.py (1)
deepgram/clients/common/v1/shared_response.py (1)
  • BaseResponse (16-44)
🔇 Additional comments (1)
deepgram/clients/auth/v1/response.py (1)

17-20: Access token default value looks good.

Your implementation of the access_token field with an empty string default value addresses the previous type mismatch issue correctly. This ensures type consistency while providing a sensible default.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
deepgram/clients/auth/v1/async_client.py (1)

23-30: Consider avoiding repeated addition of StreamHandlers.
If multiple instances of this client are created, this might attach multiple stream handlers to the same logger, leading to duplicated logs. You may wish to check if a handler is already present before adding a new one.

 def __init__(self, config: DeepgramClientOptions):
     self._logger = verboselogs.VerboseLogger(__name__)
-    self._logger.addHandler(logging.StreamHandler())
+    if not self._logger.handlers:
+        self._logger.addHandler(logging.StreamHandler())
     self._logger.setLevel(config.verbose)
     ...
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 72e61a2 and eb03638.

📒 Files selected for processing (5)
  • deepgram/clients/auth/v1/__init__.py (1 hunks)
  • deepgram/clients/auth/v1/async_client.py (1 hunks)
  • deepgram/clients/auth/v1/client.py (1 hunks)
  • examples/auth/async_token/main.py (1 hunks)
  • examples/auth/token/main.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • deepgram/clients/auth/v1/init.py
  • examples/auth/token/main.py
  • deepgram/clients/auth/v1/client.py
  • examples/auth/async_token/main.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
deepgram/clients/auth/v1/async_client.py (3)
deepgram/options.py (1)
  • DeepgramClientOptions (17-136)
deepgram/clients/common/v1/abstract_async_rest.py (1)
  • AbstractAsyncRestClient (16-383)
deepgram/clients/auth/v1/response.py (1)
  • GrantTokenResponse (13-24)
🔇 Additional comments (4)
deepgram/clients/auth/v1/async_client.py (4)

1-3: License header looks good.
No concerns with the MIT license header and SPDX identifier.


5-10: Imports appear well-organized.
All imported modules are relevant. No issues found with ordering or structure.


13-17: Class docstring is clear and accurate.
It correctly describes this class as handling authentication endpoint requests.


31-52:

❓ Verification inconclusive

Verify the DeepgramTypeError reference.
The docstring mentions DeepgramTypeError, but there's no explicit import. Confirm that AbstractAsyncRestClient or another module properly raises it, or add an import if needed.


🏁 Script executed:

#!/bin/bash
# Description: Confirm that DeepgramTypeError is defined/imported somewhere in the codebase.

fd DeepgramTypeError --exec cat {}

Length of output: 38


Below is a revised review comment:


Action Required: Verify the DeepgramTypeError Reference

It appears that no explicit definition or import of DeepgramTypeError was found anywhere in the repository. Please confirm whether DeepgramTypeError is intended to be provided by an upstream module (for example, via a base class like AbstractAsyncRestClient) or if an explicit import/definition should be added in this file. This will ensure that the docstring’s reference accurately reflects the available exception.

jpvajda
jpvajda approved these changes Apr 11, 2025
jpvajda
jpvajda previously approved these changes Apr 11, 2025
Copy link
Contributor

@jpvajda jpvajda left a comment

Choose a reason for hiding this comment

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

Had a question on the use of the AUDIO_URL variable in the examples, other than this looks good to me.

@naomi-lgbt naomi-lgbt merged commit 016bd4d into main Apr 14, 2025
5 checks passed
@naomi-lgbt naomi-lgbt deleted the feat/auth branch April 14, 2025 16:07
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.

2 participants