@webbycrown/webbycommerce 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/admin/app.js +3 -0
- package/admin/jsconfig.json +20 -0
- package/admin/src/components/ApiCollectionsContent.jsx +4626 -0
- package/admin/src/components/CompareContent.jsx +300 -0
- package/admin/src/components/ConfigureContent.jsx +407 -0
- package/admin/src/components/Initializer.jsx +64 -0
- package/admin/src/components/LoginRegisterContent.jsx +280 -0
- package/admin/src/components/PluginIcon.jsx +6 -0
- package/admin/src/components/ShippingTypeContent.jsx +230 -0
- package/admin/src/components/SmtpContent.jsx +316 -0
- package/admin/src/components/WishlistContent.jsx +273 -0
- package/admin/src/index.js +81 -0
- package/admin/src/pages/ApiCollections.jsx +169 -0
- package/admin/src/pages/Configure.jsx +55 -0
- package/admin/src/pages/Settings.jsx +93 -0
- package/admin/src/pluginId.js +4 -0
- package/{dist/_chunks/en-CiQ97iC8.js → admin/src/translations/en.json} +712 -574
- package/bin/setup.js +50 -3
- package/package.json +14 -13
- package/server/bootstrap.js +3 -0
- package/server/register.js +3 -0
- package/server/src/bootstrap.js +3826 -0
- package/server/src/components/content-block.json +37 -0
- package/server/src/components/shipping-zone-location.json +27 -0
- package/server/src/config/index.js +7 -0
- package/server/src/content-types/address/index.js +7 -0
- package/server/src/content-types/address/schema.json +74 -0
- package/server/src/content-types/cart/index.js +61 -0
- package/server/src/content-types/cart-item/index.js +79 -0
- package/server/src/content-types/compare.js +73 -0
- package/server/src/content-types/coupon/index.js +7 -0
- package/server/src/content-types/coupon/schema.json +67 -0
- package/server/src/content-types/index.js +42 -0
- package/server/src/content-types/order/index.js +7 -0
- package/server/src/content-types/order/schema.json +121 -0
- package/server/src/content-types/payment-transaction/index.js +7 -0
- package/server/src/content-types/payment-transaction/schema.json +73 -0
- package/server/src/content-types/product/index.js +7 -0
- package/server/src/content-types/product/schema.json +104 -0
- package/server/src/content-types/product-attribute/index.js +7 -0
- package/server/src/content-types/product-attribute/schema.json +80 -0
- package/server/src/content-types/product-attribute-value/index.js +7 -0
- package/server/src/content-types/product-attribute-value/schema.json +52 -0
- package/server/src/content-types/product-category/index.js +7 -0
- package/server/src/content-types/product-category/schema.json +54 -0
- package/server/src/content-types/product-tag/index.js +7 -0
- package/server/src/content-types/product-tag/schema.json +38 -0
- package/server/src/content-types/product-variation/index.js +7 -0
- package/server/src/content-types/product-variation/schema.json +74 -0
- package/server/src/content-types/shipping-method/index.js +7 -0
- package/server/src/content-types/shipping-method/schema.json +91 -0
- package/server/src/content-types/shipping-rate/index.js +7 -0
- package/server/src/content-types/shipping-rate/schema.json +73 -0
- package/server/src/content-types/shipping-rule/index.js +7 -0
- package/server/src/content-types/shipping-rule/schema.json +84 -0
- package/server/src/content-types/shipping-zone/index.js +7 -0
- package/server/src/content-types/shipping-zone/schema.json +57 -0
- package/server/src/content-types/wishlist.js +66 -0
- package/server/src/controllers/address.js +374 -0
- package/server/src/controllers/auth.js +1409 -0
- package/server/src/controllers/cart.js +337 -0
- package/server/src/controllers/category.js +388 -0
- package/server/src/controllers/compare.js +246 -0
- package/server/src/controllers/controller.js +168 -0
- package/server/src/controllers/ecommerce.js +20 -0
- package/server/src/controllers/index.js +34 -0
- package/server/src/controllers/order.js +1100 -0
- package/server/src/controllers/payment.js +243 -0
- package/server/src/controllers/product.js +1006 -0
- package/server/src/controllers/productTag.js +370 -0
- package/server/src/controllers/productVariation.js +181 -0
- package/server/src/controllers/shipping.js +1046 -0
- package/server/src/controllers/wishlist.js +332 -0
- package/server/src/destroy.js +6 -0
- package/server/src/index.js +26 -0
- package/server/src/middlewares/index.js +4 -0
- package/server/src/policies/index.js +4 -0
- package/server/src/register.js +67 -0
- package/server/src/routes/index.js +1130 -0
- package/server/src/services/cart.js +531 -0
- package/server/src/services/compare.js +300 -0
- package/server/src/services/index.js +16 -0
- package/server/src/services/service.js +19 -0
- package/server/src/services/shipping.js +513 -0
- package/server/src/services/wishlist.js +238 -0
- package/server/src/utils/check-ecommerce-permission.js +204 -0
- package/server/src/utils/extend-user-schema.js +161 -0
- package/server/src/utils/seed-data.js +639 -0
- package/server/src/utils/send-email.js +98 -0
- package/strapi-server.js +1 -6
- package/dist/_chunks/Settings-DZXAkI24.js +0 -31539
- package/dist/_chunks/Settings-yLx-YvVy.mjs +0 -31520
- package/dist/_chunks/en-DE15m4xZ.mjs +0 -574
- package/dist/_chunks/index-CXGrFKp6.mjs +0 -128
- package/dist/_chunks/index-DgocXUgC.js +0 -127
- package/dist/admin/index.js +0 -3
- package/dist/admin/index.mjs +0 -4
- package/dist/robots.txt +0 -3
- package/dist/server/index.js +0 -27078
- package/dist/uploads/.gitkeep +0 -0
- package/dist/uploads/accessories_category_2a5631094b.jpeg +0 -0
- package/dist/uploads/beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
- package/dist/uploads/books_category_a9a253eada.jpeg +0 -0
- package/dist/uploads/classic_cotton_tshirt_1_cd713425f6.png +0 -0
- package/dist/uploads/clothing_category_d5c60ef07b.jpeg +0 -0
- package/dist/uploads/daviddoe_strapi_adbcd41787.jpeg +0 -0
- package/dist/uploads/electronics_category_fc3e5ef571.jpeg +0 -0
- package/dist/uploads/ergonomic_office_chair_1_c751cffb07.png +0 -0
- package/dist/uploads/home_garden_category_4f6eb3f8d6.jpeg +0 -0
- package/dist/uploads/istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
- package/dist/uploads/istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
- package/dist/uploads/large_daviddoe_strapi_adbcd41787.jpeg +0 -0
- package/dist/uploads/leather_travel_backpack_1_238bc1ae4d.png +0 -0
- package/dist/uploads/mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
- package/dist/uploads/medium_classic_cotton_tshirt_1_cd713425f6.png +0 -0
- package/dist/uploads/medium_daviddoe_strapi_adbcd41787.jpeg +0 -0
- package/dist/uploads/medium_ergonomic_office_chair_1_c751cffb07.png +0 -0
- package/dist/uploads/medium_leather_travel_backpack_1_238bc1ae4d.png +0 -0
- package/dist/uploads/medium_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
- package/dist/uploads/medium_smart_watch_series_5_1_cdc2511fb7.png +0 -0
- package/dist/uploads/medium_smartphone_x_pro_1_c3f0cbd080.png +0 -0
- package/dist/uploads/medium_the_great_gatsby_special_1_2e7c76d997.png +0 -0
- package/dist/uploads/medium_wireless_headphones_1_fa75cd50c3.png +0 -0
- package/dist/uploads/medium_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
- package/dist/uploads/predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
- package/dist/uploads/small_classic_cotton_tshirt_1_cd713425f6.png +0 -0
- package/dist/uploads/small_daviddoe_strapi_adbcd41787.jpeg +0 -0
- package/dist/uploads/small_ergonomic_office_chair_1_c751cffb07.png +0 -0
- package/dist/uploads/small_leather_travel_backpack_1_238bc1ae4d.png +0 -0
- package/dist/uploads/small_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
- package/dist/uploads/small_smart_watch_series_5_1_cdc2511fb7.png +0 -0
- package/dist/uploads/small_smartphone_x_pro_1_c3f0cbd080.png +0 -0
- package/dist/uploads/small_the_great_gatsby_special_1_2e7c76d997.png +0 -0
- package/dist/uploads/small_wireless_headphones_1_fa75cd50c3.png +0 -0
- package/dist/uploads/small_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
- package/dist/uploads/smart_watch_series_5_1_cdc2511fb7.png +0 -0
- package/dist/uploads/smartphone_x_pro_1_c3f0cbd080.png +0 -0
- package/dist/uploads/the_great_gatsby_special_1_2e7c76d997.png +0 -0
- package/dist/uploads/thumbnail_accessories_category_2a5631094b.jpeg +0 -0
- package/dist/uploads/thumbnail_beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
- package/dist/uploads/thumbnail_books_category_a9a253eada.jpeg +0 -0
- package/dist/uploads/thumbnail_classic_cotton_tshirt_1_cd713425f6.png +0 -0
- package/dist/uploads/thumbnail_clothing_category_d5c60ef07b.jpeg +0 -0
- package/dist/uploads/thumbnail_daviddoe_strapi_adbcd41787.jpeg +0 -0
- package/dist/uploads/thumbnail_electronics_category_fc3e5ef571.jpeg +0 -0
- package/dist/uploads/thumbnail_ergonomic_office_chair_1_c751cffb07.png +0 -0
- package/dist/uploads/thumbnail_home_garden_category_4f6eb3f8d6.jpeg +0 -0
- package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
- package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
- package/dist/uploads/thumbnail_leather_travel_backpack_1_238bc1ae4d.png +0 -0
- package/dist/uploads/thumbnail_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
- package/dist/uploads/thumbnail_predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
- package/dist/uploads/thumbnail_smart_watch_series_5_1_cdc2511fb7.png +0 -0
- package/dist/uploads/thumbnail_smartphone_x_pro_1_c3f0cbd080.png +0 -0
- package/dist/uploads/thumbnail_the_great_gatsby_special_1_2e7c76d997.png +0 -0
- package/dist/uploads/thumbnail_wireless_headphones_1_fa75cd50c3.png +0 -0
- package/dist/uploads/thumbnail_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
- package/dist/uploads/webby-commerce.png +0 -0
- package/dist/uploads/wireless_headphones_1_fa75cd50c3.png +0 -0
- package/dist/uploads/yoga_mat_premium_1_01f9a3b5fa.png +0 -0
- /package/{dist → server/src}/data/demo-data.json +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "collectionType",
|
|
3
|
+
"collectionName": "shipping_rates",
|
|
4
|
+
"info": {
|
|
5
|
+
"singularName": "shipping-rate",
|
|
6
|
+
"pluralName": "shipping-rates",
|
|
7
|
+
"displayName": "Shipping Rate",
|
|
8
|
+
"description": "Shipping rates based on weight, price, or quantity"
|
|
9
|
+
},
|
|
10
|
+
"options": {
|
|
11
|
+
"draftAndPublish": false
|
|
12
|
+
},
|
|
13
|
+
"pluginOptions": {
|
|
14
|
+
"content-manager": {
|
|
15
|
+
"visible": true
|
|
16
|
+
},
|
|
17
|
+
"content-api": {
|
|
18
|
+
"visible": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"attributes": {
|
|
22
|
+
"name": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"required": true,
|
|
25
|
+
"description": "Rate name for admin reference"
|
|
26
|
+
},
|
|
27
|
+
"condition_type": {
|
|
28
|
+
"type": "enumeration",
|
|
29
|
+
"enum": ["weight", "price", "quantity", "volume", "dimension"],
|
|
30
|
+
"required": true,
|
|
31
|
+
"description": "What the rate is based on"
|
|
32
|
+
},
|
|
33
|
+
"min_value": {
|
|
34
|
+
"type": "decimal",
|
|
35
|
+
"required": true,
|
|
36
|
+
"min": 0,
|
|
37
|
+
"description": "Minimum value for this rate tier"
|
|
38
|
+
},
|
|
39
|
+
"max_value": {
|
|
40
|
+
"type": "decimal",
|
|
41
|
+
"required": false,
|
|
42
|
+
"min": 0,
|
|
43
|
+
"description": "Maximum value for this rate tier (null for unlimited)"
|
|
44
|
+
},
|
|
45
|
+
"rate": {
|
|
46
|
+
"type": "decimal",
|
|
47
|
+
"required": true,
|
|
48
|
+
"min": 0,
|
|
49
|
+
"description": "Shipping cost for this tier"
|
|
50
|
+
},
|
|
51
|
+
"currency": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"required": true,
|
|
54
|
+
"default": "USD"
|
|
55
|
+
},
|
|
56
|
+
"shippingMethod": {
|
|
57
|
+
"type": "relation",
|
|
58
|
+
"relation": "manyToOne",
|
|
59
|
+
"target": "plugin::webbycommerce.shipping-method",
|
|
60
|
+
"required": false
|
|
61
|
+
},
|
|
62
|
+
"is_active": {
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"default": true,
|
|
65
|
+
"required": true
|
|
66
|
+
},
|
|
67
|
+
"sort_order": {
|
|
68
|
+
"type": "integer",
|
|
69
|
+
"default": 0,
|
|
70
|
+
"required": false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "collectionType",
|
|
3
|
+
"collectionName": "shipping_rules",
|
|
4
|
+
"info": {
|
|
5
|
+
"singularName": "shipping-rule",
|
|
6
|
+
"pluralName": "shipping-rules",
|
|
7
|
+
"displayName": "Shipping Rule",
|
|
8
|
+
"description": "Rules and conditions for shipping eligibility"
|
|
9
|
+
},
|
|
10
|
+
"options": {
|
|
11
|
+
"draftAndPublish": false
|
|
12
|
+
},
|
|
13
|
+
"pluginOptions": {
|
|
14
|
+
"content-manager": {
|
|
15
|
+
"visible": true
|
|
16
|
+
},
|
|
17
|
+
"content-api": {
|
|
18
|
+
"visible": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"attributes": {
|
|
22
|
+
"name": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"required": true
|
|
25
|
+
},
|
|
26
|
+
"description": {
|
|
27
|
+
"type": "text",
|
|
28
|
+
"required": false
|
|
29
|
+
},
|
|
30
|
+
"rule_type": {
|
|
31
|
+
"type": "enumeration",
|
|
32
|
+
"enum": ["restriction", "surcharge", "discount", "requirement"],
|
|
33
|
+
"required": true
|
|
34
|
+
},
|
|
35
|
+
"condition_type": {
|
|
36
|
+
"type": "enumeration",
|
|
37
|
+
"enum": ["product_category", "product_tag", "product_weight", "order_total", "customer_group", "shipping_address", "cart_quantity"],
|
|
38
|
+
"required": true
|
|
39
|
+
},
|
|
40
|
+
"condition_operator": {
|
|
41
|
+
"type": "enumeration",
|
|
42
|
+
"enum": ["equals", "not_equals", "greater_than", "less_than", "contains", "not_contains", "in", "not_in"],
|
|
43
|
+
"required": true
|
|
44
|
+
},
|
|
45
|
+
"condition_value": {
|
|
46
|
+
"type": "json",
|
|
47
|
+
"required": true,
|
|
48
|
+
"description": "Value(s) to compare against (string, number, or array)"
|
|
49
|
+
},
|
|
50
|
+
"action_type": {
|
|
51
|
+
"type": "enumeration",
|
|
52
|
+
"enum": ["hide_method", "add_fee", "subtract_fee", "set_rate", "multiply_rate"],
|
|
53
|
+
"required": true
|
|
54
|
+
},
|
|
55
|
+
"action_value": {
|
|
56
|
+
"type": "decimal",
|
|
57
|
+
"required": false,
|
|
58
|
+
"description": "Value for the action (fee amount, multiplier, etc.)"
|
|
59
|
+
},
|
|
60
|
+
"action_message": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"required": false,
|
|
63
|
+
"description": "Message to display when rule applies"
|
|
64
|
+
},
|
|
65
|
+
"is_active": {
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": true,
|
|
68
|
+
"required": true
|
|
69
|
+
},
|
|
70
|
+
"priority": {
|
|
71
|
+
"type": "integer",
|
|
72
|
+
"default": 0,
|
|
73
|
+
"required": false,
|
|
74
|
+
"description": "Higher priority rules are evaluated first"
|
|
75
|
+
},
|
|
76
|
+
"applies_to_methods": {
|
|
77
|
+
"type": "relation",
|
|
78
|
+
"relation": "manyToMany",
|
|
79
|
+
"target": "plugin::webbycommerce.shipping-method",
|
|
80
|
+
"required": false,
|
|
81
|
+
"description": "Shipping methods this rule applies to (empty/null for all)"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "collectionType",
|
|
3
|
+
"collectionName": "shipping_zones",
|
|
4
|
+
"info": {
|
|
5
|
+
"singularName": "shipping-zone",
|
|
6
|
+
"pluralName": "shipping-zones",
|
|
7
|
+
"displayName": "Shipping Zone",
|
|
8
|
+
"description": "Geographical shipping zones for rate calculation"
|
|
9
|
+
},
|
|
10
|
+
"options": {
|
|
11
|
+
"draftAndPublish": false
|
|
12
|
+
},
|
|
13
|
+
"pluginOptions": {
|
|
14
|
+
"content-manager": {
|
|
15
|
+
"visible": true
|
|
16
|
+
},
|
|
17
|
+
"content-api": {
|
|
18
|
+
"visible": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"attributes": {
|
|
22
|
+
"name": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"required": true,
|
|
25
|
+
"unique": true
|
|
26
|
+
},
|
|
27
|
+
"description": {
|
|
28
|
+
"type": "text",
|
|
29
|
+
"required": false
|
|
30
|
+
},
|
|
31
|
+
"location": {
|
|
32
|
+
"type": "component",
|
|
33
|
+
"repeatable": true,
|
|
34
|
+
"component": "plugin::webbycommerce.shipping-zone-location",
|
|
35
|
+
"required": false,
|
|
36
|
+
"description": "Location rules for matching shipping addresses"
|
|
37
|
+
},
|
|
38
|
+
"is_active": {
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"default": true,
|
|
41
|
+
"required": true
|
|
42
|
+
},
|
|
43
|
+
"sort_order": {
|
|
44
|
+
"type": "integer",
|
|
45
|
+
"default": 0,
|
|
46
|
+
"required": false
|
|
47
|
+
},
|
|
48
|
+
"shippingMethods": {
|
|
49
|
+
"type": "relation",
|
|
50
|
+
"relation": "oneToMany",
|
|
51
|
+
"target": "plugin::webbycommerce.shipping-method",
|
|
52
|
+
"required": false
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wishlist.js
|
|
5
|
+
*
|
|
6
|
+
* Wishlist content type lifecycle
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const schema = {
|
|
10
|
+
kind: 'collectionType',
|
|
11
|
+
collectionName: 'wishlists',
|
|
12
|
+
info: {
|
|
13
|
+
singularName: 'wishlist',
|
|
14
|
+
pluralName: 'wishlists',
|
|
15
|
+
displayName: 'Wishlist',
|
|
16
|
+
description: 'User wishlist for storing favorite products',
|
|
17
|
+
},
|
|
18
|
+
options: {
|
|
19
|
+
draftAndPublish: false,
|
|
20
|
+
timestamps: true,
|
|
21
|
+
},
|
|
22
|
+
pluginOptions: {
|
|
23
|
+
'content-manager': {
|
|
24
|
+
visible: true,
|
|
25
|
+
},
|
|
26
|
+
'content-type-builder': {
|
|
27
|
+
visible: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
attributes: {
|
|
31
|
+
userId: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
required: true,
|
|
34
|
+
configurable: false,
|
|
35
|
+
},
|
|
36
|
+
userEmail: {
|
|
37
|
+
type: 'email',
|
|
38
|
+
required: true,
|
|
39
|
+
configurable: false,
|
|
40
|
+
},
|
|
41
|
+
products: {
|
|
42
|
+
type: 'relation',
|
|
43
|
+
relation: 'manyToMany',
|
|
44
|
+
target: 'plugin::webbycommerce.product',
|
|
45
|
+
mappedBy: 'wishlists',
|
|
46
|
+
},
|
|
47
|
+
isPublic: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
default: false,
|
|
50
|
+
required: false,
|
|
51
|
+
},
|
|
52
|
+
name: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
maxLength: 100,
|
|
55
|
+
required: false,
|
|
56
|
+
},
|
|
57
|
+
description: {
|
|
58
|
+
type: 'text',
|
|
59
|
+
required: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
schema,
|
|
66
|
+
};
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'webbycommerce';
|
|
4
|
+
const { ensureEcommercePermission } = require('../utils/check-ecommerce-permission');
|
|
5
|
+
|
|
6
|
+
const getStore = () => {
|
|
7
|
+
return strapi.store({ type: 'plugin', name: PLUGIN_ID });
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const getShippingType = async () => {
|
|
11
|
+
const store = getStore();
|
|
12
|
+
const value = (await store.get({ key: 'settings' })) || {};
|
|
13
|
+
return value.shippingType || 'single'; // 'single' or 'multiple'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
/**
|
|
18
|
+
* Get all addresses for authenticated user
|
|
19
|
+
*/
|
|
20
|
+
async getAddresses(ctx) {
|
|
21
|
+
try {
|
|
22
|
+
// Check ecommerce permission
|
|
23
|
+
const hasPermission = await ensureEcommercePermission(ctx);
|
|
24
|
+
if (!hasPermission) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const user = ctx.state.user;
|
|
29
|
+
if (!user) {
|
|
30
|
+
return ctx.unauthorized('Authentication required. Please provide a valid JWT token.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { type } = ctx.query; // Optional filter: type=0 (billing) or type=1 (shipping)
|
|
34
|
+
|
|
35
|
+
const where = { user: user.id };
|
|
36
|
+
if (type !== undefined) {
|
|
37
|
+
where.type = parseInt(type, 10);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const addresses = await strapi.db.query('plugin::webbycommerce.address').findMany({
|
|
41
|
+
where,
|
|
42
|
+
orderBy: { createdAt: 'desc' },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
ctx.send({ data: addresses });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in getAddresses:`, error);
|
|
48
|
+
ctx.internalServerError('Failed to fetch addresses. Please try again.');
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get single address by ID
|
|
54
|
+
*/
|
|
55
|
+
async getAddress(ctx) {
|
|
56
|
+
try {
|
|
57
|
+
const hasPermission = await ensureEcommercePermission(ctx);
|
|
58
|
+
if (!hasPermission) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const user = ctx.state.user;
|
|
63
|
+
if (!user) {
|
|
64
|
+
return ctx.unauthorized('Authentication required. Please provide a valid JWT token.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { id } = ctx.params;
|
|
68
|
+
|
|
69
|
+
const address = await strapi.db.query('plugin::webbycommerce.address').findOne({
|
|
70
|
+
where: { id, user: user.id },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!address) {
|
|
74
|
+
return ctx.notFound('Address not found.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ctx.send({ data: address });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in getAddress:`, error);
|
|
80
|
+
ctx.internalServerError('Failed to fetch address. Please try again.');
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create new address
|
|
86
|
+
*/
|
|
87
|
+
async createAddress(ctx) {
|
|
88
|
+
try {
|
|
89
|
+
const hasPermission = await ensureEcommercePermission(ctx);
|
|
90
|
+
if (!hasPermission) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const user = ctx.state.user;
|
|
95
|
+
if (!user) {
|
|
96
|
+
return ctx.unauthorized('Authentication required. Please provide a valid JWT token.');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const shippingType = await getShippingType();
|
|
100
|
+
const {
|
|
101
|
+
type,
|
|
102
|
+
first_name,
|
|
103
|
+
last_name,
|
|
104
|
+
company_name,
|
|
105
|
+
country,
|
|
106
|
+
region,
|
|
107
|
+
city,
|
|
108
|
+
street_address,
|
|
109
|
+
postcode,
|
|
110
|
+
phone,
|
|
111
|
+
email_address,
|
|
112
|
+
} = ctx.request.body || {};
|
|
113
|
+
|
|
114
|
+
// Validate required fields - convert type to integer for validation
|
|
115
|
+
// Check if type exists in request body
|
|
116
|
+
if (type === undefined || type === null) {
|
|
117
|
+
return ctx.badRequest('Type is required and must be 0 (billing) or 1 (shipping).');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Convert to number - handle both string and number inputs
|
|
121
|
+
// Accept: 0, 1, "0", "1", 0.0, 1.0
|
|
122
|
+
let typeInt;
|
|
123
|
+
if (type === 0 || type === '0' || type === 0.0) {
|
|
124
|
+
typeInt = 0;
|
|
125
|
+
} else if (type === 1 || type === '1' || type === 1.0) {
|
|
126
|
+
typeInt = 1;
|
|
127
|
+
} else {
|
|
128
|
+
// Try parsing as number
|
|
129
|
+
typeInt = Number(type);
|
|
130
|
+
if (isNaN(typeInt) || (typeInt !== 0 && typeInt !== 1)) {
|
|
131
|
+
return ctx.badRequest('Type is required and must be 0 (billing) or 1 (shipping).');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!first_name || typeof first_name !== 'string' || first_name.trim().length === 0) {
|
|
136
|
+
return ctx.badRequest('First name is required.');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!last_name || typeof last_name !== 'string' || last_name.trim().length === 0) {
|
|
140
|
+
return ctx.badRequest('Last name is required.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!country || typeof country !== 'string' || country.trim().length === 0) {
|
|
144
|
+
return ctx.badRequest('Country is required.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!city || typeof city !== 'string' || city.trim().length === 0) {
|
|
148
|
+
return ctx.badRequest('City is required.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!street_address || typeof street_address !== 'string' || street_address.trim().length === 0) {
|
|
152
|
+
return ctx.badRequest('Street address is required.');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!postcode || typeof postcode !== 'string' || postcode.trim().length === 0) {
|
|
156
|
+
return ctx.badRequest('Postcode is required.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!phone || typeof phone !== 'string' || phone.trim().length === 0) {
|
|
160
|
+
return ctx.badRequest('Phone is required.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Email is required for billing addresses
|
|
164
|
+
if (typeInt === 0 && (!email_address || typeof email_address !== 'string' || email_address.trim().length === 0)) {
|
|
165
|
+
return ctx.badRequest('Email address is required for billing addresses.');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate email format if provided
|
|
169
|
+
if (email_address) {
|
|
170
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
171
|
+
if (!emailRegex.test(email_address.trim())) {
|
|
172
|
+
return ctx.badRequest('Invalid email format.');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check if single mode and address already exists
|
|
177
|
+
if (shippingType === 'single') {
|
|
178
|
+
const existingAddress = await strapi.db.query('plugin::webbycommerce.address').findOne({
|
|
179
|
+
where: { user: user.id, type: typeInt },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (existingAddress) {
|
|
183
|
+
return ctx.forbidden(
|
|
184
|
+
`A ${typeInt === 0 ? 'billing' : 'shipping'} address already exists. Please update the existing address or enable multiple address mode in settings.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create address
|
|
190
|
+
const address = await strapi.db.query('plugin::webbycommerce.address').create({
|
|
191
|
+
data: {
|
|
192
|
+
type: typeInt,
|
|
193
|
+
first_name: first_name.trim(),
|
|
194
|
+
last_name: last_name.trim(),
|
|
195
|
+
company_name: company_name ? company_name.trim() : null,
|
|
196
|
+
country: country.trim(),
|
|
197
|
+
region: region ? region.trim() : null,
|
|
198
|
+
city: city.trim(),
|
|
199
|
+
street_address: street_address.trim(),
|
|
200
|
+
postcode: postcode.trim(),
|
|
201
|
+
phone: phone.trim(),
|
|
202
|
+
email_address: email_address ? email_address.trim().toLowerCase() : null,
|
|
203
|
+
user: user.id,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
ctx.send({ data: address });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in createAddress:`, error);
|
|
210
|
+
ctx.internalServerError('Failed to create address. Please try again.');
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update address
|
|
216
|
+
*/
|
|
217
|
+
async updateAddress(ctx) {
|
|
218
|
+
try {
|
|
219
|
+
const hasPermission = await ensureEcommercePermission(ctx);
|
|
220
|
+
if (!hasPermission) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const user = ctx.state.user;
|
|
225
|
+
if (!user) {
|
|
226
|
+
return ctx.unauthorized('Authentication required. Please provide a valid JWT token.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { id } = ctx.params;
|
|
230
|
+
const {
|
|
231
|
+
type,
|
|
232
|
+
first_name,
|
|
233
|
+
last_name,
|
|
234
|
+
company_name,
|
|
235
|
+
country,
|
|
236
|
+
region,
|
|
237
|
+
city,
|
|
238
|
+
street_address,
|
|
239
|
+
postcode,
|
|
240
|
+
phone,
|
|
241
|
+
email_address,
|
|
242
|
+
} = ctx.request.body;
|
|
243
|
+
|
|
244
|
+
// Verify address belongs to user
|
|
245
|
+
const existingAddress = await strapi.db.query('plugin::webbycommerce.address').findOne({
|
|
246
|
+
where: { id, user: user.id },
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!existingAddress) {
|
|
250
|
+
return ctx.notFound('Address not found.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Validate type if provided
|
|
254
|
+
if (type !== undefined) {
|
|
255
|
+
const typeInt = parseInt(type, 10);
|
|
256
|
+
if (isNaN(typeInt) || (typeInt !== 0 && typeInt !== 1)) {
|
|
257
|
+
return ctx.badRequest('Type must be 0 (billing) or 1 (shipping).');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Validate required fields if provided
|
|
262
|
+
if (first_name !== undefined && (!first_name || typeof first_name !== 'string' || first_name.trim().length === 0)) {
|
|
263
|
+
return ctx.badRequest('First name is required.');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (last_name !== undefined && (!last_name || typeof last_name !== 'string' || last_name.trim().length === 0)) {
|
|
267
|
+
return ctx.badRequest('Last name is required.');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (country !== undefined && (!country || typeof country !== 'string' || country.trim().length === 0)) {
|
|
271
|
+
return ctx.badRequest('Country is required.');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (city !== undefined && (!city || typeof city !== 'string' || city.trim().length === 0)) {
|
|
275
|
+
return ctx.badRequest('City is required.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (street_address !== undefined && (!street_address || typeof street_address !== 'string' || street_address.trim().length === 0)) {
|
|
279
|
+
return ctx.badRequest('Street address is required.');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (postcode !== undefined && (!postcode || typeof postcode !== 'string' || postcode.trim().length === 0)) {
|
|
283
|
+
return ctx.badRequest('Postcode is required.');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (phone !== undefined && (!phone || typeof phone !== 'string' || phone.trim().length === 0)) {
|
|
287
|
+
return ctx.badRequest('Phone is required.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate email if provided
|
|
291
|
+
if (email_address !== undefined) {
|
|
292
|
+
const addressType = type !== undefined ? parseInt(type, 10) : existingAddress.type;
|
|
293
|
+
if (addressType === 0 && (!email_address || typeof email_address !== 'string' || email_address.trim().length === 0)) {
|
|
294
|
+
return ctx.badRequest('Email address is required for billing addresses.');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (email_address) {
|
|
298
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
299
|
+
if (!emailRegex.test(email_address.trim())) {
|
|
300
|
+
return ctx.badRequest('Invalid email format.');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Prepare update data
|
|
306
|
+
const updateData = {};
|
|
307
|
+
if (type !== undefined) {
|
|
308
|
+
const typeInt = parseInt(type, 10);
|
|
309
|
+
if (!isNaN(typeInt) && (typeInt === 0 || typeInt === 1)) {
|
|
310
|
+
updateData.type = typeInt;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (first_name !== undefined) updateData.first_name = first_name.trim();
|
|
314
|
+
if (last_name !== undefined) updateData.last_name = last_name.trim();
|
|
315
|
+
if (company_name !== undefined) updateData.company_name = company_name ? company_name.trim() : null;
|
|
316
|
+
if (country !== undefined) updateData.country = country.trim();
|
|
317
|
+
if (region !== undefined) updateData.region = region ? region.trim() : null;
|
|
318
|
+
if (city !== undefined) updateData.city = city.trim();
|
|
319
|
+
if (street_address !== undefined) updateData.street_address = street_address.trim();
|
|
320
|
+
if (postcode !== undefined) updateData.postcode = postcode.trim();
|
|
321
|
+
if (phone !== undefined) updateData.phone = phone.trim();
|
|
322
|
+
if (email_address !== undefined) updateData.email_address = email_address ? email_address.trim().toLowerCase() : null;
|
|
323
|
+
|
|
324
|
+
// Update address
|
|
325
|
+
const updatedAddress = await strapi.db.query('plugin::webbycommerce.address').update({
|
|
326
|
+
where: { id },
|
|
327
|
+
data: updateData,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
ctx.send({ data: updatedAddress });
|
|
331
|
+
} catch (error) {
|
|
332
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in updateAddress:`, error);
|
|
333
|
+
ctx.internalServerError('Failed to update address. Please try again.');
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Delete address
|
|
339
|
+
*/
|
|
340
|
+
async deleteAddress(ctx) {
|
|
341
|
+
try {
|
|
342
|
+
const hasPermission = await ensureEcommercePermission(ctx);
|
|
343
|
+
if (!hasPermission) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const user = ctx.state.user;
|
|
348
|
+
if (!user) {
|
|
349
|
+
return ctx.unauthorized('Authentication required. Please provide a valid JWT token.');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const { id } = ctx.params;
|
|
353
|
+
|
|
354
|
+
// Verify address belongs to user
|
|
355
|
+
const address = await strapi.db.query('plugin::webbycommerce.address').findOne({
|
|
356
|
+
where: { id, user: user.id },
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!address) {
|
|
360
|
+
return ctx.notFound('Address not found.');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
await strapi.db.query('plugin::webbycommerce.address').delete({
|
|
364
|
+
where: { id },
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
ctx.send({ data: { id } });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in deleteAddress:`, error);
|
|
370
|
+
ctx.internalServerError('Failed to delete address. Please try again.');
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|