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

New Components - the_bookie #13995

Merged
merged 3 commits into from
Sep 25, 2024
Merged

New Components - the_bookie #13995

merged 3 commits into from
Sep 25, 2024

Conversation

luancazarine
Copy link
Collaborator

@luancazarine luancazarine commented Sep 18, 2024

Resolves #13973.

Summary by CodeRabbit

Release Notes

  • New Features

    • Users can now create and manage contacts within the address book.
    • New functionality to create sales invoices directly from the application.
    • Enhanced contact search capabilities with filtering options for clients and suppliers.
    • Event-driven architecture improvements for new contact and invoice creation notifications.
  • Improvements

    • Added pagination for contact and invoice listings to handle large datasets efficiently.
    • Introduced utility functions for better data handling and standardization across the application.
  • Version Update

    • The application version has been updated to 0.1.0, reflecting the introduction of these new features and improvements.

@luancazarine luancazarine added the ai-assisted Content generated by AI, with human refinement and modification label Sep 18, 2024
Copy link

vercel bot commented Sep 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
pipedream ❌ Failed (Inspect) Sep 19, 2024 5:33pm
3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Ignored (Inspect) Visit Preview Sep 19, 2024 5:33pm
pipedream-docs ⬜️ Ignored (Inspect) Sep 19, 2024 5:33pm
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Sep 19, 2024 5:33pm

Copy link
Contributor

coderabbitai bot commented Sep 18, 2024

Walkthrough

The changes introduce multiple new functionalities within "The Bookie" application, including actions for creating contacts and sales invoices, searching for contacts, and emitting events related to newly created contacts and invoices. New utility functions and constants are added to enhance data handling and standardization. The application’s architecture is improved to support real-time data updates and efficient API interactions, thereby expanding its capabilities in managing contacts and invoices.

Changes

Files Change Summary
components/the_bookie/actions/create-contact/create-contact.mjs, components/the_bookie/actions/create-sales-invoice/create-sales-invoice.mjs, components/the_bookie/actions/find-contact/find-contact.mjs Introduced actions for creating contacts and sales invoices, and searching for contacts, each with defined properties and validation logic.
components/the_bookie/common/constants.mjs, components/the_bookie/common/utils.mjs Added a constant LIMIT for standardization and introduced utility functions for file path checking, object parsing, and string capitalization.
components/the_bookie/package.json Updated version number and added a new dependency on @pipedream/platform.
components/the_bookie/sources/common/base.mjs, components/the_bookie/sources/new-contact-created/new-contact-created.mjs, components/the_bookie/sources/new-invoice-created/new-invoice-created.mjs, components/the_bookie/sources/new-invoice-paid/new-invoice-paid.mjs Implemented sources for emitting events when contacts and invoices are created or paid, enhancing the event-driven architecture of the application.
components/the_bookie/the_bookie.app.mjs Expanded the application methods to include creating and listing contacts and invoices, as well as searching for contacts, with pagination support for handling large datasets.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant Database

    User->>API: Create Contact
    API->>Database: Validate and Save Contact
    Database-->>API: Contact Created
    API-->>User: Confirmation Message

    User->>API: Create Invoice
    API->>Database: Validate and Save Invoice
    Database-->>API: Invoice Created
    API-->>User: Confirmation Message

    User->>API: Search Contact
    API->>Database: Retrieve Contacts
    Database-->>API: Contacts Found
    API-->>User: List of Contacts
Loading

Assessment against linked issues

Objective Addressed Explanation
Emit new event when a contact is created or updated (#13973)
Emit new event when the state of an invoice is changed to 'paid' (#13973)
Emit new event when a new invoice is created (#13973)
Instantly creates a new contact in the address book (#13973)
Creates a new sales invoice (#13973)

Possibly related PRs

Suggested labels

bug

Poem

🐰 In the land of The Bookie, new features bloom,
Contacts and invoices, dispelling the gloom.
With actions and events, all neatly in line,
Managing data, oh so divine!
A hop and a skip, let’s cheer with delight,
For every new change makes our future bright! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
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 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.

Actions
 - Create Contact
 - Create Sales Invoice
 - Find Contacts

Sources
 - New Contact Created
 - New Invoice Created
 - New Invoice Paid
@luancazarine luancazarine marked this pull request as ready for review September 19, 2024 19:59
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: 14

Outside diff range and nitpick comments (5)
components/the_bookie/sources/new-contact-created/test-event.mjs (1)

1-28: LGTM! The test event object structure aligns with the requirements.

The exported test event object contains the necessary properties to represent a contact entity, including:

  • Identifiers: id, organisation_kvk_number, organisation_btw_number
  • Organizational info: organisation_name, is_supplier, is_client
  • Contact methods: email, telephone_number, mobile_number
  • Address details: street, street_number, postal_code, town, country
  • Financial metrics: total_sales_amount_excl_btw, total_purchase_amount_excl_btw, etc.

The property names are descriptive and follow a consistent naming convention. The object is directly exported as the default export, making it easily accessible for testing purposes.

Consider adding a few more suggestions to enhance the test event:

  1. Include sample data for the email, telephone_number, and mobile_number properties to better represent a realistic contact.

  2. Add a website property to capture the contact's website URL, as it is a common attribute for organizations.

  3. Consider including a created_at and updated_at timestamp properties to track the creation and modification dates of the contact.

These additions would make the test event more comprehensive and representative of real-world contact data.

components/the_bookie/sources/common/base.mjs (2)

22-26: Simplify sorting by ordering items ascendingly

Currently, filterItems() sorts items in descending order by id, and then emitEvent() reverses the array to process items in ascending order. You can simplify the code by sorting the items in ascending order directly and removing the .reverse() call.

Apply these diffs:

In filterItems():

     return items
         .filter((item) => item.id > lastId)
-        .sort((a, b) => b.id - a.id);
+        .sort((a, b) => a.id - b.id);

In emitEvent():

     for (const item of responseArray.reverse()) {
+    for (const item of responseArray) {

Also applies to: 47-47


41-43: Use slice() to limit the number of results

Instead of truncating the array by setting responseArray.length, consider using slice() for clarity and to avoid side effects.

Apply this diff:

         if (maxResults && (responseArray.length > maxResults)) {
-          responseArray.length = maxResults;
+          responseArray = responseArray.slice(0, maxResults);
         }
components/the_bookie/the_bookie.app.mjs (2)

42-49: Avoid returning null in _data helper method

Returning null from the _data method when data is falsy may cause issues with requests that expect an object, even if it's empty. Instead, return an empty object to ensure consistency.

Apply this diff to return an empty object when data is falsy:

   _data(data) {
-    return data
-      ? {
-          ...data,
-          admin_id: `${this.$auth.admin_id}`,
-        }
-      : null;
+    return {
+      ...data,
+      admin_id: `${this.$auth.admin_id}`,
+    };
   },

98-118: Clarify pagination loop termination condition

In the paginate method, hasMore is assigned results.length, which relies on JavaScript's truthy/falsy evaluation. For better readability and to avoid potential confusion, explicitly compare the length to determine if more results are available.

Apply this diff for clarity:

   do {
     // Pagination logic...
     hasMore = results.length;
-  } while (hasMore);
+  } while (hasMore > 0);
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between db59016 and cf6e415.

Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing (14)
  • components/the_bookie/actions/create-contact/create-contact.mjs (1 hunks)
  • components/the_bookie/actions/create-sales-invoice/create-sales-invoice.mjs (1 hunks)
  • components/the_bookie/actions/find-contact/find-contact.mjs (1 hunks)
  • components/the_bookie/common/constants.mjs (1 hunks)
  • components/the_bookie/common/utils.mjs (1 hunks)
  • components/the_bookie/package.json (2 hunks)
  • components/the_bookie/sources/common/base.mjs (1 hunks)
  • components/the_bookie/sources/new-contact-created/new-contact-created.mjs (1 hunks)
  • components/the_bookie/sources/new-contact-created/test-event.mjs (1 hunks)
  • components/the_bookie/sources/new-invoice-created/new-invoice-created.mjs (1 hunks)
  • components/the_bookie/sources/new-invoice-created/test-event.mjs (1 hunks)
  • components/the_bookie/sources/new-invoice-paid/new-invoice-paid.mjs (1 hunks)
  • components/the_bookie/sources/new-invoice-paid/test-event.mjs (1 hunks)
  • components/the_bookie/the_bookie.app.mjs (1 hunks)
Files skipped from review due to trivial changes (1)
  • components/the_bookie/sources/new-invoice-created/test-event.mjs
Additional comments not posted (27)
components/the_bookie/common/constants.mjs (1)

1-1: Clarify the purpose and reasoning behind the chosen value.

The addition of the LIMIT constant is a good practice for promoting consistency and maintainability across the codebase.

However, the chosen value of 100 seems arbitrary without additional context. Please provide clarification on the specific purpose of this limit and the reasoning behind the selected value to ensure it aligns with the intended use cases.

components/the_bookie/sources/new-invoice-paid/test-event.mjs (1)

1-13: LGTM!

The test event object is well-structured and aligns with the purpose described in the AI summary. The property names are descriptive and the values are of appropriate types. This object can serve as a good reference for the expected structure and data of an invoice payment event.

components/the_bookie/package.json (2)

3-3: Version update looks good!

The version has been updated from 0.0.1 to 0.1.0, following semantic versioning. This indicates a minor version release, suggesting that new backward-compatible functionality has been added.


15-17: Verify the integration of the new dependency.

A new dependency @pipedream/platform with version ^3.0.1 has been introduced. Ensure that the component integrates correctly with this new dependency and that there are no unintended side effects or breaking changes.

Run the following script to verify the dependency usage:

Verification successful

Dependency integration verified and aligned with project standards

The new dependency @pipedream/platform (version ^3.0.1) has been correctly added to the package.json file for the_bookie component. While we don't see direct usage in this specific component, the dependency is extensively used across the project for HTTP requests, error handling, and other utilities. This addition aligns with the overall architecture and development practices of the project.

Key observations:

  • Widespread use of import { axios } from "@pipedream/platform" for HTTP requests
  • Utilization of ConfigurationError and DEFAULT_POLLING_SOURCE_TIMER_INTERVAL from the package
  • Consistent usage pattern across various components

The integration of this dependency in the_bookie component is likely to facilitate standardization and potential future use of these common utilities.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of the new dependency `@pipedream/platform`.

# Test: Search for the dependency import. Expect: At least one occurrence.
rg --type javascript -A 5 $'@pipedream/platform'

Length of output: 82


Script:

#!/bin/bash

# Check the content of package.json
echo "Contents of package.json:"
cat components/the_bookie/package.json

echo -e "\n--- Searching for @pipedream/platform usage ---"
# Find JavaScript files and search for @pipedream/platform
fd -e js -e jsx -e ts -e tsx | xargs grep -n "@pipedream/platform" || echo "No usage found"

Length of output: 14297

components/the_bookie/sources/new-invoice-created/new-invoice-created.mjs (5)

1-2: LGTM!

The import statements are correctly defined and follow the proper syntax and naming conventions. The common base module and sample emit function are imported from the expected locations.


4-11: LGTM!

The module is correctly exported and extends the common base module. The module's metadata properties are appropriately defined, following consistent naming conventions and specifying the correct version, type, and deduplication strategy.


12-20: LGTM!

The methods for the module are correctly defined, including the common methods from the base module and the custom getFunction and getSummary methods. The getFunction method returns the appropriate function for retrieving invoices, and the getSummary method generates a formatted summary string for each emitted event.


21-21: LGTM!

The sampleEmit function is correctly included in the exported module, allowing it to be accessed and utilized for testing or generating sample events.


1-22: Code follows best practices and demonstrates good modularity.

The code is well-structured and follows best practices for modularity and extensibility. The use of a common base module promotes code reuse and maintainability. The metadata properties provide clear documentation, and the custom methods are concise and focused on specific functionalities. Overall, the code exhibits good quality and adherence to best practices.

components/the_bookie/sources/new-contact-created/new-contact-created.mjs (4)

1-2: LGTM!

The import statements are correctly defined, importing the necessary modules for the event source.


4-11: LGTM!

The exported object correctly extends the common base module and includes the necessary attributes for the event source. The metadata provides clear information about the purpose and behavior of the event source.


21-21: LGTM!

Including the sampleEmit module in the exported object is a good practice for providing a sample event emitter for testing and demonstration purposes. It can help developers understand how to use the event source and verify its functionality.


12-20: Verify the existence of theBookie object and listContacts function.

The methods are correctly defined, with getFunction returning a reference to the listContacts function and getSummary providing a concise summary of the event.

However, please ensure that the theBookie object and its listContacts function are properly defined and accessible within the scope of this module.

Run the following script to verify the existence of theBookie object and listContacts function:

Verification successful

The theBookie object and listContacts function are correctly used and exist in the codebase.

The verification process confirms that:

  1. The theBookie object is widely used across the codebase and is imported from "../../the_bookie.app.mjs".
  2. The listContacts function is indeed a method of the theBookie object, as evidenced by its usage in the file under review.

The code in question is consistent with the rest of the codebase and uses the theBookie object and its listContacts method correctly.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of `theBookie` object and `listContacts` function.

# Test: Search for the `theBookie` object. Expect: Occurrences in the codebase.
rg --type javascript $'theBookie'

# Test: Search for the `listContacts` function. Expect: Occurrences within the `theBookie` object.
rg --type javascript $'theBookie\\.listContacts'

Length of output: 151


Script:

#!/bin/bash
# Description: Verify the existence of `theBookie` object and `listContacts` function using alternative methods.

# Search for JavaScript files and grep for 'theBookie'
echo "Searching for 'theBookie':"
fd -e js -e mjs -e jsx -e ts -e tsx | xargs grep -n 'theBookie'

echo -e "\nSearching for 'theBookie.listContacts':"
fd -e js -e mjs -e jsx -e ts -e tsx | xargs grep -n 'theBookie\.listContacts'

# Use ast-grep to search for 'theBookie' object and 'listContacts' function
echo -e "\nSearching for 'theBookie' object using ast-grep:"
ast-grep --lang javascript --pattern 'const theBookie = $_'

echo -e "\nSearching for 'listContacts' function using ast-grep:"
ast-grep --lang javascript --pattern 'listContacts: function($_) { $$$ }'

Length of output: 2288

components/the_bookie/sources/new-invoice-paid/new-invoice-paid.mjs (2)

17-19: Verify the existence and initialization of the theBookie object and its listInvoices method.

The code assumes that the theBookie object exists and has a listInvoices method. Please ensure that the theBookie object is properly initialized and has the expected method.

You can add a null check before accessing the method to prevent potential runtime errors:

getFunction() {
  return this.theBookie && this.theBookie.listInvoices;
}

20-22: Verify the structure of the item object and handle cases where the id property may be missing.

The code assumes that the item object has an id property. Please ensure that the item object always contains the expected id property.

You can add a null check or provide a default value to handle cases where the id property may be missing:

getSummary(item) {
  const invoiceId = item && item.id ? item.id : 'Unknown';
  return `Invoice ${invoiceId} paid`;
}
components/the_bookie/common/utils.mjs (3)

1-6: LGTM!

The checkTmp function correctly ensures that the given filename is prefixed with /tmp, providing a standardized way to handle temporary file paths. The implementation is concise and effective.


8-31: LGTM!

The parseObject function provides a robust way to handle parsing of objects and arrays. It correctly distinguishes between different input types (array, string, or other) and applies the appropriate parsing logic. The use of a try-catch block ensures that parsing errors are gracefully handled by returning the original input. The implementation is clear and covers various scenarios effectively.


33-37: LGTM!

The capitalize function correctly capitalizes the first character of the given string. It handles falsy inputs by returning null and ensures that the input is treated as a string by using toString(). The implementation is straightforward and effective.

components/the_bookie/actions/find-contact/find-contact.mjs (5)

1-3: LGTM!

The import statements are correctly used in the rest of the file.


4-30: LGTM!

The action definition follows the expected structure, and the props are correctly defined with appropriate types, labels, and descriptions. The inclusion of a link to the documentation in the description property is helpful.


31-40: LGTM!

The run method correctly uses the paginate method to handle the search operation asynchronously, and the searchContact function is called with the appropriate parameters. The use of the capitalize function ensures consistency in the boolean values.


42-46: LGTM!

The use of for await...of loop is appropriate for iterating over the asynchronous response, and pushing each item into the responseArray allows for collecting all the results.


48-50: LGTM!

Exporting a summary using $.export is a good practice to provide a quick overview of the results, and returning the responseArray allows for further processing of the found contacts if needed.

components/the_bookie/actions/create-contact/create-contact.mjs (2)

4-107: LGTM!

The action definition looks good:

  • The metadata (key, name, description, version, type) is appropriately defined.
  • The props cover a comprehensive set of properties for creating a contact, with clear labels and descriptions.
  • Most of the props are marked as optional, providing flexibility in specifying contact details.

108-138: LGTM!

The run method looks good:

  • The validation check for isClient or isSupplier ensures that at least one of them is true before proceeding.
  • The API call is made with the appropriate contact details passed in the data object.
  • The summary message provides useful information about the created contact's ID.
  • Returning the API response allows for further processing of the created contact data if needed.
components/the_bookie/the_bookie.app.mjs (2)

1-2: Imports are correctly defined

The import statements for axios and LIMIT are appropriate and correctly referenced.


86-97: Potential redundancy in adding admin_id to params

In the searchContact method, admin_id is added to params, but it's also added in the _data method to data. Verify whether admin_id needs to be included in both params and data to prevent duplicate data and potential conflicts.

Run the following script to check where admin_id is being appended:

Comment on lines +14 to +16
filterItems(items) {
return items.filter((item) => item.state === 20);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Use a constant or enum for the state value.

The magic number 20 is used directly in the code to represent the "paid" state. This reduces readability and maintainability.

Consider defining a constant or enum for the state values and use that instead of the magic number. For example:

const INVOICE_STATES = {
  PAID: 20,
  // other states...
};

filterItems(items) {
  return items.filter((item) => item.state === INVOICE_STATES.PAID);
}

Comment on lines +29 to +31
const response = this.theBookie.paginate({
fn: this.getFunction(),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Ensure getFunction() is implemented in subclasses

The method getFunction() is called but not defined in this base class. If it's intended to be implemented by subclasses, consider documenting this requirement or providing a default implementation that throws an error to guide developers.

Comment on lines +50 to +51
summary: this.getSummary(item),
ts: Date.parse(new Date()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Ensure getSummary() is implemented in subclasses

Similarly, getSummary(item) is called but not defined. If subclasses should implement this method, adding documentation or a default method that raises an error can help prevent runtime issues.

this.$emit(item, {
id: item.id,
summary: this.getSummary(item),
ts: Date.parse(new Date()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Fix timestamp calculation in event emission

Using Date.parse(new Date()) returns NaN because Date.parse() expects a string, but new Date() returns a Date object. To get the current timestamp, use Date.now().

Apply this diff to fix the timestamp calculation:

           ts: Date.parse(new Date()),
+          ts: Date.now(),
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ts: Date.parse(new Date()),
ts: Date.now(),

Comment on lines +29 to +36
const response = this.theBookie.paginate({
fn: this.getFunction(),
});

let responseArray = [];
for await (const item of response) {
responseArray.push(item);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add error handling for the pagination process

Currently, if this.theBookie.paginate() or this.getFunction() throw an error, it will be unhandled. Consider adding a try-catch block to handle potential errors gracefully and log meaningful messages.

Example:

       const response = this.theBookie.paginate({
         fn: this.getFunction(),
       });

+      try {
         let responseArray = [];
         for await (const item of response) {
           responseArray.push(item);
         }
+      } catch (error) {
+        console.error("Error during pagination:", error);
+        // Handle error accordingly
+      }

Committable suggestion was skipped due to low confidence.

@luancazarine luancazarine merged commit 74b4c47 into master Sep 25, 2024
12 checks passed
@luancazarine luancazarine deleted the issue-13973 branch September 25, 2024 12:39
@coderabbitai coderabbitai bot mentioned this pull request Oct 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ai-assisted Content generated by AI, with human refinement and modification
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Components] the_bookie
2 participants