@webbycrown/webbycommerce 1.2.1 → 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 +21 -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-Bg2JyQ4c.js +0 -31518
- package/dist/_chunks/Settings-BonPzbwr.mjs +0 -31499
- package/dist/_chunks/en-DE15m4xZ.mjs +0 -574
- package/dist/_chunks/index-BWVy9o1d.mjs +0 -128
- package/dist/_chunks/index-NRuOdjd7.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 -27336
- 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,388 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PLUGIN_ID = 'webbycommerce';
|
|
4
|
+
const { ensureEcommercePermission } = require('../utils/check-ecommerce-permission');
|
|
5
|
+
|
|
6
|
+
const uniqBy = (items, keyFn) => {
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
return (Array.isArray(items) ? items : []).filter((item) => {
|
|
9
|
+
const key = keyFn(item);
|
|
10
|
+
if (seen.has(key)) return false;
|
|
11
|
+
seen.add(key);
|
|
12
|
+
return true;
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
async getProductCategories(ctx) {
|
|
18
|
+
try {
|
|
19
|
+
if (!(await ensureEcommercePermission(ctx))) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { search, limit = 50, start = 0, publicationState, status, preview } = ctx.query;
|
|
24
|
+
|
|
25
|
+
// Strapi v5 stores drafts + published versions as separate rows (document model).
|
|
26
|
+
// For storefront/public APIs we should default to published records to avoid duplicates.
|
|
27
|
+
const includeDrafts =
|
|
28
|
+
preview === 'true' || publicationState === 'preview' || status === 'preview' || status === 'draft';
|
|
29
|
+
|
|
30
|
+
const where = {};
|
|
31
|
+
if (search) {
|
|
32
|
+
where.name = { $containsi: search };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!includeDrafts) {
|
|
36
|
+
where.publishedAt = { $notNull: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const productCategories = await strapi.db.query('plugin::webbycommerce.product-category').findMany({
|
|
40
|
+
where,
|
|
41
|
+
limit: parseInt(limit, 10),
|
|
42
|
+
start: parseInt(start, 10),
|
|
43
|
+
orderBy: { createdAt: 'desc' },
|
|
44
|
+
populate: includeDrafts
|
|
45
|
+
? ['products', 'image']
|
|
46
|
+
: {
|
|
47
|
+
products: {
|
|
48
|
+
where: { publishedAt: { $notNull: true } },
|
|
49
|
+
},
|
|
50
|
+
image: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!includeDrafts && Array.isArray(productCategories)) {
|
|
55
|
+
for (const category of productCategories) {
|
|
56
|
+
if (Array.isArray(category?.products)) {
|
|
57
|
+
// Safety net in case relation-level filtering is not applied by the ORM in some scenarios.
|
|
58
|
+
const publishedOnly = category.products.filter((p) => p && p.publishedAt);
|
|
59
|
+
category.products = uniqBy(publishedOnly, (p) => p.documentId || String(p.id));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const total = await strapi.db.query('plugin::webbycommerce.product-category').count({ where });
|
|
65
|
+
|
|
66
|
+
ctx.send({ data: productCategories, meta: { total, limit: parseInt(limit, 10), start: parseInt(start, 10) } });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in getProductCategories:`, error);
|
|
69
|
+
ctx.internalServerError('Failed to fetch product categories. Please try again.');
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async getProductCategory(ctx) {
|
|
74
|
+
try {
|
|
75
|
+
if (!(await ensureEcommercePermission(ctx))) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rawId = ctx.params?.id;
|
|
80
|
+
const idOrSlug = typeof rawId === 'string' ? decodeURIComponent(rawId).trim() : '';
|
|
81
|
+
const { publicationState, status, preview } = ctx.query;
|
|
82
|
+
const includeDrafts =
|
|
83
|
+
preview === 'true' || publicationState === 'preview' || status === 'preview' || status === 'draft';
|
|
84
|
+
|
|
85
|
+
if (!idOrSlug) {
|
|
86
|
+
return ctx.badRequest('Category ID or slug is required.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if it's a numeric ID or a slug
|
|
90
|
+
const isNumericId = /^[0-9]+$/.test(idOrSlug);
|
|
91
|
+
let productCategory;
|
|
92
|
+
|
|
93
|
+
if (isNumericId) {
|
|
94
|
+
// Query by ID
|
|
95
|
+
productCategory = await strapi.db.query('plugin::webbycommerce.product-category').findOne({
|
|
96
|
+
where: includeDrafts ? { id: idOrSlug } : { id: idOrSlug, publishedAt: { $notNull: true } },
|
|
97
|
+
populate: includeDrafts
|
|
98
|
+
? ['products', 'image', 'parent']
|
|
99
|
+
: {
|
|
100
|
+
products: {
|
|
101
|
+
where: { publishedAt: { $notNull: true } },
|
|
102
|
+
},
|
|
103
|
+
image: true,
|
|
104
|
+
parent: true,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
// Query by slug
|
|
109
|
+
const where = includeDrafts ? { slug: idOrSlug } : { slug: idOrSlug, publishedAt: { $notNull: true } };
|
|
110
|
+
|
|
111
|
+
// For slug queries, we might get multiple results (drafts + published), so get the latest published one
|
|
112
|
+
const results = await strapi.db.query('plugin::webbycommerce.product-category').findMany({
|
|
113
|
+
where,
|
|
114
|
+
limit: 1,
|
|
115
|
+
orderBy: { publishedAt: 'desc', id: 'desc' },
|
|
116
|
+
populate: includeDrafts
|
|
117
|
+
? ['products', 'image', 'parent']
|
|
118
|
+
: {
|
|
119
|
+
products: {
|
|
120
|
+
where: { publishedAt: { $notNull: true } },
|
|
121
|
+
},
|
|
122
|
+
image: true,
|
|
123
|
+
parent: true,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
productCategory = results?.[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!productCategory) {
|
|
131
|
+
return ctx.notFound('Product category not found.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!includeDrafts && Array.isArray(productCategory?.products)) {
|
|
135
|
+
const publishedOnly = productCategory.products.filter((p) => p && p.publishedAt);
|
|
136
|
+
productCategory.products = uniqBy(publishedOnly, (p) => p.documentId || String(p.id));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ctx.send({ data: productCategory });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in getProductCategory:`, error);
|
|
142
|
+
ctx.internalServerError('Failed to fetch product category. Please try again.');
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async createProductCategory(ctx) {
|
|
147
|
+
try {
|
|
148
|
+
if (!(await ensureEcommercePermission(ctx))) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { name, slug, description, image, parent } = ctx.request.body || {};
|
|
153
|
+
|
|
154
|
+
if (!name) {
|
|
155
|
+
return ctx.badRequest('Name is required.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const data = {
|
|
159
|
+
name,
|
|
160
|
+
slug: slug || undefined,
|
|
161
|
+
description: description || undefined,
|
|
162
|
+
image: image || undefined,
|
|
163
|
+
publishedAt: new Date(), // Set publishedAt so it appears in content manager
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Handle parent relation if provided
|
|
167
|
+
if (parent) {
|
|
168
|
+
data.parent = typeof parent === 'object' && parent.id ? parent.id : parent;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Use entityService for Strapi v5 document model compatibility
|
|
172
|
+
// This ensures categories appear in the content manager
|
|
173
|
+
const productCategory = await strapi.entityService.create('plugin::webbycommerce.product-category', {
|
|
174
|
+
data,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Re-fetch with populated relations
|
|
178
|
+
const populated = await strapi.entityService.findOne('plugin::webbycommerce.product-category', productCategory.id, {
|
|
179
|
+
populate: ['products', 'image', 'parent'],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
ctx.send({ data: populated || productCategory });
|
|
183
|
+
} catch (error) {
|
|
184
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in createProductCategory:`, error);
|
|
185
|
+
ctx.internalServerError('Failed to create product category. Please try again.');
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async updateProductCategory(ctx) {
|
|
190
|
+
try {
|
|
191
|
+
if (!(await ensureEcommercePermission(ctx))) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const { id } = ctx.params;
|
|
196
|
+
const updateData = ctx.request.body || {};
|
|
197
|
+
|
|
198
|
+
const updated = await strapi.db.query('plugin::webbycommerce.product-category').update({
|
|
199
|
+
where: { id },
|
|
200
|
+
data: updateData,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!updated) {
|
|
204
|
+
return ctx.notFound('Product category not found.');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ctx.send({ data: updated });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in updateProductCategory:`, error);
|
|
210
|
+
ctx.internalServerError('Failed to update product category. Please try again.');
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
async deleteProductCategory(ctx) {
|
|
215
|
+
try {
|
|
216
|
+
if (!(await ensureEcommercePermission(ctx))) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { id } = ctx.params;
|
|
221
|
+
|
|
222
|
+
const existing = await strapi.db.query('plugin::webbycommerce.product-category').findOne({ where: { id } });
|
|
223
|
+
if (!existing) {
|
|
224
|
+
return ctx.notFound('Product category not found.');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await strapi.db.query('plugin::webbycommerce.product-category').delete({ where: { id } });
|
|
228
|
+
|
|
229
|
+
ctx.send({ data: { id } });
|
|
230
|
+
} catch (error) {
|
|
231
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in deleteProductCategory:`, error);
|
|
232
|
+
ctx.internalServerError('Failed to delete product category. Please try again.');
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Standard Strapi controller methods for content manager
|
|
237
|
+
async find(ctx) {
|
|
238
|
+
try {
|
|
239
|
+
const { search, limit = 25, start = 0, sort = 'createdAt:desc' } = ctx.query;
|
|
240
|
+
|
|
241
|
+
const where = { publishedAt: { $notNull: true } };
|
|
242
|
+
|
|
243
|
+
if (search) {
|
|
244
|
+
where.name = { $containsi: search };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const categories = await strapi.db.query('plugin::webbycommerce.product-category').findMany({
|
|
248
|
+
where,
|
|
249
|
+
limit: parseInt(limit, 10),
|
|
250
|
+
start: parseInt(start, 10),
|
|
251
|
+
orderBy: sort.split(':').reduce((acc, val, i) => {
|
|
252
|
+
if (i === 0) acc[val] = 'asc';
|
|
253
|
+
else acc[val] = 'desc';
|
|
254
|
+
return acc;
|
|
255
|
+
}, {}),
|
|
256
|
+
populate: ['products', 'image'],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const total = await strapi.db.query('plugin::webbycommerce.product-category').count({ where });
|
|
260
|
+
|
|
261
|
+
ctx.send({
|
|
262
|
+
data: categories,
|
|
263
|
+
meta: {
|
|
264
|
+
pagination: {
|
|
265
|
+
page: Math.floor(parseInt(start, 10) / parseInt(limit, 10)) + 1,
|
|
266
|
+
pageSize: parseInt(limit, 10),
|
|
267
|
+
pageCount: Math.ceil(total / parseInt(limit, 10)),
|
|
268
|
+
total,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in find:`, error);
|
|
274
|
+
ctx.internalServerError('Failed to fetch product categories.');
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
async findOne(ctx) {
|
|
279
|
+
try {
|
|
280
|
+
const rawId = ctx.params?.id;
|
|
281
|
+
const idOrSlug = typeof rawId === 'string' ? decodeURIComponent(rawId).trim() : '';
|
|
282
|
+
|
|
283
|
+
if (!idOrSlug) {
|
|
284
|
+
return ctx.badRequest('Category ID or slug is required.');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check if it's a numeric ID or a slug
|
|
288
|
+
const isNumericId = /^[0-9]+$/.test(idOrSlug);
|
|
289
|
+
let category;
|
|
290
|
+
|
|
291
|
+
if (isNumericId) {
|
|
292
|
+
// Query by ID
|
|
293
|
+
category = await strapi.db.query('plugin::webbycommerce.product-category').findOne({
|
|
294
|
+
where: { id: idOrSlug, publishedAt: { $notNull: true } },
|
|
295
|
+
populate: ['products', 'image', 'parent'],
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
// Query by slug
|
|
299
|
+
const results = await strapi.db.query('plugin::webbycommerce.product-category').findMany({
|
|
300
|
+
where: { slug: idOrSlug, publishedAt: { $notNull: true } },
|
|
301
|
+
limit: 1,
|
|
302
|
+
orderBy: { publishedAt: 'desc', id: 'desc' },
|
|
303
|
+
populate: ['products', 'image', 'parent'],
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
category = results?.[0];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!category) {
|
|
310
|
+
return ctx.notFound('Product category not found.');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
ctx.send({ data: category });
|
|
314
|
+
} catch (error) {
|
|
315
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in findOne:`, error);
|
|
316
|
+
ctx.internalServerError('Failed to fetch product category.');
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
async create(ctx) {
|
|
321
|
+
try {
|
|
322
|
+
const data = ctx.request.body.data || ctx.request.body;
|
|
323
|
+
|
|
324
|
+
if (!data.publishedAt) {
|
|
325
|
+
data.publishedAt = new Date();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Use entityService for Strapi v5 document model compatibility
|
|
329
|
+
const category = await strapi.entityService.create('plugin::webbycommerce.product-category', {
|
|
330
|
+
data,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const populated = await strapi.entityService.findOne('plugin::webbycommerce.product-category', category.id, {
|
|
334
|
+
populate: ['products', 'image', 'parent'],
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
ctx.send({ data: populated || category });
|
|
338
|
+
} catch (error) {
|
|
339
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in create:`, error);
|
|
340
|
+
ctx.internalServerError('Failed to create product category.');
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
async update(ctx) {
|
|
345
|
+
try {
|
|
346
|
+
const { id } = ctx.params;
|
|
347
|
+
const data = ctx.request.body.data || ctx.request.body;
|
|
348
|
+
|
|
349
|
+
const category = await strapi.db.query('plugin::webbycommerce.product-category').update({
|
|
350
|
+
where: { id },
|
|
351
|
+
data,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!category) {
|
|
355
|
+
return ctx.notFound('Product category not found.');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const populated = await strapi.db.query('plugin::webbycommerce.product-category').findOne({
|
|
359
|
+
where: { id: category.id },
|
|
360
|
+
populate: ['products', 'image'],
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
ctx.send({ data: populated || category });
|
|
364
|
+
} catch (error) {
|
|
365
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in update:`, error);
|
|
366
|
+
ctx.internalServerError('Failed to update product category.');
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
async delete(ctx) {
|
|
371
|
+
try {
|
|
372
|
+
const { id } = ctx.params;
|
|
373
|
+
|
|
374
|
+
const category = await strapi.db.query('plugin::webbycommerce.product-category').delete({
|
|
375
|
+
where: { id },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (!category) {
|
|
379
|
+
return ctx.notFound('Product category not found.');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
ctx.send({ data: category });
|
|
383
|
+
} catch (error) {
|
|
384
|
+
strapi.log.error(`[${PLUGIN_ID}] Error in delete:`, error);
|
|
385
|
+
ctx.internalServerError('Failed to delete product category.');
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* compare controller
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { createCoreController } = require('@strapi/strapi').factories;
|
|
8
|
+
|
|
9
|
+
module.exports = createCoreController('plugin::webbycommerce.compare', ({ strapi }) => ({
|
|
10
|
+
async getCompare(ctx) {
|
|
11
|
+
try {
|
|
12
|
+
const user = ctx.state.user;
|
|
13
|
+
if (!user) {
|
|
14
|
+
return ctx.unauthorized('Authentication required');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const compare = await strapi
|
|
18
|
+
.plugin('webbycommerce')
|
|
19
|
+
.service('compare')
|
|
20
|
+
.findUserCompare(user.id);
|
|
21
|
+
|
|
22
|
+
if (!compare) {
|
|
23
|
+
// Return empty compare structure
|
|
24
|
+
return ctx.send({
|
|
25
|
+
data: {
|
|
26
|
+
id: null,
|
|
27
|
+
userId: user.id,
|
|
28
|
+
userEmail: user.email,
|
|
29
|
+
products: [],
|
|
30
|
+
category: null,
|
|
31
|
+
isPublic: false,
|
|
32
|
+
name: null,
|
|
33
|
+
notes: null,
|
|
34
|
+
},
|
|
35
|
+
meta: {
|
|
36
|
+
totalProducts: 0,
|
|
37
|
+
comparisonData: {},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const compareData = await strapi
|
|
43
|
+
.plugin('webbycommerce')
|
|
44
|
+
.service('compare')
|
|
45
|
+
.getCompareData(user.id);
|
|
46
|
+
|
|
47
|
+
ctx.send({
|
|
48
|
+
data: compare,
|
|
49
|
+
meta: {
|
|
50
|
+
totalProducts: compare?.products?.length || 0,
|
|
51
|
+
comparisonData: compareData.comparisonData,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
strapi.log.error('Error fetching compare:', error);
|
|
56
|
+
ctx.badRequest('Failed to fetch compare list', { error: error.message });
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
async addToCompare(ctx) {
|
|
61
|
+
try {
|
|
62
|
+
const user = ctx.state.user;
|
|
63
|
+
if (!user) {
|
|
64
|
+
return ctx.unauthorized('Authentication required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { productId } = ctx.request.body;
|
|
68
|
+
if (!productId) {
|
|
69
|
+
return ctx.badRequest('Product ID is required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const compare = await strapi
|
|
73
|
+
.plugin('webbycommerce')
|
|
74
|
+
.service('compare')
|
|
75
|
+
.addProductToCompare(user.id, user.email, productId);
|
|
76
|
+
|
|
77
|
+
const compareData = await strapi
|
|
78
|
+
.plugin('webbycommerce')
|
|
79
|
+
.service('compare')
|
|
80
|
+
.getCompareData(user.id);
|
|
81
|
+
|
|
82
|
+
ctx.send({
|
|
83
|
+
data: compare,
|
|
84
|
+
meta: {
|
|
85
|
+
totalProducts: compare?.products?.length || 0,
|
|
86
|
+
comparisonData: compareData.comparisonData,
|
|
87
|
+
},
|
|
88
|
+
message: 'Product added to compare list successfully',
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
strapi.log.error('Error adding to compare:', error);
|
|
92
|
+
ctx.badRequest('Failed to add product to compare list', { error: error.message });
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async removeFromCompare(ctx) {
|
|
97
|
+
try {
|
|
98
|
+
const user = ctx.state.user;
|
|
99
|
+
if (!user) {
|
|
100
|
+
return ctx.unauthorized('Authentication required');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { productId } = ctx.params;
|
|
104
|
+
if (!productId) {
|
|
105
|
+
return ctx.badRequest('Product ID is required');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const compare = await strapi
|
|
109
|
+
.plugin('webbycommerce')
|
|
110
|
+
.service('compare')
|
|
111
|
+
.removeProductFromCompare(user.id, productId);
|
|
112
|
+
|
|
113
|
+
const compareData = await strapi
|
|
114
|
+
.plugin('webbycommerce')
|
|
115
|
+
.service('compare')
|
|
116
|
+
.getCompareData(user.id);
|
|
117
|
+
|
|
118
|
+
ctx.send({
|
|
119
|
+
data: compare,
|
|
120
|
+
meta: {
|
|
121
|
+
totalProducts: compare?.products?.length || 0,
|
|
122
|
+
comparisonData: compareData.comparisonData,
|
|
123
|
+
},
|
|
124
|
+
message: 'Product removed from compare list successfully',
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
strapi.log.error('Error removing from compare:', error);
|
|
128
|
+
ctx.badRequest('Failed to remove product from compare list', { error: error.message });
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
async clearCompare(ctx) {
|
|
133
|
+
try {
|
|
134
|
+
const user = ctx.state.user;
|
|
135
|
+
if (!user) {
|
|
136
|
+
return ctx.unauthorized('Authentication required');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const compare = await strapi
|
|
140
|
+
.plugin('webbycommerce')
|
|
141
|
+
.service('compare')
|
|
142
|
+
.clearCompare(user.id);
|
|
143
|
+
|
|
144
|
+
ctx.send({
|
|
145
|
+
data: compare,
|
|
146
|
+
meta: {
|
|
147
|
+
totalProducts: 0,
|
|
148
|
+
comparisonData: {},
|
|
149
|
+
},
|
|
150
|
+
message: 'Compare list cleared successfully',
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
strapi.log.error('Error clearing compare:', error);
|
|
154
|
+
ctx.badRequest('Failed to clear compare list', { error: error.message });
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
async updateCompare(ctx) {
|
|
159
|
+
try {
|
|
160
|
+
const user = ctx.state.user;
|
|
161
|
+
if (!user) {
|
|
162
|
+
return ctx.unauthorized('Authentication required');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { name, notes, isPublic } = ctx.request.body;
|
|
166
|
+
|
|
167
|
+
const compare = await strapi
|
|
168
|
+
.plugin('webbycommerce')
|
|
169
|
+
.service('compare')
|
|
170
|
+
.updateCompare(user.id, { name, notes, isPublic });
|
|
171
|
+
|
|
172
|
+
ctx.send({
|
|
173
|
+
data: compare,
|
|
174
|
+
message: 'Compare list updated successfully',
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
strapi.log.error('Error updating compare:', error);
|
|
178
|
+
ctx.badRequest('Failed to update compare list', { error: error.message });
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async getComparisonData(ctx) {
|
|
183
|
+
try {
|
|
184
|
+
const user = ctx.state.user;
|
|
185
|
+
if (!user) {
|
|
186
|
+
return ctx.unauthorized('Authentication required');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const compareData = await strapi
|
|
190
|
+
.plugin('webbycommerce')
|
|
191
|
+
.service('compare')
|
|
192
|
+
.getCompareData(user.id);
|
|
193
|
+
|
|
194
|
+
ctx.send({
|
|
195
|
+
data: compareData,
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
strapi.log.error('Error getting comparison data:', error);
|
|
199
|
+
ctx.badRequest('Failed to get comparison data', { error: error.message });
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
async checkCompareStatus(ctx) {
|
|
204
|
+
try {
|
|
205
|
+
const user = ctx.state.user;
|
|
206
|
+
if (!user) {
|
|
207
|
+
return ctx.unauthorized('Authentication required');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { productIds } = ctx.query;
|
|
211
|
+
if (!productIds) {
|
|
212
|
+
return ctx.badRequest('Product IDs are required');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const compare = await strapi
|
|
216
|
+
.plugin('webbycommerce')
|
|
217
|
+
.service('compare')
|
|
218
|
+
.findUserCompare(user.id);
|
|
219
|
+
|
|
220
|
+
const productIdArray = Array.isArray(productIds)
|
|
221
|
+
? productIds.map(id => parseInt(id))
|
|
222
|
+
: [parseInt(productIds)];
|
|
223
|
+
|
|
224
|
+
const inCompare = {};
|
|
225
|
+
if (compare && compare.products && Array.isArray(compare.products)) {
|
|
226
|
+
productIdArray.forEach(productId => {
|
|
227
|
+
inCompare[productId] = compare.products.some(product => {
|
|
228
|
+
const productIdValue = typeof product === 'object' && product !== null ? product.id : product;
|
|
229
|
+
return productIdValue === productId;
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
} else {
|
|
233
|
+
productIdArray.forEach(productId => {
|
|
234
|
+
inCompare[productId] = false;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ctx.send({
|
|
239
|
+
data: inCompare,
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
strapi.log.error('Error checking compare status:', error);
|
|
243
|
+
ctx.badRequest('Failed to check compare status', { error: error.message });
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
}));
|