-
Notifications
You must be signed in to change notification settings - Fork 73
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
🚀 enhance: Indexed DB integration to sync products over counters and improve frontend products search #126
base: develop
Are you sure you want to change the base?
Conversation
…DB through REST API
compiled js files
…s per API changes
…xed console error on storing products data
… missing issue on search
…interval to 15 seconds. Decremented product logs counter counts at the time of deleting any counter
…search made case insensitive.
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; | ||
|
||
$tables = [ | ||
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use IF NOT EXISTS
while creating tables using dbDelta
as it will synchronize the schema chnages automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please follow the instructions mentioned here: https://developer.wordpress.org/reference/functions/dbdelta/#user-contributed-notes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bhai, in case of future changes to the table schema, we'll implement the changes manually using upgraders. So, I'm leaving IF NOT EXISTS
here.
4) NOT NULL DEFAULT 0.0000, | ||
`product_stock` bigint signed NULL, | ||
`counter_counts` bigint unsigned NULL DEFAULT 0, | ||
PRIMARY KEY (`id`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also adding index for product_id
would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bhai, the table rows are going to be created/updated/deleted frequently while products are sold or edited by multiple counters since this is the product logs table. So, we are not implementing indexing
here.
…roducts-search-improvement
WalkthroughThe recent updates enhance the product management capabilities within the wePOS system by implementing a module for managing a product database using IndexedDB. This includes functionalities for creating and manipulating product entries, allowing for efficient searching, updating, and deletion of products. Additionally, a new feature for refreshing product logs has been introduced in the Home component, improving data synchronization and user experience. Changes
Assessment against linked issues
Poem
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 (3)
includes/REST/ProductsLogController.php (1)
217-217
: Ensure that error messages are localized for better user experience.Consider using localization functions for error messages to support multiple languages.
Also applies to: 255-255, 321-321, 409-409
assets/src/frontend/components/ProductSearch.vue (1)
245-250
: Optimize the search functionality by caching results or using more efficient search algorithms.Consider implementing caching mechanisms or optimizing the search algorithm to improve performance.
assets/src/frontend/components/Home.vue (1)
1241-1248
: Use CSS animations responsibly.While the rotation animation on the refresh icon is a nice visual touch, ensure it does not distract from the user experience, especially in a fast-paced environment like a POS system.
Review Details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (18)
- assets/less/style.less (1 hunks)
- assets/src/frontend/components/CustomerSearch.vue (8 hunks)
- assets/src/frontend/components/Home.vue (8 hunks)
- assets/src/frontend/components/ProductSearch.vue (6 hunks)
- assets/src/utils/Bootstrap.js (3 hunks)
- assets/src/utils/productIndexedDb.js (1 hunks)
- assets/src/utils/productLogs.js (1 hunks)
- includes/Admin/Updates.php (1 hunks)
- includes/Admin/Updates/upgrade-1.3.0.php (1 hunks)
- includes/Ajax.php (1 hunks)
- includes/Assets.php (2 hunks)
- includes/Installer.php (1 hunks)
- includes/ProductsLog.php (1 hunks)
- includes/REST/Manager.php (2 hunks)
- includes/REST/ProductsLogController.php (1 hunks)
- includes/Uninstaller.php (1 hunks)
- includes/functions.php (1 hunks)
- wepos.php (3 hunks)
Files skipped from review due to trivial changes (2)
- assets/less/style.less
- assets/src/frontend/components/CustomerSearch.vue
Additional Context Used
Biome (23)
assets/src/utils/Bootstrap.js (16)
46-66: This function expression can be turned into an arrow function.
68-72: This function expression can be turned into an arrow function.
79-81: This function expression can be turned into an arrow function.
88-88: The computed expression can be simplified without the use of a string literal.
89-89: The computed expression can be simplified without the use of a string literal.
90-90: The computed expression can be simplified without the use of a string literal.
91-91: The computed expression can be simplified without the use of a string literal.
92-92: The computed expression can be simplified without the use of a string literal.
93-93: The computed expression can be simplified without the use of a string literal.
94-94: The computed expression can be simplified without the use of a string literal.
95-95: The computed expression can be simplified without the use of a string literal.
96-96: The computed expression can be simplified without the use of a string literal.
116-119: This function expression can be turned into an arrow function.
121-130: This function expression can be turned into an arrow function.
113-131: This function expression can be turned into an arrow function.
114-114: This let declares a variable that is only assigned once.
assets/src/utils/productIndexedDb.js (4)
85-94: Prefer for...of instead of forEach.
153-153: This let declares a variable that is only assigned once.
156-156: This let declares a variable that is only assigned once.
158-158: This let declares a variable that is only assigned once.
assets/src/utils/productLogs.js (3)
3-12: Prefer for...of instead of forEach.
16-16: Template literals are preferred over string concatenation.
18-18: Template literals are preferred over string concatenation.
Additional comments not posted (21)
includes/Uninstaller.php (2)
20-22
: Therun
method is well-implemented and clear in its purpose.
31-44
: Ensure that the capabilities being removed are not required elsewhere in the application.includes/Ajax.php (2)
19-21
: The constructor is correctly set up to register the heartbeat handlers.
34-44
: Ensure that all data received from the front end is properly sanitized and unslashed.Verification successful
The verification process has confirmed that the data received from the front end is properly sanitized and unslashed in the relevant files.
- The
$_POST
and$_GET
variables are handled with appropriate sanitization functions such aswp_unslash
,sanitize_text_field
, and others.- The specific instances in the
includes/Ajax.php
file and other related files show proper sanitization practices.Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that all data received from the front end is properly sanitized. # Test: Search for unsanitized data usage. Expect: No unsanitized data. rg --type php '$_POST|$_GET'Length of output: 30
Script:
#!/bin/bash # Description: Verify that all data received from the front end is properly sanitized. # Search for all instances of $_POST and $_GET in the codebase. rg --type php '\$_POST|\$_GET' -l | xargs -I {} sh -c 'echo "File: {}"; rg --type php "\$_POST|\$_GET" {}' # Check if these instances are properly sanitized. rg --type php 'wp_unslash|sanitize_text_field|sanitize_email|sanitize_key|sanitize_meta|sanitize_mime_type|sanitize_option|sanitize_sql_orderby|sanitize_textarea_field|sanitize_title|sanitize_title_for_query|sanitize_title_with_dashes|sanitize_user'Length of output: 2657
includes/Admin/Updates/upgrade-1.3.0.php (1)
10-12
: The update function is well-structured and clear in its purpose.includes/Installer.php (2)
20-23
: Therun
method is well-implemented and clear in its purpose.
32-55
: Ensure that the capabilities being added are appropriate and not excessive.Verification successful
The capabilities
publish_shop_orders
andlist_users
are used in multiple places within the codebase, including their addition and removal in the installer and uninstaller, and checks in the REST controller. This indicates that these capabilities are necessary and appropriately scoped.
includes/Installer.php
: Adds the capabilities.includes/Uninstaller.php
: Removes the capabilities.includes/REST/PaymentController.php
: Checks for thepublish_shop_orders
capability.These usages suggest that the capabilities are not excessive and are appropriately managed within the codebase.
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that added capabilities are appropriate and not excessive. # Test: Search for usage of added capabilities. Expect: No excessive or inappropriate capabilities. rg --type php 'publish_shop_orders|list_users'Length of output: 906
includes/Admin/Updates.php (1)
18-18
: The addition of the new update entry for version 1.3.0 is correctly implemented.assets/src/utils/Bootstrap.js (2)
97-98
: LGTM! AddingproductIndexedDb
andproductLogs
to the globalwindow.wepos
object facilitates their access across the application.
14-15
: Ensure that the imported modulesproductIndexedDb
andproductLogs
are correctly implemented and exported in their respective files.Verification successful
The modules
productIndexedDb
andproductLogs
are correctly implemented and exported in their respective files.
assets/src/utils/productIndexedDb.js
assets/src/utils/productLogs.js
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the correct implementation and export of modules in productIndexedDb.js and productLogs.js. # Test: Search for the export statements in productIndexedDb.js and productLogs.js. Expect: Correct export statements. rg --type js --files-with-matches 'export' src/utils/productIndexedDb.js src/utils/productLogs.jsLength of output: 232
Script:
#!/bin/bash # Description: Locate the correct paths for productIndexedDb and productLogs. # Search for the files named productIndexedDb.js and productLogs.js in the repository. fd productIndexedDb.js fd productLogs.jsLength of output: 113
Script:
#!/bin/bash # Description: Verify the correct implementation and export of modules in productIndexedDb.js and productLogs.js. # Test: Search for the export statements in productIndexedDb.js and productLogs.js. Expect: Correct export statements. rg --type js --files-with-matches 'export' assets/src/utils/productIndexedDb.js assets/src/utils/productLogs.jsLength of output: 180
includes/REST/Manager.php (1)
24-31
: LGTM! The addition ofProductsLogController
to the class map ensures that it is loaded and its routes are registered, which is crucial for handling product logs.includes/Assets.php (1)
196-196
: Ensure that the 'heartbeat' script is correctly registered before enqueuing to avoid potential issues.Verification successful
The 'heartbeat' script is not registered within the codebase, but it is enqueued in
includes/Assets.php
. This suggests that 'heartbeat' might be a default script provided by WordPress, which does not require explicit registration.
includes/Assets.php
:wp_enqueue_script( 'heartbeat' );
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that the 'heartbeat' script is registered before being enqueued. # Test: Search for the registration of the 'heartbeat' script. Expect: Registration statement before the enqueue. rg --type php 'wp_register_script.*heartbeat' includes/Assets.phpLength of output: 67
Script:
#!/bin/bash # Description: Verify that the 'heartbeat' script is registered before being enqueued. # Test: Search for the registration of the 'heartbeat' script in the entire codebase. rg --type php 'wp_register_script.*heartbeat'Length of output: 47
Script:
#!/bin/bash # Description: Verify if 'heartbeat' is a default script provided by WordPress. # Test: Search for 'heartbeat' in the WordPress core scripts documentation or source code. rg --type php 'heartbeat' wp-includesLength of output: 89
Script:
#!/bin/bash # Description: Verify if 'heartbeat' is a default script provided by WordPress. # Test: Search for 'heartbeat' in the entire repository to verify if it is a default script. rg --type php 'heartbeat'Length of output: 484
wepos.php (3)
275-277
: Ensure proper error handling during plugin activation.The activation method now uses an
Installer
class to run installation tasks. It's crucial to ensure that any potential errors during the installation process are handled gracefully. Consider adding error handling mechanisms or logging to capture and respond to any issues that might occur during activation.try { $installer = new WeDevs\WePOS\Installer(); $installer->run(); } catch (Exception $e) { // Handle exception or log error }
288-290
: Ensure proper error handling during plugin deactivation.Similar to the activation process, the deactivation method uses an
Uninstaller
class. Proper error handling should also be implemented here to manage any exceptions or errors that might arise during the deactivation process.try { $uninstaller = new WeDevs\WePOS\Uninstaller(); $uninstaller->run(); } catch (Exception $e) { // Handle exception or log error }
333-337
: Initialization of new components in the plugin architecture.The addition of new components such as
common
,rest
,assets
,products_log
, andajax
in theinit_classes
method is a good architectural move. It modularizes the code and separates concerns, making the system more maintainable and scalable. Ensure that these components are properly documented and tested to maintain code quality.includes/REST/ProductsLogController.php (1)
32-32
: Ensure proper sanitization and validation of the 'id' parameter.Verification successful
The 'id' parameter is defined and used in the
includes/REST/ProductsLogController.php
file. It is associated with theabsint
sanitization callback, which is appropriate for integer values.
register_rest_route( $this->namespace, '/' . $this->base. '/(?P<id>[\d]+)', [
'id' => [ 'description' => __( 'Unique identifier for the object.', 'wepos' ),
$args['counter_id'] = $request['id'];
$data['id'] = intval( $item['id'] );
These usages indicate that the 'id' parameter is properly sanitized and validated.
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that the 'id' parameter is properly sanitized and validated. # Test: Search for the sanitization and validation logic for 'id' parameter. rg --type php $'sanitize_callback'Length of output: 1698
Script:
#!/bin/bash # Description: Verify the specific usage of the 'id' parameter in the context of the sanitize_callback. # Test: Search for the definition and usage of the 'id' parameter in the ProductsLogController.php file. rg --type php 'id' includes/REST/ProductsLogController.phpLength of output: 783
includes/ProductsLog.php (2)
283-290
: Optimize bulk insert operations to improve performance.
[PERFORMANCE]
Consider using prepared statements for bulk inserts to enhance performance and security.
75-76
: Ensure proper permission checks are in place to prevent unauthorized access.Also applies to: 106-108, 306-307
Verification successful
The search results indicate that permission checks using
current_user_can
are present in theincludes/ProductsLog.php
file. Here are the relevant lines:
includes/ProductsLog.php: if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'cashier' ) )
Given these findings, it appears that proper permission checks are in place to prevent unauthorized access. Therefore, the concern mentioned in the review comment is addressed.
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that permission checks are properly implemented. # Test: Search for permission checks in the codebase. rg --type php $'current_user_can'Length of output: 3316
assets/src/frontend/components/Home.vue (3)
37-46
: Add a tooltip to enhance user interaction with the refresh products feature.The addition of a refresh products section with a tooltip provides a clear and user-friendly way to trigger product updates. This aligns with the PR's goal to improve product search and synchronization.
604-604
: Ensure proper initialization ofproductLogsLoading
and related states.Initializing
productLogsLoading
tofalse
ensures that the loading state is managed correctly, which is crucial for the new product refresh functionality.
1198-1206
: Verify the creation of IndexedDB on component creation.The proactive check and creation of the IndexedDB when the component is created is a good practice, ensuring that the database is ready for use as soon as the component is loaded.
} else if ( this.isProductsStorageUpdateRequired() ) { | ||
// Remove existing products from the IndexedDB storage. | ||
wepos.productIndexedDb.deleteAllProducts(); | ||
|
||
// Insert products to the IndexedDB storage. | ||
wepos.productIndexedDb.insertProducts( this.products ); | ||
|
||
// Store products IndexedDB updating time to Local Storage. | ||
localStorage.setItem( 'productsStorageUpdatedOn', dayjs().unix() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimize the handling of IndexedDB operations.
Consider abstracting IndexedDB operations into a separate method to improve code readability and maintainability.
- wepos.productIndexedDb.deleteAllProducts();
- wepos.productIndexedDb.insertProducts(this.products);
- localStorage.setItem('productsStorageUpdatedOn', dayjs().unix());
+ this.updateIndexedDBProducts();
And then define updateIndexedDBProducts
method in the methods section.
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.
} else if ( this.isProductsStorageUpdateRequired() ) { | |
// Remove existing products from the IndexedDB storage. | |
wepos.productIndexedDb.deleteAllProducts(); | |
// Insert products to the IndexedDB storage. | |
wepos.productIndexedDb.insertProducts( this.products ); | |
// Store products IndexedDB updating time to Local Storage. | |
localStorage.setItem( 'productsStorageUpdatedOn', dayjs().unix() ); | |
} else if ( this.isProductsStorageUpdateRequired() ) { | |
this.updateIndexedDBProducts(); | |
``` | |
And then define `updateIndexedDBProducts` method in the methods section: | |
```suggestion | |
methods: { | |
updateIndexedDBProducts() { | |
// Remove existing products from the IndexedDB storage. | |
wepos.productIndexedDb.deleteAllProducts(); | |
// Insert products to the IndexedDB storage. | |
wepos.productIndexedDb.insertProducts(this.products); | |
// Store products IndexedDB updating time to Local Storage. | |
localStorage.setItem('productsStorageUpdatedOn', dayjs().unix()); | |
}, | |
// other methods... | |
} |
* @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response | ||
*/ | ||
public function get_items( $request ) { | ||
$args['counter_id'] = $request['id']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency in accessing request parameters: Use a consistent method to access request parameters to avoid confusion and potential bugs.
- $args['counter_id'] = $request['id'];
+ $args['counter_id'] = $request->get_param('id');
Also applies to: 135-135
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.
$args['counter_id'] = $request['id']; | |
$args['counter_id'] = $request->get_param('id'); |
refreshProducts() { | ||
this.fetchProductLogs( wepos.current_cashier.counter_id ); | ||
}, | ||
fetchProductLogs( counterId ) { | ||
let fetchingToast = { | ||
title: this.__( 'Products already updated!', 'wepos' ), | ||
type: 'info' | ||
} | ||
|
||
this.productLogsLoading = true; | ||
|
||
wepos.api.get( wepos.rest.root + wepos.rest.posversion + '/product/logs/' + counterId ) | ||
.done( ( response, status, xhr ) => { | ||
if ( response.length > 0 ) { | ||
wepos.productLogs.updateProductsToIndexedDb( response ); | ||
wepos.productLogs.updateProductLogsData( counterId ); | ||
|
||
fetchingToast.title = this.__( 'Products refreshed successfully!', 'wepos' ); | ||
fetchingToast.type = 'success'; | ||
} | ||
} ).fail( ( response, status, xhr ) => { | ||
fetchingToast.title = this.__( 'Failed to refresh products!', 'wepos' ); | ||
fetchingToast.type = 'error'; | ||
} ).then( ( response, status, xhr ) => { | ||
this.productLogsLoading = false; | ||
|
||
this.toast( fetchingToast ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling in the fetchProductLogs
method.
Currently, the method does not handle potential API failures gracefully. Consider adding error handling to improve the robustness of the product log fetching process.
+ .fail( (error) => {
+ console.error('Failed to fetch product logs:', error);
+ this.productLogsLoading = false;
+ });
Committable suggestion was skipped due low confidence.
|
||
$request->set_query_params( [ 'context' => 'view' ] ); | ||
|
||
$product_logs = wepos()->products_log->get_product_logs( $args ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip
Codebase Verification
Consider handling potential exceptions or errors from external calls.
The search results indicate that there is some error handling implemented in the codebase (e.g., in includes/Admin/Settings.php
). However, the specific lines in includes/REST/ProductsLogController.php
(lines 97 and 139) do not show any error handling for the external call to wepos()->products_log->get_product_logs($args)
.
includes/REST/ProductsLogController.php
(lines 97 and 139)
Analysis chain
Consider handling potential exceptions or errors from external calls.
Also applies to: 139-139
Scripts Executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that error handling is implemented for external calls.
# Test: Search for error handling patterns in the codebase.
rg --type php $'try {'
Length of output: 97
Script:
#!/bin/bash
# Description: Verify that error handling is implemented for external calls.
# Test: Search for error handling patterns in the codebase.
rg --type php 'try {'
Length of output: 97
Script:
#!/bin/bash
# Description: Verify that error handling is implemented for external calls.
# Test: Search for error handling patterns in the codebase.
rg --type php 'try \{'
Length of output: 66
if ( null !== product.stock && 0 === Number( product.stock ) ) { | ||
return false; | ||
} | ||
|
||
if ( 0 === Number( product.price ) ) { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor the eligibility check to make it more concise and readable.
- if ( null !== product.stock && 0 === Number( product.stock ) ) {
- return false;
- }
- if ( 0 === Number( product.price ) ) {
- return false;
- }
- return true;
+ return product.stock !== null && Number(product.stock) > 0 && Number(product.price) > 0;
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.
if ( null !== product.stock && 0 === Number( product.stock ) ) { | |
return false; | |
} | |
if ( 0 === Number( product.price ) ) { | |
return false; | |
} | |
return product.stock !== null && Number(product.stock) > 0 && Number(product.price) > 0; |
return new \WP_Error( 'failed-to-insert', __( 'Failed to insert product log data', 'wepos' ) ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standardize error handling across the class to improve maintainability.
Consider using a helper function to handle common error scenarios.
Also applies to: 255-256, 321-322, 409-410
* Get cashier data by user id. | ||
* | ||
* @param int $user_id | ||
* | ||
* @return array|\WP_Error | ||
*/ | ||
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | ||
global $wpdb; | ||
|
||
$user_data = get_userdata( $user_id ); | ||
|
||
if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | ||
return []; | ||
} | ||
|
||
$cashier_data = $wpdb->get_row( | ||
$wpdb->prepare( | ||
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'", | ||
$user_id | ||
) | ||
); | ||
|
||
if ( empty( $cashier_data ) ) { | ||
return []; | ||
} | ||
|
||
return $cashier_data; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure proper error handling in wepos_get_cashier_data_by_user_id
to return a more informative error message when the user data is not found or the user does not have the required roles.
- if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) {
+ if ( empty( $user_data ) ) {
+ return new WP_Error('user_not_found', 'The user with the provided ID does not exist.');
+ }
+ if ( empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) {
+ return new WP_Error('invalid_role', 'The user does not have the required roles.');
}
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.
* Get cashier data by user id. | |
* | |
* @param int $user_id | |
* | |
* @return array|\WP_Error | |
*/ | |
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | |
global $wpdb; | |
$user_data = get_userdata( $user_id ); | |
if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | |
return []; | |
} | |
$cashier_data = $wpdb->get_row( | |
$wpdb->prepare( | |
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'", | |
$user_id | |
) | |
); | |
if ( empty( $cashier_data ) ) { | |
return []; | |
} | |
return $cashier_data; | |
} | |
* Get cashier data by user id. | |
* | |
* @param int $user_id | |
* | |
* @return array|\WP_Error | |
*/ | |
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | |
global $wpdb; | |
$user_data = get_userdata( $user_id ); | |
if ( empty( $user_data ) ) { | |
return new WP_Error('user_not_found', 'The user with the provided ID does not exist.'); | |
} | |
if ( empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | |
return new WP_Error('invalid_role', 'The user does not have the required roles.'); | |
} | |
$cashier_data = $wpdb->get_row( | |
$wpdb->prepare( | |
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'", | |
$user_id | |
) | |
); | |
if ( empty( $cashier_data ) ) { | |
return []; | |
} | |
return $cashier_data; | |
} |
productLogs.forEach( productLog => { | ||
wepos.productIndexedDb.updateProduct( { | ||
id: Number( productLog.product_id ), | ||
title: productLog.product_title, | ||
type: productLog.product_type, | ||
sku: productLog.product_sku, | ||
price: productLog.product_price, | ||
stock: productLog.product_stock, | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using for...of
instead of forEach
for better performance and readability.
- productLogs.forEach( productLog => {
+ for (const productLog of productLogs) {
wepos.productIndexedDb.updateProduct({
id: Number(productLog.product_id),
title: productLog.product_title,
type: productLog.product_type,
sku: productLog.product_sku,
price: productLog.product_price,
stock: productLog.product_stock,
});
- });
+ }
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.
productLogs.forEach( productLog => { | |
wepos.productIndexedDb.updateProduct( { | |
id: Number( productLog.product_id ), | |
title: productLog.product_title, | |
type: productLog.product_type, | |
sku: productLog.product_sku, | |
price: productLog.product_price, | |
stock: productLog.product_stock, | |
} ); | |
} ); | |
for (const productLog of productLogs) { | |
wepos.productIndexedDb.updateProduct( { | |
id: Number( productLog.product_id ), | |
title: productLog.product_title, | |
type: productLog.product_type, | |
sku: productLog.product_sku, | |
price: productLog.product_price, | |
stock: productLog.product_stock, | |
} ); | |
} |
wepos.api.put( wepos.rest.root + wepos.rest.posversion + '/product/logs/' + counterId ) | ||
.fail( response => { | ||
alert( response.status + ' : ' + response.responseJSON.message ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace string concatenation with template literals and consider using a more user-friendly alert system like SweetAlert.
- wepos.api.put(wepos.rest.root + wepos.rest.posversion + '/product/logs/' + counterId)
+ wepos.api.put(`${wepos.rest.root}${wepos.rest.posversion}/product/logs/${counterId}`)
.fail(response => {
- alert(response.status + ' : ' + response.responseJSON.message);
+ Swal.fire({
+ icon: 'error',
+ title: 'Oops...',
+ text: `${response.status} : ${response.responseJSON.message}`,
+ });
});
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.
wepos.api.put( wepos.rest.root + wepos.rest.posversion + '/product/logs/' + counterId ) | |
.fail( response => { | |
alert( response.status + ' : ' + response.responseJSON.message ); | |
} ); | |
wepos.api.put(`${wepos.rest.root}${wepos.rest.posversion}/product/logs/${counterId}`) | |
.fail(response => { | |
Swal.fire({ | |
icon: 'error', | |
title: 'Oops...', | |
text: `${response.status} : ${response.responseJSON.message}`, | |
}); | |
}); |
function wepos_create_product_log_tables() { | ||
global $wpdb; | ||
|
||
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; | ||
|
||
$collate = $wpdb->get_charset_collate(); | ||
$tables = [ | ||
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` ( | ||
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | ||
`product_id` bigint unsigned NOT NULL, | ||
`product_title` text(255) NOT NULL, | ||
`product_type` varchar(100) NOT NULL, | ||
`product_sku` varchar(100) NULL, | ||
`product_price` decimal (19, | ||
4) NOT NULL DEFAULT 0.0000, | ||
`product_stock` bigint signed NULL, | ||
`counter_counts` bigint unsigned NULL DEFAULT 0, | ||
PRIMARY KEY (`id`) | ||
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | ||
|
||
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_log_counters` ( | ||
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | ||
`product_log_id` bigint unsigned NOT NULL, | ||
`counter_id` bigint unsigned NOT NULL, | ||
PRIMARY KEY (`id`), | ||
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE | ||
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | ||
]; | ||
|
||
// Include 'upgrade.php' if not included earlier. | ||
if ( ! function_exists( 'dbDelta' ) ) { | ||
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; | ||
} | ||
|
||
// Create tables through looping. | ||
foreach ( $tables as $key => $table ) { | ||
dbDelta( $table ); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider optimizing the data types and table creation logic.
- "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` (
+ "CREATE TABLE `{$wpdb->prefix}wepos_product_logs` (
- `product_title` text(255) NOT NULL,
+ `product_title` varchar(255) NOT NULL,
- `product_type` varchar(100) NOT NULL,
+ `product_type` varchar(50) NOT NULL,
- `product_sku` varchar(100) NULL,
+ `product_sku` varchar(50) NULL,
- `product_price` decimal (19, 4) NOT NULL DEFAULT 0.0000,
+ `product_price` decimal(10, 2) NOT NULL DEFAULT 0.0000,
- `product_stock` bigint signed NULL,
+ `product_stock` int signed NULL,
- `counter_counts` bigint unsigned NULL DEFAULT 0,
+ `counter_counts` int unsigned NULL DEFAULT 0,
- FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE
+ FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE ON UPDATE CASCADE
);
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.
function wepos_create_product_log_tables() { | |
global $wpdb; | |
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; | |
$collate = $wpdb->get_charset_collate(); | |
$tables = [ | |
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` ( | |
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | |
`product_id` bigint unsigned NOT NULL, | |
`product_title` text(255) NOT NULL, | |
`product_type` varchar(100) NOT NULL, | |
`product_sku` varchar(100) NULL, | |
`product_price` decimal (19, | |
4) NOT NULL DEFAULT 0.0000, | |
`product_stock` bigint signed NULL, | |
`counter_counts` bigint unsigned NULL DEFAULT 0, | |
PRIMARY KEY (`id`) | |
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | |
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_log_counters` ( | |
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | |
`product_log_id` bigint unsigned NOT NULL, | |
`counter_id` bigint unsigned NOT NULL, | |
PRIMARY KEY (`id`), | |
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE | |
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | |
]; | |
// Include 'upgrade.php' if not included earlier. | |
if ( ! function_exists( 'dbDelta' ) ) { | |
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; | |
} | |
// Create tables through looping. | |
foreach ( $tables as $key => $table ) { | |
dbDelta( $table ); | |
} | |
} | |
function wepos_create_product_log_tables() { | |
global $wpdb; | |
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; | |
$collate = $wpdb->get_charset_collate(); | |
$tables = [ | |
"CREATE TABLE `{$wpdb->prefix}wepos_product_logs` ( | |
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | |
`product_id` bigint unsigned NOT NULL, | |
`product_title` varchar(255) NOT NULL, | |
`product_type` varchar(50) NOT NULL, | |
`product_sku` varchar(50) NULL, | |
`product_price` decimal(10, 2) NOT NULL DEFAULT 0.0000, | |
`product_stock` int signed NULL, | |
`counter_counts` int unsigned NULL DEFAULT 0, | |
PRIMARY KEY (`id`) | |
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | |
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_log_counters` ( | |
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | |
`product_log_id` bigint unsigned NOT NULL, | |
`counter_id` bigint unsigned NOT NULL, | |
PRIMARY KEY (`id`), | |
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE ON UPDATE CASCADE | |
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | |
]; | |
// Include 'upgrade.php' if not included earlier. | |
if ( ! function_exists( 'dbDelta' ) ) { | |
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; | |
} | |
// Create tables through looping. | |
foreach ( $tables as $key => $table ) { | |
dbDelta( $table ); | |
} | |
} |
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (1)
- assets/src/utils/productIndexedDb.js (1 hunks)
Additional context used
Biome
assets/src/utils/productIndexedDb.js
[error] 85-94: Prefer for...of instead of forEach.
[error] 153-153: This let declares a variable that is only assigned once.
[error] 156-156: This let declares a variable that is only assigned once.
[error] 158-158: This let declares a variable that is only assigned once.
assets/src/utils/productIndexedDb.js
Outdated
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
let condition = new RegExp( searchKeyword, 'i' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor to use const
for variables that are assigned only once.
- let condition = new RegExp( searchKeyword, 'i' );
- let products = event.target.result;
- let result = products.filter( ( product ) => {
+ const condition = new RegExp( searchKeyword, 'i' );
+ const products = event.target.result;
+ const result = products.filter( ( product ) => {
Also applies to: 156-156, 158-158
Committable suggestion was skipped due low confidence.
Tools
Biome
[error] 153-153: This let declares a variable that is only assigned once.
Scenario: DB Error:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Outside diff range and nitpick comments (3)
includes/Admin/Updates.php (1)
Line range hint
1-138
: Consider enhancing error handling and logging.The overall structure of the
Updates
class is solid and follows good practices. However, consider the following improvements:
- Add error handling in the
perform_updates
method, especially when including upgrade files.- Implement logging for successful updates and any errors encountered during the update process.
These enhancements would improve the robustness of the update process and make debugging easier in case of issues.
Here's a suggested modification to the
perform_updates
method:public function perform_updates() { if ( ! $this->is_needs_update() ) { return; } $installed_version = get_option( 'we_pos_version' ); foreach ( self::$updates as $version => $path ) { if ( version_compare( $installed_version, $version, '<' ) ) { $full_path = WEPOS_INCLUDES . '/admin/updates/' . $path; if ( file_exists( $full_path ) ) { try { include $full_path; update_option( 'we_pos_version', $version ); error_log( "WePOS: Successfully updated to version $version" ); } catch ( Exception $e ) { error_log( "WePOS: Error updating to version $version: " . $e->getMessage() ); // Consider how to handle failed updates. Maybe break the loop or continue? } } else { error_log( "WePOS: Update file not found for version $version" ); } } } update_option( 'we_pos_version', WEPOS_VERSION ); $url = wp_unslash( add_query_arg( [ 'page' => 'wepos' ], admin_url( 'admin.php' ) ) ); $location = esc_url( $url ) . '#/settings'; wp_redirect( $location ); exit(); }includes/Installer.php (1)
34-34
: Ensure consistency in@since
version tagsThe docblocks use both
@since WEPOS_SINCE
and@since WEPOS_LITE_SINCE
. Please ensure that the correct version constant is used consistently throughout the codebase to avoid confusion.Also applies to: 85-85
assets/src/frontend/components/Home.vue (1)
39-41
: Improve accessibility by addingaria-label
to the refresh buttonTo enhance accessibility for users relying on screen readers, consider adding an
aria-label
attribute to the refresh button to clearly describe its purpose.
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (6)
- assets/src/frontend/components/Home.vue (8 hunks)
- includes/Admin/Updates.php (2 hunks)
- includes/Installer.php (3 hunks)
- includes/REST/Manager.php (2 hunks)
- includes/functions.php (1 hunks)
- wepos.php (2 hunks)
Additional comments not posted (12)
includes/Admin/Updates.php (4)
2-2
: LGTM: Namespace declaration added.The addition of the namespace
WeDevs\WePOS\Admin
is a good practice. It correctly reflects the file's location in the project structure and helps in organizing the code.
5-5
: LGTM: ABSPATH check added for security.The addition of the ABSPATH check is a good security practice. It prevents direct access to the file, which is important for maintaining the integrity of the plugin.
Line range hint
1-138
: Summary: Good improvements, with suggestions for further enhancements.The changes made to this file are minimal but significant:
- Addition of namespace improves code organization.
- ABSPATH check enhances security.
- New update entry (1.3.0) indicates ongoing development.
The overall structure and functionality of the
Updates
class remain solid. However, consider the suggested improvements for error handling and logging to further enhance the robustness of the update process.Overall, these changes contribute positively to the maintainability and security of the plugin.
23-23
: Verify the upgrade file and version number.The addition of the new entry for version 1.3.0 is good. However, please ensure that:
- The file 'upgrade-1.3.0.php' exists in the correct directory.
- The version number 1.3.0 aligns with the intended release version for this PR.
Run the following script to verify the upgrade file's existence:
Verification successful
Upgrade File and Version Number Verified Successfully.
The file
upgrade-1.3.0.php
exists in theincludes/Admin/Updates/
directory and contains the appropriate update functions for version1.3.0
. The version number aligns with the intended release for this PR.Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify the existence of the upgrade file and check its content # Test 1: Check if the upgrade file exists if fd -t f "upgrade-1.3.0.php" --exec echo "Upgrade file found: {}"; then echo "Upgrade file exists." else echo "Error: Upgrade file 'upgrade-1.3.0.php' not found." fi # Test 2: Check the content of the upgrade file fd -t f "upgrade-1.3.0.php" --exec cat {}Length of output: 2029
includes/REST/Manager.php (4)
2-5
: LGTM: Improved code organization and security.The addition of the namespace declaration and the ABSPATH check enhances code organization and security. These are good practices in WordPress plugin development.
Line range hint
1-214
: Suggestion: Ensure compatibility with IndexedDB integration.While the changes in this file are minimal, it's crucial to verify that the existing methods, particularly
product_response
,tax_response
,category_response
, andvalidate_item_stock_before_order
, are fully compatible with the new IndexedDB integration mentioned in the PR objectives.Please conduct thorough testing to ensure these methods work correctly with the IndexedDB integration, especially for large product lists. Consider the following test scenarios:
- Test
product_response
with a large number of products (e.g., 3000+) to ensure it handles the load efficiently.- Verify that
tax_response
andcategory_response
work correctly with products stored in IndexedDB.- Test
validate_item_stock_before_order
with products fetched from IndexedDB to ensure stock validation remains accurate.If possible, provide test results or update test cases to cover these scenarios.
Line range hint
1-214
: Summary: Changes support PR objectives, but further verification needed.The changes to this file, particularly the addition of the ProductsLogController, align well with the PR objectives of improving product management. However, given the scope of the changes described in the PR (IndexedDB integration and improved frontend product search), there are a few key points that require attention:
- Ensure that the newly added ProductsLogController is fully implemented and tested.
- Verify that all existing methods in this file, especially those dealing with product responses and stock validation, are compatible with the new IndexedDB integration.
- Consider adding or updating unit tests to cover the interaction between these REST endpoints and the IndexedDB storage.
These verifications are crucial to ensure that the enhancements don't introduce any regressions in the existing functionality while supporting the new features.
28-34
: LGTM: Added ProductsLogController to class map.The addition of the ProductsLogController to the class map is consistent with existing entries and aligns with the PR objective of improving product management.
Please ensure that the
ProductsLogController.php
file exists and is correctly implemented. Run the following script to verify:Verification successful
Verified: ProductsLogController has been successfully added.
The
ProductsLogController
has been correctly added to the class map, and the corresponding file exists with the expected structure and necessary methods.Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify the existence and basic structure of ProductsLogController.php # Check if the file exists if [ ! -f "includes/REST/ProductsLogController.php" ]; then echo "Error: ProductsLogController.php does not exist in the expected location." exit 1 fi # Check for the class definition if ! grep -q "class ProductsLogController" "includes/REST/ProductsLogController.php"; then echo "Error: ProductsLogController class is not defined in the file." exit 1 fi # Check for the register_routes method if ! grep -q "public function register_routes()" "includes/REST/ProductsLogController.php"; then echo "Warning: register_routes() method is not found in ProductsLogController. Ensure it's implemented." fi echo "ProductsLogController.php exists and contains basic expected structure."Length of output: 375
wepos.php (4)
Line range hint
1-465
: Overall approval with suggestions for verificationThe changes in this file appear to be part of a larger update to the wePOS plugin. The main modifications include:
- Refactoring the deactivation process to use a dedicated Uninstaller class.
- Adding new ProductsLog and Ajax functionalities.
- Updating the plugin version to 1.2.8.
These changes seem to improve the code structure and add new features. However, to ensure the quality and stability of the update, please:
- Thoroughly test the new Uninstaller class to ensure it correctly handles all deactivation tasks.
- Provide documentation for the new ProductsLog and Ajax functionalities.
- Ensure comprehensive testing of the new features and their integration with existing code.
- Verify version number consistency across all plugin files and update the changelog accordingly.
286-288
: Approve change, but verify Uninstaller implementationThe refactoring of the
deactivate
method to use a dedicatedUninstaller
class is a good practice for better code organization. However, we need to ensure that theUninstaller
class properly handles all deactivation tasks, including those previously done in this method (such as removing user capabilities).Please run the following script to verify the
Uninstaller
class implementation:#!/bin/bash # Description: Verify the Uninstaller class implementation # Test: Check if the Uninstaller class exists and has a run method ast-grep --lang php --pattern 'class Uninstaller { $$$ public function run() { $$$ } $$$ }' # Test: Check if the Uninstaller class handles user capability removal rg --type php 'remove_cap|remove_role|delete_option.*user_roles' -A 5 -B 5
Line range hint
18-18
: Approve version update, verify consistency and documentationThe update of the plugin version to 1.2.8 is noted and approved. To ensure a smooth release:
- Please verify that this version number is consistent across all relevant files (e.g., README, changelog).
- Ensure that all changes for this version are properly documented in the changelog.
- Confirm that all new features and changes have been thoroughly tested.
Please run the following script to verify version consistency and documentation:
#!/bin/bash # Description: Verify version consistency and documentation # Test: Check for version number in other files rg --type-add 'text:*.{md,txt}' --type php --type text '1\.2\.8' # Test: Look for a changelog entry for this version rg --type-add 'text:*.{md,txt}' --type text '## \[1\.2\.8\]' -A 10 # Test: Check for any TODO or FIXME comments that might need addressing before release rg --type php 'TODO|FIXME'
335-336
: Approve additions, but request more informationThe addition of 'products_log' and 'ajax' classes to the container is approved. These additions likely enhance the functionality of the POS system. However, to ensure they are properly integrated:
- Could you provide more information about the purpose and implementation of these new classes?
- Have these additions been thoroughly tested to ensure they don't introduce any performance issues or conflicts with existing functionality?
Please run the following script to verify the implementation of these new classes:
/** | ||
* Get cashier data by user id. | ||
* | ||
* @param int $user_id | ||
* | ||
* @return array|\WP_Error | ||
*/ | ||
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | ||
global $wpdb; | ||
|
||
$user_data = get_userdata( $user_id ); | ||
|
||
if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | ||
return []; | ||
} | ||
|
||
$cashier_data = $wpdb->get_row( | ||
$wpdb->prepare( | ||
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'", | ||
$user_id | ||
) | ||
); | ||
|
||
if ( empty( $cashier_data ) ) { | ||
return []; | ||
} | ||
|
||
return $cashier_data; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling and add database error checks
The function looks good overall, especially with the use of prepared statements for the database query. However, there are a few improvements we can make:
- Implement more informative error handling as suggested in the past review.
- Add checks for potential database errors and table existence.
Here's a suggested implementation with improved error handling and database checks:
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) {
global $wpdb;
$user_data = get_userdata( $user_id );
- if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) {
- return [];
+ if ( empty( $user_data ) ) {
+ return new WP_Error('user_not_found', 'The user with the provided ID does not exist.');
+ }
+ if ( empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) {
+ return new WP_Error('invalid_role', 'The user does not have the required roles.');
}
+ // Check if the table exists
+ $table_name = $wpdb->prefix . 'wepos_login';
+ if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
+ return new WP_Error('table_not_found', 'The required database table does not exist.');
+ }
+
$cashier_data = $wpdb->get_row(
$wpdb->prepare(
- "SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'",
+ "SELECT user_id, outlet_id, counter_id, is_logged_in FROM $table_name WHERE user_id=%d AND `is_logged_in`='1'",
$user_id
)
);
+ if ( $wpdb->last_error ) {
+ return new WP_Error('database_error', 'A database error occurred: ' . $wpdb->last_error);
+ }
+
if ( empty( $cashier_data ) ) {
- return [];
+ return new WP_Error('no_active_session', 'No active cashier session found for this user.');
}
return $cashier_data;
}
These changes will provide more informative error messages and handle potential database issues.
The overall structure of the function and the use of prepared statements for the database query are good practices. These improvements will make the function more robust and easier to debug.
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.
/** | |
* Get cashier data by user id. | |
* | |
* @param int $user_id | |
* | |
* @return array|\WP_Error | |
*/ | |
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | |
global $wpdb; | |
$user_data = get_userdata( $user_id ); | |
if ( empty( $user_data ) || empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | |
return []; | |
} | |
$cashier_data = $wpdb->get_row( | |
$wpdb->prepare( | |
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM {$wpdb->prefix}wepos_login WHERE user_id=%d AND `is_logged_in`='1'", | |
$user_id | |
) | |
); | |
if ( empty( $cashier_data ) ) { | |
return []; | |
} | |
return $cashier_data; | |
} | |
/** | |
* Get cashier data by user id. | |
* | |
* @param int $user_id | |
* | |
* @return array|\WP_Error | |
*/ | |
function wepos_get_cashier_data_by_user_id( $user_id = 0 ) { | |
global $wpdb; | |
$user_data = get_userdata( $user_id ); | |
if ( empty( $user_data ) ) { | |
return new WP_Error('user_not_found', 'The user with the provided ID does not exist.'); | |
} | |
if ( empty( array_intersect( [ 'administrator', 'cashier' ], $user_data->roles ) ) ) { | |
return new WP_Error('invalid_role', 'The user does not have the required roles.'); | |
} | |
// Check if the table exists | |
$table_name = $wpdb->prefix . 'wepos_login'; | |
if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) { | |
return new WP_Error('table_not_found', 'The required database table does not exist.'); | |
} | |
$cashier_data = $wpdb->get_row( | |
$wpdb->prepare( | |
"SELECT user_id, outlet_id, counter_id, is_logged_in FROM $table_name WHERE user_id=%d AND `is_logged_in`='1'", | |
$user_id | |
) | |
); | |
if ( $wpdb->last_error ) { | |
return new WP_Error('database_error', 'A database error occurred: ' . $wpdb->last_error); | |
} | |
if ( empty( $cashier_data ) ) { | |
return new WP_Error('no_active_session', 'No active cashier session found for this user.'); | |
} | |
return $cashier_data; | |
} |
public function create_tables() { | ||
global $wpdb; | ||
|
||
include_once ABSPATH . 'wp-admin/includes/upgrade.php'; | ||
|
||
$collate = $wpdb->get_charset_collate(); | ||
$tables = [ | ||
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` ( | ||
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | ||
`product_id` bigint unsigned NOT NULL, | ||
`product_title` text(255) NOT NULL, | ||
`product_type` varchar(100) NOT NULL, | ||
`product_sku` varchar(100) NULL, | ||
`product_price` decimal (19, | ||
4) NOT NULL DEFAULT 0.0000, | ||
`product_stock` bigint signed NULL, | ||
`counter_counts` bigint unsigned NULL DEFAULT 0, | ||
PRIMARY KEY (`id`) | ||
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | ||
|
||
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_log_counters` ( | ||
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | ||
`product_log_id` bigint unsigned NOT NULL, | ||
`counter_id` bigint unsigned NOT NULL, | ||
PRIMARY KEY (`id`), | ||
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE | ||
) ENGINE=InnoDB AUTO_INCREMENT=1 {$collate};", | ||
]; | ||
|
||
foreach ( $tables as $key => $table ) { | ||
dbDelta( $table ); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for dbDelta
execution
It is advisable to handle and log any errors that may occur during the execution of dbDelta
when creating tables. This will aid in debugging and ensure that any issues during table creation are promptly identified.
`product_price` decimal (19, | ||
4) NOT NULL DEFAULT 0.0000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the syntax for the DECIMAL
data type of product_price
The DECIMAL
data type definition should not include a line break between the precision and scale. It should be defined as DECIMAL(19, 4)
without any interruption.
Apply this diff to fix the syntax:
- `product_price` decimal (19,
- 4) NOT NULL DEFAULT 0.0000,
+ `product_price` decimal(19, 4) NOT NULL DEFAULT 0.0000,
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.
`product_price` decimal (19, | |
4) NOT NULL DEFAULT 0.0000, | |
`product_price` decimal(19, 4) NOT NULL DEFAULT 0.0000, |
"CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wepos_product_logs` ( | ||
`id` bigint unsigned NOT NULL AUTO_INCREMENT, | ||
`product_id` bigint unsigned NOT NULL, | ||
`product_title` text(255) NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove length specification from 'TEXT' data type for product_title
The TEXT
data type in MySQL does not accept a length parameter. Specifying text(255)
is invalid syntax. You should remove the length specification.
Apply this diff to correct the syntax:
- `product_title` text(255) NOT NULL,
+ `product_title` text NOT NULL,
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.
`product_title` text(255) NOT NULL, | |
`product_title` text NOT NULL, |
PRIMARY KEY (`id`), | ||
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure proper syntax for the FOREIGN KEY
constraint
To maintain correct SQL syntax, remove the comma after the PRIMARY KEY
definition since the FOREIGN KEY
constraint follows it as the last item in the table definition.
Apply this diff to correct the syntax:
- PRIMARY KEY (`id`),
+ PRIMARY KEY (`id`)
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.
PRIMARY KEY (`id`), | |
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE | |
PRIMARY KEY (`id`) | |
FOREIGN KEY (product_log_id) REFERENCES {$wpdb->prefix}wepos_product_logs (id) ON DELETE CASCADE |
if ( ! this.productsStorageUpdatedOn || this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure correct comparison by parsing productsStorageUpdatedOn
to a number
The value of productsStorageUpdatedOn
retrieved from localStorage
is a string. Comparing it directly to a number may lead to incorrect results due to type coercion. To ensure proper comparison, parse productsStorageUpdatedOn
to an integer before the comparison.
Apply this diff to fix the comparison:
isProductsStorageUpdateRequired() {
if ( ! this.productsStorageUpdatedOn ||
- this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) {
+ parseInt(this.productsStorageUpdatedOn, 10) < dayjs().subtract( 7, 'days' ).unix() ) {
return true;
}
return false;
}
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.
if ( ! this.productsStorageUpdatedOn || this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) { | |
return true; | |
} | |
if ( ! this.productsStorageUpdatedOn || | |
parseInt(this.productsStorageUpdatedOn, 10) < dayjs().subtract( 7, 'days' ).unix() ) { | |
return true; | |
} |
async beforeCreate() { | ||
const dbName = 'ProductsDB'; | ||
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName ); | ||
|
||
if ( ! isExistsProductsDB ) { | ||
wepos.productIndexedDb.createProductsDB(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure browser compatibility when using indexedDB.databases()
The indexedDB.databases()
method is experimental and not supported in all browsers, such as Safari and Internet Explorer. Relying on it may cause runtime errors in unsupported browsers. Consider checking for its availability before using it or providing a fallback mechanism. Alternatively, attempt to open the database and handle exceptions if it doesn't exist.
Apply this diff to handle browser compatibility:
async beforeCreate() {
+ if ('databases' in indexedDB) {
const dbName = 'ProductsDB';
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName );
if ( ! isExistsProductsDB ) {
wepos.productIndexedDb.createProductsDB();
}
+ } else {
+ // Fallback for browsers without indexedDB.databases()
+ const request = indexedDB.open('ProductsDB');
+ request.onupgradeneeded = (event) => {
+ wepos.productIndexedDb.createProductsDB();
+ };
+ request.onsuccess = (event) => {
+ // Database exists
+ };
+ request.onerror = (event) => {
+ // Handle errors
+ };
+ }
}
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.
async beforeCreate() { | |
const dbName = 'ProductsDB'; | |
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName ); | |
if ( ! isExistsProductsDB ) { | |
wepos.productIndexedDb.createProductsDB(); | |
} | |
async beforeCreate() { | |
if ('databases' in indexedDB) { | |
const dbName = 'ProductsDB'; | |
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName ); | |
if ( ! isExistsProductsDB ) { | |
wepos.productIndexedDb.createProductsDB(); | |
} | |
} else { | |
// Fallback for browsers without indexedDB.databases() | |
const request = indexedDB.open('ProductsDB'); | |
request.onupgradeneeded = (event) => { | |
wepos.productIndexedDb.createProductsDB(); | |
}; | |
request.onsuccess = (event) => { | |
// Database exists | |
}; | |
request.onerror = (event) => { | |
// Handle errors | |
}; | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Outside diff range and nitpick comments (3)
assets/src/utils/productIndexedDb.js (3)
7-8
: Enhance error messages for better debugging.Currently, the error messages in
request.onerror
are generic. Including the error code or message can aid in debugging.Example:
-7 console.error( "An error occurred with IndexedDB" ); -8 console.error( event ); +7 console.error( `IndexedDB Error: ${event.target.errorCode}` );Also applies to: 31-32, 66-67, 205-206, 233-234
1-255
: Ensure consistent return types across all methods.Some methods (
getAllProducts
,getProductsBySearchKeyword
,updateProduct
) return Promises, while others (insertProduct
,insertProducts
,deleteProductById
,deleteAllProducts
,createProductsDB
) do not. For consistency and better asynchronous handling, consider returning Promises from all methods.This allows callers to use
async/await
or.then()
uniformly across all database operations.
42-44
: Review the necessity of both logging and rejecting errors.In your error handlers, you log the error to the console and then reject with the same error message. This might be redundant. Decide whether both actions are necessary or if one suffices.
Also applies to: 77-79, 114-117, 146-147, 184-187, 216-218, 244-246
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (1)
- assets/src/utils/productIndexedDb.js (1 hunks)
Additional comments not posted (1)
assets/src/utils/productIndexedDb.js (1)
193-195
: Confirm 'updateProduct' properly resolves the Promise.In the
updateProduct
method, ensure that the Promise is resolved upon successful completion of theput
operation.
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error: 'reject' is not defined in these functions.
In the methods createProductsDB
, insertProduct
, insertProducts
, deleteProductById
, and deleteAllProducts
, you are calling reject(event);
within the request.onerror
handler. However, reject
is not defined in these functions since they are not returning Promises. This will cause a ReferenceError
at runtime.
To fix this issue, you have two options:
- Remove the
reject(event);
calls if you do not intend to use Promises. - Refactor the functions to return Promises, allowing you to use
reject
.
Option 1: Remove reject(event);
calls
6 request.onerror = ( event ) => {
7 console.error( "An error occurred with IndexedDB" );
8 console.error( event );
9
-10 reject( event );
11 };
Apply similar changes to the other functions where reject(event);
is called without a defined reject
.
Option 2: Refactor functions to return Promises
Example for insertProduct
:
26 insertProduct( product = {} ) {
+27 return new Promise( ( resolve, reject ) => {
28 let db;
29 const request = indexedDB.open( "ProductsDB", 1 );
30 request.onerror = ( event ) => {
31 console.error( "An error occurred with IndexedDB" );
32 console.error( event );
33 reject( event );
34 };
35
36 request.onsuccess = ( event ) => {
37 db = event.target.result;
38
39 db.onerror = ( event ) => {
40 console.error( `wePOS IndexedDB Database error: \
${event.target.error.code} - ${event.target.error.message}` );
41
42 reject( `wePOS IndexedDB Database error: \
${event.target.error.code} - ${event.target.error.message}` );
43 };
44
45 const transaction = db.transaction( ["ProductsDB"], "readwrite" );
46 const objectStore = transaction.objectStore( "ProductsDB" );
47
48 objectStore.add( product );
+49 resolve();
50 };
+51 });
52 },
Apply this pattern to the other functions to ensure reject
is defined.
Also applies to: 30-35, 65-70, 204-209, 232-237
objectStore.add( { | ||
id: product.id, | ||
name: product.name, | ||
type: product.type, | ||
sku: product.sku, | ||
price: product.price, | ||
stock: product.stock_quantity | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid unnecessary object reconstruction when adding products.
In insertProduct
and insertProducts
, you're creating a new object by mapping properties from product
. Since the product
object likely already contains the necessary properties, you can add it directly to the object store.
Example for insertProduct
:
50 objectStore.add( {
51 id: product.id,
52 name: product.name,
53 type: product.type,
54 sku: product.sku,
55 price: product.price,
56 stock: product.stock_quantity
57 } );
+ // Ensure 'product' has the correct structure
+ // objectStore.add( product );
Note: Before making this change, verify that the product
object aligns with the structure expected by the object store.
Also applies to: 86-93
products.forEach( ( product ) => { | ||
objectStore.add( { | ||
id: product.id, | ||
name: product.name, | ||
type: product.type, | ||
sku: product.sku, | ||
price: product.price, | ||
stock: product.stock_quantity | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle errors during bulk product insertion.
In insertProducts
, if an error occurs while adding a product (e.g., due to a duplicate key), it may fail silently. Consider handling errors for each add
operation.
products.forEach( ( product ) => {
const request = objectStore.add( product );
request.onerror = ( event ) => {
console.error( `Error adding product ID ${product.id}: \
${event.target.error.message}` );
// Optional: handle the error, e.g., retry or skip
};
});
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify and centralize error handling to reduce duplication.
The db.onerror
handler is repeated in multiple functions with identical code. Consider refactoring this into a shared error handling function or determine if setting db.onerror
is necessary in each function.
Example:
function handleDbError( event ) {
console.error( `wePOS IndexedDB Database error: \
${event.target.error.code} - ${event.target.error.message}` );
// Optional: handle rejection if within a Promise
}
Then, in your methods:
db.onerror = handleDbError;
Also applies to: 75-80, 113-118, 145-148, 183-188, 215-219, 243-247
assets/src/utils/productIndexedDb.js
Outdated
let condition = new RegExp( searchKeyword, 'i' ); | ||
|
||
objectStore.getAll().onsuccess = ( event ) => { | ||
let products = event.target.result; | ||
|
||
let result = products.filter( ( product ) => { | ||
return condition.test( product.name ) || condition.test( product.id ) || condition.test( product.sku ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use 'const' for variables that are not reassigned.
Variables condition
, products
, and result
are not reassigned after initialization. Using const
enhances code readability and prevents accidental reassignments.
153 - let condition = new RegExp( searchKeyword, 'i' );
153 + const condition = new RegExp( searchKeyword, 'i' );
156 - let products = event.target.result;
156 + const products = event.target.result;
158 - let result = products.filter( ( product ) => {
158 + const result = products.filter( ( product ) => {
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.
let condition = new RegExp( searchKeyword, 'i' ); | |
objectStore.getAll().onsuccess = ( event ) => { | |
let products = event.target.result; | |
let result = products.filter( ( product ) => { | |
return condition.test( product.name ) || condition.test( product.id ) || condition.test( product.sku ); | |
} ); | |
const condition = new RegExp( searchKeyword, 'i' ); | |
objectStore.getAll().onsuccess = ( event ) => { | |
const products = event.target.result; | |
const result = products.filter( ( product ) => { | |
return condition.test( product.name ) || condition.test( product.id ) || condition.test( product.sku ); | |
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (3)
assets/src/utils/productIndexedDb.js (3)
98-128
: StreamlinegetAllProducts
method for better code organizationThe
getAllProducts
method is structured well, but there are opportunities for improvement:
- There's redundant error handling code.
- The
db.onerror
handler is set but never used in this context.Consider the following refactoring:
getAllProducts() { return new Promise((resolve, reject) => { const request = indexedDB.open("ProductsDB", 1); request.onerror = (event) => { console.error("An error occurred with IndexedDB"); reject(event); }; request.onsuccess = (event) => { const db = event.target.result; const transaction = db.transaction(["ProductsDB"], "readonly"); const objectStore = transaction.objectStore("ProductsDB"); const getAllRequest = objectStore.getAll(); getAllRequest.onerror = (event) => { reject(`Error retrieving products: ${event.target.error}`); }; getAllRequest.onsuccess = (event) => { resolve(event.target.result); }; }; }); },This refactoring:
- Removes the redundant
db.onerror
handler.- Uses a read-only transaction since we're only reading data.
- Adds specific error handling for the
getAll
request.- Simplifies the success handler.
The overall structure of returning a Promise and using IndexedDB API is correct.
130-174
: OptimizegetProductsBySearchKeyword
method for better performanceThe
getProductsBySearchKeyword
method is well-structured, but there are opportunities for optimization:
- It currently iterates through all products, which could be slow for large datasets.
- The search is performed in memory, which might not be efficient for very large datasets.
Consider the following optimizations:
- Use an index for searching if possible. If you frequently search by name or SKU, create a specific index for these fields.
- Limit the number of results returned to improve performance for large datasets.
- Use a more efficient search algorithm if possible, such as a prefix search or full-text search if supported by your IndexedDB implementation.
Here's a potential optimization using an index:
getProductsBySearchKeyword(searchKeyword = "") { return new Promise((resolve, reject) => { const request = indexedDB.open("ProductsDB", 1); request.onerror = (event) => { console.error("An error occurred with IndexedDB"); reject(event); }; request.onsuccess = (event) => { const db = event.target.result; const transaction = db.transaction(["ProductsDB"], "readonly"); const objectStore = transaction.objectStore("ProductsDB"); const nameIndex = objectStore.index("name"); const range = IDBKeyRange.bound(searchKeyword, searchKeyword + '\uffff'); const request = nameIndex.openCursor(range); const results = []; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { results.push(cursor.value); cursor.continue(); } else { resolve(results); } }; request.onerror = (event) => { reject(`Error searching products: ${event.target.error}`); }; }; }); },This optimization:
- Uses the "name" index for searching, which can be faster for large datasets.
- Uses a key range to find products that start with the search keyword.
- Still allows for case-insensitive search by modifying the
range
creation if needed.The overall structure and error handling are good. These optimizations can help improve performance for larger datasets.
176-206
: StreamlineupdateProduct
method for better code organizationThe
updateProduct
method is structured well, but there are opportunities for improvement:
- There's redundant error handling code.
- The
db.onerror
handler is set but never used in this context.Consider the following refactoring:
updateProduct(product) { return new Promise((resolve, reject) => { const request = indexedDB.open("ProductsDB", 1); request.onerror = (event) => { console.error("An error occurred with IndexedDB"); reject(event); }; request.onsuccess = (event) => { const db = event.target.result; const transaction = db.transaction(["ProductsDB"], "readwrite"); const objectStore = transaction.objectStore("ProductsDB"); const updateRequest = objectStore.put(product); updateRequest.onerror = (event) => { reject(`Error updating product: ${event.target.error}`); }; updateRequest.onsuccess = (event) => { resolve(event.target.result); }; }; }); },This refactoring:
- Removes the redundant
db.onerror
handler.- Adds specific error handling for the
put
request.- Simplifies the success handler.
The overall structure of returning a Promise and using IndexedDB API is correct.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- assets/src/frontend/components/Home.vue (9 hunks)
- assets/src/utils/productIndexedDb.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- assets/src/frontend/components/Home.vue
createProductsDB() { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
} | ||
|
||
// This method is only invoked after changing IndexedDB version. | ||
request.onupgradeneeded = ( event ) => { | ||
db = event.target.result; | ||
const objectStore = db.createObjectStore( "ProductsDB", { keyPath: "id" } ); | ||
|
||
objectStore.createIndex( "name", "name", { unique: false } ); | ||
objectStore.createIndex( "type", "type", { unique: false } ); | ||
objectStore.createIndex( "sku", "sku", { unique: false } ); | ||
objectStore.createIndex( "price", "price", { unique: false } ); | ||
objectStore.createIndex( "stock", "stock", { unique: false } ); | ||
}; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix error handling in createProductsDB
method
The createProductsDB
method has an issue in its error handling:
- The
reject
function is called in theonerror
callback, but it's not defined as this method doesn't return a Promise. - There's no success callback to confirm the database creation.
To resolve these issues:
- Remove the
reject(event);
call or refactor the method to return a Promise. - Add an
onsuccess
handler to confirm the database creation.
Here's a suggested implementation:
createProductsDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
console.error(event);
reject(event);
};
request.onsuccess = (event) => {
console.log("ProductsDB created successfully");
resolve(event.target.result);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
const objectStore = db.createObjectStore("ProductsDB", { keyPath: "id" });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("type", "type", { unique: false });
objectStore.createIndex("sku", "sku", { unique: false });
objectStore.createIndex("price", "price", { unique: false });
objectStore.createIndex("stock", "stock", { unique: false });
};
});
},
This implementation wraps the database creation in a Promise, properly handles errors, and confirms successful creation.
insertProduct( product = {} ) { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.add( { | ||
id: product.id, | ||
name: product.name, | ||
type: product.type, | ||
sku: product.sku, | ||
price: product.price, | ||
stock: product.stock_quantity | ||
} ); | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor insertProduct
method for better error handling and Promise support
The insertProduct
method has several issues:
- It uses
reject
without being in a Promise context. - It doesn't return a Promise, making it difficult to know when the operation is complete.
- There's unnecessary object reconstruction when adding the product.
Here's a suggested refactoring:
insertProduct(product = {}) {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
reject(event);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["ProductsDB"], "readwrite");
const objectStore = transaction.objectStore("ProductsDB");
const addRequest = objectStore.add(product);
addRequest.onerror = (event) => {
reject(`Error adding product: ${event.target.error}`);
};
addRequest.onsuccess = (event) => {
resolve(event.target.result);
};
};
});
},
This refactoring:
- Wraps the operation in a Promise for better async handling.
- Simplifies error handling.
- Directly adds the
product
object without reconstruction. - Provides proper resolution or rejection based on the operation's outcome.
Consider applying similar refactoring to other methods in this file for consistency and improved error handling.
insertProducts( products = [] ) { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
products.forEach( ( product ) => { | ||
objectStore.add( { | ||
id: product.id, | ||
name: product.name, | ||
type: product.type, | ||
sku: product.sku, | ||
price: product.price, | ||
stock: product.stock_quantity | ||
} ); | ||
} ); | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve insertProducts
method for better error handling and Promise support
The insertProducts
method has several issues:
- It uses
reject
without being in a Promise context. - It doesn't return a Promise, making it difficult to know when all insertions are complete.
- There's no error handling for individual product insertions.
Here's a suggested refactoring:
insertProducts(products = []) {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
reject(event);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["ProductsDB"], "readwrite");
const objectStore = transaction.objectStore("ProductsDB");
const insertPromises = products.map(product =>
new Promise((resolveProduct, rejectProduct) => {
const addRequest = objectStore.add(product);
addRequest.onerror = (event) => rejectProduct(`Error adding product ${product.id}: ${event.target.error}`);
addRequest.onsuccess = () => resolveProduct();
})
);
Promise.all(insertPromises)
.then(() => resolve())
.catch((error) => reject(error));
transaction.oncomplete = () => resolve();
transaction.onerror = (event) => reject(event);
};
});
},
This refactoring:
- Wraps the operation in a Promise for better async handling.
- Handles errors for both the database opening and individual product insertions.
- Uses
Promise.all
to track all insertions and resolve when all are complete. - Provides proper resolution or rejection based on the operation's outcome.
Consider applying similar refactoring to other methods in this file for consistency and improved error handling.
deleteProductById( id ) { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.delete( id ); | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor deleteProductById
method for better error handling and Promise support
The deleteProductById
method has several issues:
- It doesn't return a Promise, making it difficult to know when the operation is complete.
- It uses
reject
without being in a Promise context. - There's no success handling to confirm the deletion.
Here's a suggested refactoring:
deleteProductById(id) {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
reject(event);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["ProductsDB"], "readwrite");
const objectStore = transaction.objectStore("ProductsDB");
const deleteRequest = objectStore.delete(id);
deleteRequest.onerror = (event) => {
reject(`Error deleting product: ${event.target.error}`);
};
deleteRequest.onsuccess = () => {
resolve();
};
};
});
},
This refactoring:
- Wraps the operation in a Promise for better async handling.
- Adds specific error handling for the
delete
request. - Provides proper resolution or rejection based on the operation's outcome.
Consider applying similar refactoring to other methods in this file for consistency and improved error handling.
deleteAllProducts() { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.clear(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor deleteAllProducts
method for better error handling and Promise support
The deleteAllProducts
method has several issues:
- It doesn't return a Promise, making it difficult to know when the operation is complete.
- It uses
reject
without being in a Promise context. - There's no success handling to confirm the deletion of all products.
Here's a suggested refactoring:
deleteAllProducts() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
reject(event);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["ProductsDB"], "readwrite");
const objectStore = transaction.objectStore("ProductsDB");
const clearRequest = objectStore.clear();
clearRequest.onerror = (event) => {
reject(`Error clearing products: ${event.target.error}`);
};
clearRequest.onsuccess = () => {
resolve();
};
};
});
},
This refactoring:
- Wraps the operation in a Promise for better async handling.
- Adds specific error handling for the
clear
request. - Provides proper resolution or rejection based on the operation's outcome.
Consider applying similar refactoring to other methods in this file for consistency and improved error handling.
results.push( product ); | ||
} | ||
|
||
cursor.continue(); | ||
} else { | ||
resolve( results ); | ||
} | ||
} | ||
} | ||
} ); | ||
}, | ||
|
||
updateProduct( product ) { | ||
return new Promise( ( resolve, reject ) => { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.put( product ).onsuccess = ( event ) => { | ||
resolve( event.target.result ); | ||
}; | ||
} | ||
}); | ||
}, | ||
|
||
deleteProductById( id ) { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.delete( id ); | ||
} | ||
}, | ||
|
||
deleteAllProducts() { | ||
let db; | ||
const request = indexedDB.open( "ProductsDB", 1 ); | ||
|
||
request.onerror = ( event ) => { | ||
console.error( "An error occurred with IndexedDB" ); | ||
console.error( event ); | ||
|
||
reject( event ); | ||
}; | ||
|
||
request.onsuccess = ( event ) => { | ||
db = event.target.result; | ||
|
||
db.onerror = ( event ) => { | ||
// Generic error handler for all errors targeted at this database's requests. | ||
console.error( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
|
||
reject( `wePOS IndexedDB Database error: ${event.target.error.code} - ${event.target.error.message}` ); | ||
} | ||
|
||
const transaction = db.transaction( ["ProductsDB"], "readwrite" ); | ||
const objectStore = transaction.objectStore( "ProductsDB" ); | ||
|
||
objectStore.clear(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement consistent error handling and Promise usage across the file
After reviewing the entire file, there are several patterns that could be improved:
- Inconsistent Promise usage: Some methods return Promises while others don't.
- Redundant error handling code across methods.
- Inconsistent error logging strategy.
Consider the following improvements:
- Ensure all methods return Promises for consistent asynchronous operation handling.
- Create a utility function for opening the database to reduce redundant code:
function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("ProductsDB", 1);
request.onerror = (event) => {
console.error("An error occurred with IndexedDB");
reject(event);
};
request.onsuccess = (event) => resolve(event.target.result);
// Include onupgradeneeded here if needed
});
}
- Implement a consistent error logging strategy. For example:
function logError(method, error) {
console.error(`Error in ${method}:`, error);
}
- Use these utility functions in your methods:
getAllProducts() {
return openDatabase().then(db => {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["ProductsDB"], "readonly");
const objectStore = transaction.objectStore("ProductsDB");
const request = objectStore.getAll();
request.onerror = (event) => {
logError("getAllProducts", event);
reject(event);
};
request.onsuccess = (event) => resolve(event.target.result);
});
});
},
By implementing these changes consistently across all methods, you can significantly improve the code's maintainability and error handling.
Consider creating a separate file for database operations and utility functions. This separation of concerns will make the code more modular and easier to maintain.
Scenario: Steps to reproduce:
DB Error:
|
@devAsadNur, kindly update the target upgrader version to the next release version(1.2.9). |
The
IndexedDB
has been integrated to sync products over multiple counters. Also, improved frontend product searching.Summary by CodeRabbit
New Features
Improvements
Chores