@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,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wishlist service
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { createCoreService } = require('@strapi/strapi').factories;
|
|
8
|
+
|
|
9
|
+
module.exports = createCoreService('plugin::webbycommerce.wishlist', ({ strapi }) => ({
|
|
10
|
+
async findUserWishlist(userId) {
|
|
11
|
+
try {
|
|
12
|
+
const wishlists = await strapi.db.query('plugin::webbycommerce.wishlist').findMany({
|
|
13
|
+
where: {
|
|
14
|
+
userId: String(userId),
|
|
15
|
+
},
|
|
16
|
+
orderBy: { createdAt: 'desc' },
|
|
17
|
+
populate: {
|
|
18
|
+
products: {
|
|
19
|
+
populate: {
|
|
20
|
+
images: true,
|
|
21
|
+
product_categories: true,
|
|
22
|
+
tags: true,
|
|
23
|
+
variations: {
|
|
24
|
+
populate: {
|
|
25
|
+
attributes: true,
|
|
26
|
+
attributeValues: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return wishlists.length > 0 ? wishlists[0] : null;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(`Failed to find user wishlist: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async createUserWishlist(userId, userEmail, data = {}) {
|
|
41
|
+
try {
|
|
42
|
+
const wishlistData = {
|
|
43
|
+
userId: String(userId),
|
|
44
|
+
userEmail,
|
|
45
|
+
products: [],
|
|
46
|
+
isPublic: data.isPublic || false,
|
|
47
|
+
name: data.name || null,
|
|
48
|
+
description: data.description || null,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
await strapi.entityService.create('plugin::webbycommerce.wishlist', {
|
|
52
|
+
data: wishlistData,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Refetch wishlist with populated products
|
|
56
|
+
return await this.findUserWishlist(userId);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`Failed to create user wishlist: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async addProductToWishlist(userId, userEmail, productId) {
|
|
63
|
+
try {
|
|
64
|
+
// Find existing wishlist or create new one
|
|
65
|
+
let wishlist = await this.findUserWishlist(userId);
|
|
66
|
+
|
|
67
|
+
if (!wishlist) {
|
|
68
|
+
wishlist = await this.createUserWishlist(userId, userEmail);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Ensure products array exists (for newly created wishlists)
|
|
72
|
+
if (!wishlist.products) {
|
|
73
|
+
wishlist.products = [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check if product already exists in wishlist
|
|
77
|
+
// Handle both populated (object with id) and unpopulated (just id) cases
|
|
78
|
+
const productExists = wishlist.products.some(product => {
|
|
79
|
+
const productIdValue = typeof product === 'object' && product !== null ? product.id : product;
|
|
80
|
+
return productIdValue === parseInt(productId);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (productExists) {
|
|
84
|
+
throw new Error('Product already exists in wishlist');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Verify product exists (check both published and draft products)
|
|
88
|
+
const product = await strapi.db.query('plugin::webbycommerce.product').findOne({
|
|
89
|
+
where: { id: productId },
|
|
90
|
+
populate: ['product_categories', 'tags', 'images', 'variations'],
|
|
91
|
+
});
|
|
92
|
+
if (!product) {
|
|
93
|
+
throw new Error('Product not found');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get existing product IDs (handle both populated and unpopulated cases)
|
|
97
|
+
const existingProductIds = wishlist.products.map(p => {
|
|
98
|
+
return typeof p === 'object' && p !== null ? p.id : p;
|
|
99
|
+
}).filter(id => id != null);
|
|
100
|
+
|
|
101
|
+
// Add product to wishlist
|
|
102
|
+
await strapi.entityService.update('plugin::webbycommerce.wishlist', wishlist.id, {
|
|
103
|
+
data: {
|
|
104
|
+
products: { set: [...existingProductIds, parseInt(productId, 10)].map((id) => ({ id })) },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Refetch wishlist with populated products
|
|
109
|
+
return await this.findUserWishlist(userId);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(`Failed to add product to wishlist: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async removeProductFromWishlist(userId, productId) {
|
|
116
|
+
try {
|
|
117
|
+
const wishlist = await this.findUserWishlist(userId);
|
|
118
|
+
|
|
119
|
+
if (!wishlist) {
|
|
120
|
+
throw new Error('Wishlist not found');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ensure products array exists
|
|
124
|
+
if (!wishlist.products) {
|
|
125
|
+
wishlist.products = [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Remove product from wishlist
|
|
129
|
+
// Handle both populated (object with id) and unpopulated (just id) cases
|
|
130
|
+
const updatedProducts = wishlist.products
|
|
131
|
+
.filter(product => {
|
|
132
|
+
const productIdValue = typeof product === 'object' && product !== null ? product.id : product;
|
|
133
|
+
return productIdValue !== parseInt(productId);
|
|
134
|
+
})
|
|
135
|
+
.map(product => {
|
|
136
|
+
return typeof product === 'object' && product !== null ? product.id : product;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await strapi.entityService.update('plugin::webbycommerce.wishlist', wishlist.id, {
|
|
140
|
+
data: {
|
|
141
|
+
products: { set: updatedProducts.map((id) => ({ id })) },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Refetch wishlist with populated products
|
|
146
|
+
return await this.findUserWishlist(userId);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new Error(`Failed to remove product from wishlist: ${error.message}`);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async clearWishlist(userId) {
|
|
153
|
+
try {
|
|
154
|
+
const wishlist = await this.findUserWishlist(userId);
|
|
155
|
+
|
|
156
|
+
if (!wishlist) {
|
|
157
|
+
throw new Error('Wishlist not found');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await strapi.entityService.update('plugin::webbycommerce.wishlist', wishlist.id, {
|
|
161
|
+
data: {
|
|
162
|
+
products: { set: [] },
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Refetch wishlist with populated products
|
|
167
|
+
return await this.findUserWishlist(userId);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
throw new Error(`Failed to clear wishlist: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
async updateWishlist(userId, data) {
|
|
174
|
+
try {
|
|
175
|
+
const wishlist = await this.findUserWishlist(userId);
|
|
176
|
+
|
|
177
|
+
if (!wishlist) {
|
|
178
|
+
throw new Error('Wishlist not found');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await strapi.entityService.update('plugin::webbycommerce.wishlist', wishlist.id, {
|
|
182
|
+
data: {
|
|
183
|
+
name: data.name !== undefined ? data.name : wishlist.name,
|
|
184
|
+
description: data.description !== undefined ? data.description : wishlist.description,
|
|
185
|
+
isPublic: data.isPublic !== undefined ? data.isPublic : wishlist.isPublic,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Refetch wishlist with populated products
|
|
190
|
+
return await this.findUserWishlist(userId);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new Error(`Failed to update wishlist: ${error.message}`);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async getWishlistStats(userId) {
|
|
197
|
+
try {
|
|
198
|
+
const wishlist = await this.findUserWishlist(userId);
|
|
199
|
+
|
|
200
|
+
if (!wishlist) {
|
|
201
|
+
return {
|
|
202
|
+
totalProducts: 0,
|
|
203
|
+
totalValue: 0,
|
|
204
|
+
categories: [],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Ensure products array exists
|
|
209
|
+
if (!wishlist.products) {
|
|
210
|
+
wishlist.products = [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const totalProducts = wishlist.products.length;
|
|
214
|
+
const totalValue = wishlist.products.reduce((sum, product) => {
|
|
215
|
+
return sum + (product.price || 0);
|
|
216
|
+
}, 0);
|
|
217
|
+
|
|
218
|
+
const categoryCounts = {};
|
|
219
|
+
wishlist.products.forEach(product => {
|
|
220
|
+
const categoryName = product.product_categories?.[0]?.name || 'Uncategorized';
|
|
221
|
+
categoryCounts[categoryName] = (categoryCounts[categoryName] || 0) + 1;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const categories = Object.entries(categoryCounts).map(([name, count]) => ({
|
|
225
|
+
name,
|
|
226
|
+
count,
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
totalProducts,
|
|
231
|
+
totalValue,
|
|
232
|
+
categories,
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw new Error(`Failed to get wishlist stats: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
}));
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'webbycommerce';
|
|
4
|
+
const ECOMMERCE_ACTION = 'plugin::webbycommerce.ecommerce.enable';
|
|
5
|
+
const PATCHED_FLAG = Symbol('webbycommerce-permissions-patched');
|
|
6
|
+
const SETTINGS_KEY = 'settings';
|
|
7
|
+
|
|
8
|
+
const getStore = () => {
|
|
9
|
+
return strapi.store({ type: 'plugin', name: PLUGIN_ID });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const loadAllowedOrigins = async () => {
|
|
13
|
+
const store = getStore();
|
|
14
|
+
const value = (await store.get({ key: SETTINGS_KEY })) || {};
|
|
15
|
+
const input = Array.isArray(value.allowedOrigins) ? value.allowedOrigins : [];
|
|
16
|
+
|
|
17
|
+
return input
|
|
18
|
+
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
|
19
|
+
.filter((value) => value.length > 0)
|
|
20
|
+
.map((value) => value.toLowerCase().replace(/\/+$/, ''));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isOriginAllowed = async (ctx) => {
|
|
24
|
+
const allowedOrigins = await loadAllowedOrigins();
|
|
25
|
+
|
|
26
|
+
// If no origins are configured, allow all
|
|
27
|
+
if (!allowedOrigins.length) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const originHeader = ctx.request.header?.origin || '';
|
|
32
|
+
const hostHeader = ctx.request.header?.host || '';
|
|
33
|
+
let hostname = '';
|
|
34
|
+
|
|
35
|
+
if (originHeader) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = new URL(originHeader);
|
|
38
|
+
hostname = parsed.hostname.toLowerCase();
|
|
39
|
+
} catch {
|
|
40
|
+
// ignore invalid origin header
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!hostname && hostHeader) {
|
|
45
|
+
hostname = hostHeader.split(':')[0].toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!hostname) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalized = (value) =>
|
|
53
|
+
value
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/^https?:\/\//, '')
|
|
56
|
+
.split('/')[0]
|
|
57
|
+
.split(':')[0];
|
|
58
|
+
|
|
59
|
+
const requestHost = hostname.split(':')[0];
|
|
60
|
+
|
|
61
|
+
return allowedOrigins.some((origin) => normalized(origin) === requestHost);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const patchUsersPermissionsGetActions = (usersPermissionsService) => {
|
|
65
|
+
if (!usersPermissionsService || usersPermissionsService[PATCHED_FLAG]) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const originalGetActions = usersPermissionsService.getActions.bind(usersPermissionsService);
|
|
70
|
+
|
|
71
|
+
usersPermissionsService.getActions = (options = {}) => {
|
|
72
|
+
const result = originalGetActions(options) || {};
|
|
73
|
+
const defaultEnable = options?.defaultEnable ?? false;
|
|
74
|
+
const namespace = `plugin::${PLUGIN_ID}`;
|
|
75
|
+
|
|
76
|
+
const currentControllers = result[namespace]?.controllers || {};
|
|
77
|
+
const patchedControllers = { ...currentControllers };
|
|
78
|
+
|
|
79
|
+
// Register ecommerce enable action
|
|
80
|
+
const controllerActions = patchedControllers.ecommerce ? { ...patchedControllers.ecommerce } : {};
|
|
81
|
+
if (!controllerActions.enable) {
|
|
82
|
+
controllerActions.enable = { enabled: defaultEnable, policy: '' };
|
|
83
|
+
}
|
|
84
|
+
patchedControllers.ecommerce = controllerActions;
|
|
85
|
+
|
|
86
|
+
result[namespace] = {
|
|
87
|
+
controllers: patchedControllers,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
usersPermissionsService[PATCHED_FLAG] = true;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const registerEcommerceActions = async () => {
|
|
97
|
+
const permissionsService = strapi.plugin('users-permissions')?.service('users-permissions');
|
|
98
|
+
|
|
99
|
+
if (!permissionsService || typeof permissionsService.syncPermissions !== 'function') {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
patchUsersPermissionsGetActions(permissionsService);
|
|
104
|
+
|
|
105
|
+
// Sync ensures the action exists in the Users & Permissions schema
|
|
106
|
+
await permissionsService.syncPermissions();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getRoleIdFromCtx = async (ctx) => {
|
|
110
|
+
if (ctx.state?.user?.role?.id) {
|
|
111
|
+
return ctx.state.user.role.id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (ctx.state?.auth?.credentials?.role?.id) {
|
|
115
|
+
return ctx.state.auth.credentials.role.id;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (ctx.state?.auth?.strategy?.name === 'api-token') {
|
|
119
|
+
// API tokens are handled separately via the permissions array on the token
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const publicRole = await strapi.db.query('plugin::users-permissions.role').findOne({
|
|
124
|
+
where: { type: 'public' },
|
|
125
|
+
select: ['id'],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return publicRole?.id || null;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const hasApiTokenPermission = (ctx, action) => {
|
|
132
|
+
if (ctx.state?.auth?.strategy?.name !== 'api-token') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const permissions = ctx.state?.auth?.credentials?.permissions;
|
|
137
|
+
if (!Array.isArray(permissions)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return permissions.includes(action);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const isActionEnabledForRole = (rolePermissions, action) => {
|
|
145
|
+
if (!rolePermissions) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Action format for plugins:
|
|
150
|
+
// plugin::webbycommerce.ecommerce.enable
|
|
151
|
+
// namespace: plugin::webbycommerce
|
|
152
|
+
// controller: ecommerce
|
|
153
|
+
// actionName: enable
|
|
154
|
+
const [namespace, controller, actionName] = action.split('.');
|
|
155
|
+
|
|
156
|
+
const controllerSet = rolePermissions?.[namespace]?.controllers?.[controller];
|
|
157
|
+
return controllerSet?.[actionName]?.enabled === true;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const ensureEcommercePermission = async (ctx) => {
|
|
161
|
+
const action = ECOMMERCE_ACTION;
|
|
162
|
+
|
|
163
|
+
// First, enforce allowed origin settings (if any are configured)
|
|
164
|
+
const originOk = await isOriginAllowed(ctx);
|
|
165
|
+
if (!originOk) {
|
|
166
|
+
ctx.unauthorized('Ecommerce facility is not enabled for this origin');
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (hasApiTokenPermission(ctx, action)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const roleId = await getRoleIdFromCtx(ctx);
|
|
175
|
+
|
|
176
|
+
if (!roleId) {
|
|
177
|
+
ctx.unauthorized('Ecommerce facility is not enabled for this role');
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check plugin permission via Users & Permissions permissions table
|
|
182
|
+
const permissions = await strapi.db
|
|
183
|
+
.query('plugin::users-permissions.permission')
|
|
184
|
+
.findMany({
|
|
185
|
+
where: {
|
|
186
|
+
action,
|
|
187
|
+
role: roleId,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (Array.isArray(permissions) && permissions.length > 0) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
ctx.unauthorized('Ecommerce facility is not enabled for this role');
|
|
196
|
+
return false;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
ECOMMERCE_ACTION,
|
|
201
|
+
registerEcommerceActions,
|
|
202
|
+
ensureEcommercePermission,
|
|
203
|
+
};
|
|
204
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extend the users-permissions user schema with OTP fields
|
|
5
|
+
* This function adds 'otp' and 'isOtpVerified' fields to the user table
|
|
6
|
+
*/
|
|
7
|
+
async function extendUserSchemaWithOtpFields(strapi) {
|
|
8
|
+
try {
|
|
9
|
+
const db = strapi.db;
|
|
10
|
+
const client = db.config.connection.client;
|
|
11
|
+
const tableName = 'up_users'; // Strapi's default users table name
|
|
12
|
+
|
|
13
|
+
// Check if database columns already exist by querying the database directly
|
|
14
|
+
let fieldsExist = false;
|
|
15
|
+
try {
|
|
16
|
+
const connection = db.connection;
|
|
17
|
+
const knex = connection;
|
|
18
|
+
|
|
19
|
+
// Check if columns exist in the database
|
|
20
|
+
if (client === 'postgres') {
|
|
21
|
+
const result = await knex.raw(`
|
|
22
|
+
SELECT column_name
|
|
23
|
+
FROM information_schema.columns
|
|
24
|
+
WHERE table_name='${tableName}'
|
|
25
|
+
AND (column_name='otp' OR column_name='is_otp_verified')
|
|
26
|
+
`);
|
|
27
|
+
fieldsExist = result.rows.length >= 2;
|
|
28
|
+
} else if (client === 'mysql' || client === 'mysql2') {
|
|
29
|
+
const result = await knex.raw(`
|
|
30
|
+
SELECT COLUMN_NAME
|
|
31
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
32
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
33
|
+
AND TABLE_NAME = '${tableName}'
|
|
34
|
+
AND (COLUMN_NAME = 'otp' OR COLUMN_NAME = 'is_otp_verified')
|
|
35
|
+
`);
|
|
36
|
+
fieldsExist = result[0].length >= 2;
|
|
37
|
+
} else {
|
|
38
|
+
// SQLite
|
|
39
|
+
const tableInfo = await knex.raw(`PRAGMA table_info(${tableName})`);
|
|
40
|
+
const columns = tableInfo.map((col) => col.name);
|
|
41
|
+
fieldsExist = columns.includes('otp') && columns.includes('is_otp_verified');
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
fieldsExist = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (fieldsExist) {
|
|
48
|
+
strapi.log.info('[webbycommerce] OTP fields already exist in database');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
strapi.log.info('[webbycommerce] OTP fields not found, adding them to user schema...');
|
|
53
|
+
|
|
54
|
+
// Get the database connection
|
|
55
|
+
const connection = db.connection;
|
|
56
|
+
const knex = connection;
|
|
57
|
+
|
|
58
|
+
// Add columns based on database type
|
|
59
|
+
let otpAdded = false;
|
|
60
|
+
let isOtpVerifiedAdded = false;
|
|
61
|
+
|
|
62
|
+
if (client === 'sqlite' || client === 'sqlite3') {
|
|
63
|
+
// SQLite doesn't support ALTER TABLE ADD COLUMN IF NOT EXISTS directly
|
|
64
|
+
// We need to check if column exists first
|
|
65
|
+
const tableInfo = await knex.raw(`PRAGMA table_info(${tableName})`);
|
|
66
|
+
const columns = tableInfo.map((col) => col.name);
|
|
67
|
+
|
|
68
|
+
if (!columns.includes('otp')) {
|
|
69
|
+
await knex.schema.alterTable(tableName, (table) => {
|
|
70
|
+
table.integer('otp').nullable();
|
|
71
|
+
});
|
|
72
|
+
otpAdded = true;
|
|
73
|
+
strapi.log.info('[webbycommerce] Added "otp" column to user table');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!columns.includes('is_otp_verified')) {
|
|
77
|
+
await knex.schema.alterTable(tableName, (table) => {
|
|
78
|
+
table.boolean('is_otp_verified').defaultTo(false);
|
|
79
|
+
});
|
|
80
|
+
isOtpVerifiedAdded = true;
|
|
81
|
+
strapi.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
|
|
82
|
+
}
|
|
83
|
+
} else if (client === 'postgres') {
|
|
84
|
+
// PostgreSQL
|
|
85
|
+
const otpExists = await knex.raw(`
|
|
86
|
+
SELECT column_name
|
|
87
|
+
FROM information_schema.columns
|
|
88
|
+
WHERE table_name='${tableName}' AND column_name='otp'
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
if (otpExists.rows.length === 0) {
|
|
92
|
+
await knex.raw(`ALTER TABLE ${tableName} ADD COLUMN otp INTEGER`);
|
|
93
|
+
otpAdded = true;
|
|
94
|
+
strapi.log.info('[webbycommerce] Added "otp" column to user table');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const isOtpVerifiedExists = await knex.raw(`
|
|
98
|
+
SELECT column_name
|
|
99
|
+
FROM information_schema.columns
|
|
100
|
+
WHERE table_name='${tableName}' AND column_name='is_otp_verified'
|
|
101
|
+
`);
|
|
102
|
+
|
|
103
|
+
if (isOtpVerifiedExists.rows.length === 0) {
|
|
104
|
+
await knex.raw(`ALTER TABLE ${tableName} ADD COLUMN is_otp_verified BOOLEAN DEFAULT false`);
|
|
105
|
+
isOtpVerifiedAdded = true;
|
|
106
|
+
strapi.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
|
|
107
|
+
}
|
|
108
|
+
} else if (client === 'mysql' || client === 'mysql2') {
|
|
109
|
+
// MySQL
|
|
110
|
+
const otpExists = await knex.raw(`
|
|
111
|
+
SELECT COLUMN_NAME
|
|
112
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
113
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
114
|
+
AND TABLE_NAME = '${tableName}'
|
|
115
|
+
AND COLUMN_NAME = 'otp'
|
|
116
|
+
`);
|
|
117
|
+
|
|
118
|
+
if (otpExists[0].length === 0) {
|
|
119
|
+
await knex.raw(`ALTER TABLE \`${tableName}\` ADD COLUMN \`otp\` INT NULL`);
|
|
120
|
+
otpAdded = true;
|
|
121
|
+
strapi.log.info('[webbycommerce] Added "otp" column to user table');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const isOtpVerifiedExists = await knex.raw(`
|
|
125
|
+
SELECT COLUMN_NAME
|
|
126
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
127
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
128
|
+
AND TABLE_NAME = '${tableName}'
|
|
129
|
+
AND COLUMN_NAME = 'is_otp_verified'
|
|
130
|
+
`);
|
|
131
|
+
|
|
132
|
+
if (isOtpVerifiedExists[0].length === 0) {
|
|
133
|
+
await knex.raw(`ALTER TABLE \`${tableName}\` ADD COLUMN \`is_otp_verified\` BOOLEAN DEFAULT false`);
|
|
134
|
+
isOtpVerifiedAdded = true;
|
|
135
|
+
strapi.log.info('[webbycommerce] Added "is_otp_verified" column to user table');
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
strapi.log.warn(
|
|
139
|
+
`[webbycommerce] Database client "${client}" not supported for automatic schema extension. Please manually add OTP fields to user schema.`
|
|
140
|
+
);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Note: We don't modify the content-type schema at runtime as it breaks the admin panel
|
|
145
|
+
// The raw SQL queries in the controller will work with the database columns directly
|
|
146
|
+
// If you need the fields to appear in the admin panel, create a schema extension file
|
|
147
|
+
// in your main Strapi project: src/extensions/users-permissions/content-types/user/schema.json
|
|
148
|
+
|
|
149
|
+
strapi.log.info('[webbycommerce] User schema extension completed successfully');
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
strapi.log.error('[webbycommerce] Failed to extend user schema with OTP fields:', error);
|
|
153
|
+
strapi.log.error('[webbycommerce] Error details:', error.message);
|
|
154
|
+
strapi.log.error(
|
|
155
|
+
'[webbycommerce] Please manually extend the user schema by creating a schema extension file in your Strapi project.'
|
|
156
|
+
);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { extendUserSchemaWithOtpFields };
|