merchi_sdk_ts 1.8.1 → 1.9.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/dist/entities/domain.js +12 -51
- package/dist/entities/domain.test.js +0 -126
- package/dist/entities/notification.test.js +30 -0
- package/dist/entity.js +8 -0
- package/dist/index.js +2 -1
- package/dist/merchi.js +2 -0
- package/package.json +1 -1
- package/src/entities/domain.test.ts +0 -136
- package/src/entities/domain.ts +9 -150
- package/src/entities/notification.test.ts +34 -0
- package/src/entities/product_review.ts +221 -0
- package/src/entity.ts +22 -1
- package/src/index.ts +6 -0
- package/src/merchi.ts +5 -0
package/dist/entities/domain.js
CHANGED
|
@@ -49,57 +49,6 @@ var Domain = /** @class */ (function (_super) {
|
|
|
49
49
|
}
|
|
50
50
|
return _this.activeTheme;
|
|
51
51
|
};
|
|
52
|
-
_this.getDomainId = function () {
|
|
53
|
-
if (_this.id === undefined || _this.id === null) {
|
|
54
|
-
throw new Error('id is undefined, did you forget to set it?');
|
|
55
|
-
}
|
|
56
|
-
return _this.id;
|
|
57
|
-
};
|
|
58
|
-
_this.storefrontV2DomainResource = function (suffix) {
|
|
59
|
-
if (suffix === void 0) { suffix = ''; }
|
|
60
|
-
return "/domains/".concat(_this.getDomainId(), "/storefront_v2/").concat(suffix);
|
|
61
|
-
};
|
|
62
|
-
_this.storefrontV2Request = function (resource, method, payload) {
|
|
63
|
-
var fetchOptions = {
|
|
64
|
-
method: method,
|
|
65
|
-
query: [['skip_rights', 'y']]
|
|
66
|
-
};
|
|
67
|
-
if (payload !== undefined) {
|
|
68
|
-
fetchOptions.body = JSON.stringify(payload);
|
|
69
|
-
fetchOptions.headers = { 'Content-Type': 'application/json' };
|
|
70
|
-
}
|
|
71
|
-
return _this.merchi.authenticatedFetch(resource, fetchOptions);
|
|
72
|
-
};
|
|
73
|
-
_this.getStorefrontV2 = function () {
|
|
74
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource(), 'GET');
|
|
75
|
-
};
|
|
76
|
-
_this.provisionStorefrontV2 = function (payload) {
|
|
77
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource('provision/'), 'POST', payload);
|
|
78
|
-
};
|
|
79
|
-
_this.createStorefrontChangeRequest = function (payload) {
|
|
80
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource('requests/'), 'POST', payload);
|
|
81
|
-
};
|
|
82
|
-
_this.getStorefrontChangeRequest = function (requestId) {
|
|
83
|
-
return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/"), 'GET');
|
|
84
|
-
};
|
|
85
|
-
_this.runStorefrontChangeRequest = function (requestId, payload) {
|
|
86
|
-
return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/run/"), 'POST', payload);
|
|
87
|
-
};
|
|
88
|
-
_this.approveStorefrontChangeRequest = function (requestId, payload) {
|
|
89
|
-
return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/approve/"), 'POST', payload);
|
|
90
|
-
};
|
|
91
|
-
_this.rejectStorefrontChangeRequest = function (requestId, payload) {
|
|
92
|
-
return _this.storefrontV2Request("/storefront_change_requests/".concat(String(requestId), "/reject/"), 'POST', payload);
|
|
93
|
-
};
|
|
94
|
-
_this.getStorefrontV2Deployments = function () {
|
|
95
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource('deployments/'), 'GET');
|
|
96
|
-
};
|
|
97
|
-
_this.getStorefrontV2DeploymentLogs = function (deploymentId) {
|
|
98
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource("deployments/".concat(String(deploymentId), "/logs/")), 'GET');
|
|
99
|
-
};
|
|
100
|
-
_this.rollbackStorefrontV2 = function (payload) {
|
|
101
|
-
return _this.storefrontV2Request(_this.storefrontV2DomainResource('rollback/'), 'POST', payload);
|
|
102
|
-
};
|
|
103
52
|
return _this;
|
|
104
53
|
}
|
|
105
54
|
Domain.resourceName = 'domains';
|
|
@@ -265,6 +214,18 @@ var Domain = /** @class */ (function (_super) {
|
|
|
265
214
|
Domain.property({ type: String }),
|
|
266
215
|
__metadata("design:type", String)
|
|
267
216
|
], Domain.prototype, "googleMerchantId", void 0);
|
|
217
|
+
__decorate([
|
|
218
|
+
Domain.property({ type: Boolean }),
|
|
219
|
+
__metadata("design:type", Boolean)
|
|
220
|
+
], Domain.prototype, "googleProductReviewsFeedEnabled", void 0);
|
|
221
|
+
__decorate([
|
|
222
|
+
Domain.property({ type: String }),
|
|
223
|
+
__metadata("design:type", Object)
|
|
224
|
+
], Domain.prototype, "googleProductReviewsFeedToken", void 0);
|
|
225
|
+
__decorate([
|
|
226
|
+
Domain.property({ type: String }),
|
|
227
|
+
__metadata("design:type", Object)
|
|
228
|
+
], Domain.prototype, "googleProductReviewsFeedUrl", void 0);
|
|
268
229
|
__decorate([
|
|
269
230
|
Domain.property({ type: String }),
|
|
270
231
|
__metadata("design:type", Object)
|
|
@@ -66,129 +66,3 @@ test('fail to delete non-existant domain', function () {
|
|
|
66
66
|
expect(fetch.mock.calls[0][1].method).toBe('DELETE');
|
|
67
67
|
return invocation.catch(function (e) { return expect(e.statusCode).toEqual(404); });
|
|
68
68
|
});
|
|
69
|
-
test('can get storefront v2 config', function () {
|
|
70
|
-
var merchi = new Merchi();
|
|
71
|
-
var domain = new merchi.Domain();
|
|
72
|
-
domain.id = 42;
|
|
73
|
-
var fetch = mockFetch(true, {}, 200);
|
|
74
|
-
domain.getStorefrontV2();
|
|
75
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
76
|
-
var query = fetch.mock.calls[0][1].query;
|
|
77
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
78
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/');
|
|
79
|
-
expect(query).toContainEqual(['skip_rights', 'y']);
|
|
80
|
-
});
|
|
81
|
-
test('can provision storefront v2', function () {
|
|
82
|
-
var merchi = new Merchi();
|
|
83
|
-
var domain = new merchi.Domain();
|
|
84
|
-
domain.id = 42;
|
|
85
|
-
var fetch = mockFetch(true, {}, 200);
|
|
86
|
-
domain.provisionStorefrontV2({ force: true });
|
|
87
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
88
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
89
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
90
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/provision/');
|
|
91
|
-
expect(body).toEqual({ force: true });
|
|
92
|
-
});
|
|
93
|
-
test('can create storefront v2 change request', function () {
|
|
94
|
-
var merchi = new Merchi();
|
|
95
|
-
var domain = new merchi.Domain();
|
|
96
|
-
domain.id = 42;
|
|
97
|
-
var fetch = mockFetch(true, {}, 200);
|
|
98
|
-
domain.createStorefrontChangeRequest({ prompt: 'update hero' });
|
|
99
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
100
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
101
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
102
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/requests/');
|
|
103
|
-
expect(body).toEqual({ prompt: 'update hero' });
|
|
104
|
-
});
|
|
105
|
-
test('can get storefront v2 change request by id', function () {
|
|
106
|
-
var merchi = new Merchi();
|
|
107
|
-
var domain = new merchi.Domain();
|
|
108
|
-
var fetch = mockFetch(true, {}, 200);
|
|
109
|
-
domain.getStorefrontChangeRequest(9);
|
|
110
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
111
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
112
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/');
|
|
113
|
-
});
|
|
114
|
-
test('can run storefront v2 change request', function () {
|
|
115
|
-
var merchi = new Merchi();
|
|
116
|
-
var domain = new merchi.Domain();
|
|
117
|
-
var fetch = mockFetch(true, {}, 200);
|
|
118
|
-
domain.runStorefrontChangeRequest(9, {
|
|
119
|
-
status: 'running',
|
|
120
|
-
pullRequestNumber: 42,
|
|
121
|
-
checksSummary: {
|
|
122
|
-
overall: 'passing',
|
|
123
|
-
counts: { total: 4, passed: 4, failed: 0, pending: 0, neutral: 0 },
|
|
124
|
-
},
|
|
125
|
-
checksUpdatedAt: '2026-05-15T00:00:00Z',
|
|
126
|
-
});
|
|
127
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
128
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
129
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
130
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/run/');
|
|
131
|
-
expect(body).toEqual({
|
|
132
|
-
status: 'running',
|
|
133
|
-
pullRequestNumber: 42,
|
|
134
|
-
checksSummary: {
|
|
135
|
-
overall: 'passing',
|
|
136
|
-
counts: { total: 4, passed: 4, failed: 0, pending: 0, neutral: 0 },
|
|
137
|
-
},
|
|
138
|
-
checksUpdatedAt: '2026-05-15T00:00:00Z',
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
test('can approve storefront v2 change request', function () {
|
|
142
|
-
var merchi = new Merchi();
|
|
143
|
-
var domain = new merchi.Domain();
|
|
144
|
-
var fetch = mockFetch(true, {}, 200);
|
|
145
|
-
domain.approveStorefrontChangeRequest(9, { comment: 'looks good' });
|
|
146
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
147
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
148
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
149
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/approve/');
|
|
150
|
-
expect(body).toEqual({ comment: 'looks good' });
|
|
151
|
-
});
|
|
152
|
-
test('can reject storefront v2 change request', function () {
|
|
153
|
-
var merchi = new Merchi();
|
|
154
|
-
var domain = new merchi.Domain();
|
|
155
|
-
var fetch = mockFetch(true, {}, 200);
|
|
156
|
-
domain.rejectStorefrontChangeRequest(9, { reason: 'needs edits' });
|
|
157
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
158
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
159
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
160
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/reject/');
|
|
161
|
-
expect(body).toEqual({ reason: 'needs edits' });
|
|
162
|
-
});
|
|
163
|
-
test('can get storefront v2 deployments', function () {
|
|
164
|
-
var merchi = new Merchi();
|
|
165
|
-
var domain = new merchi.Domain();
|
|
166
|
-
domain.id = 42;
|
|
167
|
-
var fetch = mockFetch(true, {}, 200);
|
|
168
|
-
domain.getStorefrontV2Deployments();
|
|
169
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
170
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
171
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/');
|
|
172
|
-
});
|
|
173
|
-
test('can get storefront v2 deployment logs', function () {
|
|
174
|
-
var merchi = new Merchi();
|
|
175
|
-
var domain = new merchi.Domain();
|
|
176
|
-
domain.id = 42;
|
|
177
|
-
var fetch = mockFetch(true, {}, 200);
|
|
178
|
-
domain.getStorefrontV2DeploymentLogs('dep_123');
|
|
179
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
180
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
181
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/dep_123/logs/');
|
|
182
|
-
});
|
|
183
|
-
test('can rollback storefront v2 deployment', function () {
|
|
184
|
-
var merchi = new Merchi();
|
|
185
|
-
var domain = new merchi.Domain();
|
|
186
|
-
domain.id = 42;
|
|
187
|
-
var fetch = mockFetch(true, {}, 200);
|
|
188
|
-
domain.rollbackStorefrontV2({ deploymentId: 'dep_123' });
|
|
189
|
-
var fetchUrl = fetch.mock.calls[0][0];
|
|
190
|
-
var body = JSON.parse(fetch.mock.calls[0][1].body);
|
|
191
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
192
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/rollback/');
|
|
193
|
-
expect(body).toEqual({ deploymentId: 'dep_123' });
|
|
194
|
-
});
|
|
@@ -19,3 +19,33 @@ test('can list notifications with notificationSender filter', function () {
|
|
|
19
19
|
expect(query).toContainEqual(['notification_sender', '123']);
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
|
+
test('list forwards skipCount=true as skip_count query param', function () {
|
|
23
|
+
var merchi = new Merchi();
|
|
24
|
+
var fetch = mockFetch(true, {
|
|
25
|
+
'notifications': [],
|
|
26
|
+
// backend returns ``available: null`` when skip_count was honoured
|
|
27
|
+
'available': null,
|
|
28
|
+
'count': 0
|
|
29
|
+
}, 200);
|
|
30
|
+
var options = { skipCount: true };
|
|
31
|
+
return merchi.Notification.list(options).then(function (response) {
|
|
32
|
+
var query = fetch.mock.calls[0][1]['query'];
|
|
33
|
+
expect(query).toContainEqual(['skip_count', 'true']);
|
|
34
|
+
// ``available`` must round-trip as ``null``; the typed metadata
|
|
35
|
+
// is now ``number | null``, so consumers can detect the absence.
|
|
36
|
+
expect(response.metadata.available).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
test('list omits skip_count when skipCount is not set', function () {
|
|
40
|
+
var merchi = new Merchi();
|
|
41
|
+
var fetch = mockFetch(true, {
|
|
42
|
+
'notifications': [],
|
|
43
|
+
'available': 0,
|
|
44
|
+
'count': 0
|
|
45
|
+
}, 200);
|
|
46
|
+
return merchi.Notification.list({}).then(function () {
|
|
47
|
+
var query = fetch.mock.calls[0][1]['query'];
|
|
48
|
+
var skipCountEntries = query.filter(function (entry) { return entry[0] === 'skip_count'; });
|
|
49
|
+
expect(skipCountEntries).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
});
|
package/dist/entity.js
CHANGED
|
@@ -614,6 +614,14 @@ var Entity = /** @class */ (function () {
|
|
|
614
614
|
if (options.limit !== undefined) {
|
|
615
615
|
fetchOptions.query.push(['limit', options.limit.toString()]);
|
|
616
616
|
}
|
|
617
|
+
if (options.skipCount !== undefined) {
|
|
618
|
+
// Snake-case is the canonical form on the server; the API
|
|
619
|
+
// also accepts ``skipCount`` via its camelCase alias, but
|
|
620
|
+
// matching the wire shape of the other ``*_only`` /
|
|
621
|
+
// ``*_filter`` flags keeps logs and traffic captures
|
|
622
|
+
// consistent.
|
|
623
|
+
fetchOptions.query.push(['skip_count', options.skipCount.toString()]);
|
|
624
|
+
}
|
|
617
625
|
if (options.q !== undefined) {
|
|
618
626
|
fetchOptions.query.push(['q', options.q]);
|
|
619
627
|
}
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ import { Payment } from './entities/payment.js';
|
|
|
51
51
|
import { PaymentDevice } from './entities/payment_device.js';
|
|
52
52
|
import { PhoneNumber } from './entities/phone_number.js';
|
|
53
53
|
import { Product } from './entities/product.js';
|
|
54
|
+
import { ProductReview } from './entities/product_review.js';
|
|
54
55
|
import { ProductionComment } from './entities/production_comment.js';
|
|
55
56
|
import { Quote } from './entities/quote.js';
|
|
56
57
|
import { QuoteItem } from './entities/quote_item.js';
|
|
@@ -82,4 +83,4 @@ import * as constants from './constants/index.js';
|
|
|
82
83
|
import * as request from './request.js';
|
|
83
84
|
import * as util from './util/index.js';
|
|
84
85
|
import { toastNotifications } from './toasts.js';
|
|
85
|
-
export { constants, request, util, toastNotifications, Address, AgentConversation, AgentSkill, AgentSkillVersion, AgentSkillApproval, DomainChatSettings, SupportConversation, SupportMessage, Assignment, AutomaticPaymentRelationship, Bank, Cart, CartItem, CartShipmentGroup, CartShipmentQuote, Category, Company, CompanyInvitation, Component, ComponentTag, ComponentVersion, CountryTax, Discount, DiscountGroup, Domain, DomainInvitation, DomainTag, Draft, DraftComment, DraftTemplate, EmailAddress, EmailCounter, EnrolledDomain, Entity, ExchangeRate, InternalTag, Inventory, InventoryUnitVariation, Invoice, Item, Job, JobComment, MatchingInventory, Menu, MenuItem, Merchi, MerchiFile, Notification, Page, Payment, PaymentDevice, PhoneNumber, Product, ProductionComment, Quote, QuoteItem, Reminder, apiFetch, apiFetchWithProgress, Session, Shipment, ShipmentLog, ShipmentItem, ShipmentItemFulfillment, ShipmentMethod, ShipmentMethodVariation, ShortUrl, SubscriptionPlan, SupplyDomain, SystemRole, Theme, ThemeCssSetting, User, UserCompany, Variation, VariationField, VariationFieldsOption, VariationOption, VariationsGroup, generateUUID, getCookie };
|
|
86
|
+
export { constants, request, util, toastNotifications, Address, AgentConversation, AgentSkill, AgentSkillVersion, AgentSkillApproval, DomainChatSettings, SupportConversation, SupportMessage, Assignment, AutomaticPaymentRelationship, Bank, Cart, CartItem, CartShipmentGroup, CartShipmentQuote, Category, Company, CompanyInvitation, Component, ComponentTag, ComponentVersion, CountryTax, Discount, DiscountGroup, Domain, DomainInvitation, DomainTag, Draft, DraftComment, DraftTemplate, EmailAddress, EmailCounter, EnrolledDomain, Entity, ExchangeRate, InternalTag, Inventory, InventoryUnitVariation, Invoice, Item, Job, JobComment, MatchingInventory, Menu, MenuItem, Merchi, MerchiFile, Notification, Page, Payment, PaymentDevice, PhoneNumber, Product, ProductReview, ProductionComment, Quote, QuoteItem, Reminder, apiFetch, apiFetchWithProgress, Session, Shipment, ShipmentLog, ShipmentItem, ShipmentItemFulfillment, ShipmentMethod, ShipmentMethodVariation, ShortUrl, SubscriptionPlan, SupplyDomain, SystemRole, Theme, ThemeCssSetting, User, UserCompany, Variation, VariationField, VariationFieldsOption, VariationOption, VariationsGroup, generateUUID, getCookie };
|
package/dist/merchi.js
CHANGED
|
@@ -20,6 +20,7 @@ import { VariationField } from './entities/variation_field.js';
|
|
|
20
20
|
import { VariationOption } from './entities/variation_option.js';
|
|
21
21
|
import { ProductionComment } from './entities/production_comment.js';
|
|
22
22
|
import { Product } from './entities/product.js';
|
|
23
|
+
import { ProductReview } from './entities/product_review.js';
|
|
23
24
|
import { InternalTag } from './entities/internal_tag.js';
|
|
24
25
|
import { Inventory } from './entities/inventory.js';
|
|
25
26
|
import { InventoryGroup } from './entities/inventory_group.js';
|
|
@@ -254,6 +255,7 @@ var Merchi = /** @class */ (function () {
|
|
|
254
255
|
this.CountryTax = this.setupClass(CountryTax);
|
|
255
256
|
this.ShortUrl = this.setupClass(ShortUrl);
|
|
256
257
|
this.Product = this.setupClass(Product);
|
|
258
|
+
this.ProductReview = this.setupClass(ProductReview);
|
|
257
259
|
this.SystemRole = this.setupClass(SystemRole);
|
|
258
260
|
this.CartItem = this.setupClass(CartItem);
|
|
259
261
|
this.UserCompany = this.setupClass(UserCompany);
|
package/package.json
CHANGED
|
@@ -77,139 +77,3 @@ test('fail to delete non-existant domain', () => {
|
|
|
77
77
|
expect(fetch.mock.calls[0][1].method).toBe('DELETE');
|
|
78
78
|
return invocation.catch(e => expect(e.statusCode).toEqual(404));
|
|
79
79
|
});
|
|
80
|
-
|
|
81
|
-
test('can get storefront v2 config', () => {
|
|
82
|
-
const merchi = new Merchi();
|
|
83
|
-
const domain = new merchi.Domain();
|
|
84
|
-
domain.id = 42;
|
|
85
|
-
const fetch = mockFetch(true, {}, 200);
|
|
86
|
-
domain.getStorefrontV2();
|
|
87
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
88
|
-
const query = fetch.mock.calls[0][1].query;
|
|
89
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
90
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/');
|
|
91
|
-
expect(query).toContainEqual(['skip_rights', 'y']);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('can provision storefront v2', () => {
|
|
95
|
-
const merchi = new Merchi();
|
|
96
|
-
const domain = new merchi.Domain();
|
|
97
|
-
domain.id = 42;
|
|
98
|
-
const fetch = mockFetch(true, {}, 200);
|
|
99
|
-
domain.provisionStorefrontV2({force: true});
|
|
100
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
101
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
102
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
103
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/provision/');
|
|
104
|
-
expect(body).toEqual({force: true});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('can create storefront v2 change request', () => {
|
|
108
|
-
const merchi = new Merchi();
|
|
109
|
-
const domain = new merchi.Domain();
|
|
110
|
-
domain.id = 42;
|
|
111
|
-
const fetch = mockFetch(true, {}, 200);
|
|
112
|
-
domain.createStorefrontChangeRequest({prompt: 'update hero'});
|
|
113
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
114
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
115
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
116
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/requests/');
|
|
117
|
-
expect(body).toEqual({prompt: 'update hero'});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('can get storefront v2 change request by id', () => {
|
|
121
|
-
const merchi = new Merchi();
|
|
122
|
-
const domain = new merchi.Domain();
|
|
123
|
-
const fetch = mockFetch(true, {}, 200);
|
|
124
|
-
domain.getStorefrontChangeRequest(9);
|
|
125
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
126
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
127
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('can run storefront v2 change request', () => {
|
|
131
|
-
const merchi = new Merchi();
|
|
132
|
-
const domain = new merchi.Domain();
|
|
133
|
-
const fetch = mockFetch(true, {}, 200);
|
|
134
|
-
domain.runStorefrontChangeRequest(9, {
|
|
135
|
-
status: 'running',
|
|
136
|
-
pullRequestNumber: 42,
|
|
137
|
-
checksSummary: {
|
|
138
|
-
overall: 'passing',
|
|
139
|
-
counts: {total: 4, passed: 4, failed: 0, pending: 0, neutral: 0},
|
|
140
|
-
},
|
|
141
|
-
checksUpdatedAt: '2026-05-15T00:00:00Z',
|
|
142
|
-
});
|
|
143
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
144
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
145
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
146
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/run/');
|
|
147
|
-
expect(body).toEqual({
|
|
148
|
-
status: 'running',
|
|
149
|
-
pullRequestNumber: 42,
|
|
150
|
-
checksSummary: {
|
|
151
|
-
overall: 'passing',
|
|
152
|
-
counts: {total: 4, passed: 4, failed: 0, pending: 0, neutral: 0},
|
|
153
|
-
},
|
|
154
|
-
checksUpdatedAt: '2026-05-15T00:00:00Z',
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('can approve storefront v2 change request', () => {
|
|
159
|
-
const merchi = new Merchi();
|
|
160
|
-
const domain = new merchi.Domain();
|
|
161
|
-
const fetch = mockFetch(true, {}, 200);
|
|
162
|
-
domain.approveStorefrontChangeRequest(9, {comment: 'looks good'});
|
|
163
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
164
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
165
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
166
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/approve/');
|
|
167
|
-
expect(body).toEqual({comment: 'looks good'});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test('can reject storefront v2 change request', () => {
|
|
171
|
-
const merchi = new Merchi();
|
|
172
|
-
const domain = new merchi.Domain();
|
|
173
|
-
const fetch = mockFetch(true, {}, 200);
|
|
174
|
-
domain.rejectStorefrontChangeRequest(9, {reason: 'needs edits'});
|
|
175
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
176
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
177
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
178
|
-
expect(fetchUrl).toContain('/storefront_change_requests/9/reject/');
|
|
179
|
-
expect(body).toEqual({reason: 'needs edits'});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('can get storefront v2 deployments', () => {
|
|
183
|
-
const merchi = new Merchi();
|
|
184
|
-
const domain = new merchi.Domain();
|
|
185
|
-
domain.id = 42;
|
|
186
|
-
const fetch = mockFetch(true, {}, 200);
|
|
187
|
-
domain.getStorefrontV2Deployments();
|
|
188
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
189
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
190
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('can get storefront v2 deployment logs', () => {
|
|
194
|
-
const merchi = new Merchi();
|
|
195
|
-
const domain = new merchi.Domain();
|
|
196
|
-
domain.id = 42;
|
|
197
|
-
const fetch = mockFetch(true, {}, 200);
|
|
198
|
-
domain.getStorefrontV2DeploymentLogs('dep_123');
|
|
199
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
200
|
-
expect(fetch.mock.calls[0][1].method).toBe('GET');
|
|
201
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/deployments/dep_123/logs/');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('can rollback storefront v2 deployment', () => {
|
|
205
|
-
const merchi = new Merchi();
|
|
206
|
-
const domain = new merchi.Domain();
|
|
207
|
-
domain.id = 42;
|
|
208
|
-
const fetch = mockFetch(true, {}, 200);
|
|
209
|
-
domain.rollbackStorefrontV2({deploymentId: 'dep_123'});
|
|
210
|
-
const fetchUrl = fetch.mock.calls[0][0];
|
|
211
|
-
const body = JSON.parse(fetch.mock.calls[0][1].body as string);
|
|
212
|
-
expect(fetch.mock.calls[0][1].method).toBe('POST');
|
|
213
|
-
expect(fetchUrl).toContain('/domains/42/storefront_v2/rollback/');
|
|
214
|
-
expect(body).toEqual({deploymentId: 'dep_123'});
|
|
215
|
-
});
|
package/src/entities/domain.ts
CHANGED
|
@@ -21,43 +21,6 @@ import { DomainChatSettings } from './domain_chat_settings.js';
|
|
|
21
21
|
import { Theme } from './theme.js';
|
|
22
22
|
import { DomainType } from '../constants/domain_types.js';
|
|
23
23
|
import { ShipmentMethod } from './shipment_method.js';
|
|
24
|
-
import { RequestOptions } from '../request.js';
|
|
25
|
-
|
|
26
|
-
export type StorefrontChangeRequestStatus =
|
|
27
|
-
| 'created'
|
|
28
|
-
| 'running'
|
|
29
|
-
| 'preview_ready'
|
|
30
|
-
| 'approved'
|
|
31
|
-
| 'rejected';
|
|
32
|
-
|
|
33
|
-
export interface StorefrontChecksSummary {
|
|
34
|
-
overall?: 'passing' | 'failing' | 'pending' | 'unknown';
|
|
35
|
-
statusState?: string;
|
|
36
|
-
counts?: {
|
|
37
|
-
total?: number;
|
|
38
|
-
passed?: number;
|
|
39
|
-
failed?: number;
|
|
40
|
-
pending?: number;
|
|
41
|
-
neutral?: number;
|
|
42
|
-
};
|
|
43
|
-
updatedAt?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface StorefrontV2ChangeRequest {
|
|
47
|
-
id: number;
|
|
48
|
-
domainId: number;
|
|
49
|
-
storefrontV2Id: number;
|
|
50
|
-
status: StorefrontChangeRequestStatus;
|
|
51
|
-
prompt: string;
|
|
52
|
-
branchName?: string | null;
|
|
53
|
-
commitSha?: string | null;
|
|
54
|
-
pullRequestNumber?: number | null;
|
|
55
|
-
previewUrl?: string | null;
|
|
56
|
-
summary?: string | null;
|
|
57
|
-
checksSummary?: StorefrontChecksSummary | null;
|
|
58
|
-
checksUpdatedAt?: string | null;
|
|
59
|
-
errorDetails?: string | null;
|
|
60
|
-
}
|
|
61
24
|
|
|
62
25
|
export class Domain extends Entity {
|
|
63
26
|
protected static resourceName = 'domains';
|
|
@@ -184,6 +147,15 @@ export class Domain extends Entity {
|
|
|
184
147
|
@Domain.property({type: String})
|
|
185
148
|
public googleMerchantId?: string;
|
|
186
149
|
|
|
150
|
+
@Domain.property({type: Boolean})
|
|
151
|
+
public googleProductReviewsFeedEnabled?: boolean;
|
|
152
|
+
|
|
153
|
+
@Domain.property({type: String})
|
|
154
|
+
public googleProductReviewsFeedToken?: string | null;
|
|
155
|
+
|
|
156
|
+
@Domain.property({type: String})
|
|
157
|
+
public googleProductReviewsFeedUrl?: string | null;
|
|
158
|
+
|
|
187
159
|
@Domain.property({type: String})
|
|
188
160
|
public socialBitchute?: string | null;
|
|
189
161
|
|
|
@@ -319,117 +291,4 @@ export class Domain extends Entity {
|
|
|
319
291
|
}
|
|
320
292
|
return this.activeTheme!;
|
|
321
293
|
};
|
|
322
|
-
|
|
323
|
-
private getDomainId = () => {
|
|
324
|
-
if (this.id === undefined || this.id === null) {
|
|
325
|
-
throw new Error('id is undefined, did you forget to set it?');
|
|
326
|
-
}
|
|
327
|
-
return this.id;
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
private storefrontV2DomainResource = (suffix = '') => {
|
|
331
|
-
return `/domains/${this.getDomainId()}/storefront_v2/${suffix}`;
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
private storefrontV2Request = (
|
|
335
|
-
resource: string,
|
|
336
|
-
method: 'GET' | 'POST',
|
|
337
|
-
payload?: Record<string, any>
|
|
338
|
-
) => {
|
|
339
|
-
const fetchOptions: RequestOptions = {
|
|
340
|
-
method: method,
|
|
341
|
-
query: [['skip_rights', 'y']]
|
|
342
|
-
};
|
|
343
|
-
if (payload !== undefined) {
|
|
344
|
-
fetchOptions.body = JSON.stringify(payload);
|
|
345
|
-
fetchOptions.headers = {'Content-Type': 'application/json'};
|
|
346
|
-
}
|
|
347
|
-
return this.merchi.authenticatedFetch(resource, fetchOptions);
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
public getStorefrontV2 = () => {
|
|
351
|
-
return this.storefrontV2Request(this.storefrontV2DomainResource(), 'GET');
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
public provisionStorefrontV2 = (payload?: Record<string, any>) => {
|
|
355
|
-
return this.storefrontV2Request(
|
|
356
|
-
this.storefrontV2DomainResource('provision/'),
|
|
357
|
-
'POST',
|
|
358
|
-
payload
|
|
359
|
-
);
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
public createStorefrontChangeRequest = (
|
|
363
|
-
payload?: Record<string, any>
|
|
364
|
-
): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
|
|
365
|
-
return this.storefrontV2Request(
|
|
366
|
-
this.storefrontV2DomainResource('requests/'),
|
|
367
|
-
'POST',
|
|
368
|
-
payload
|
|
369
|
-
) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
public getStorefrontChangeRequest = (
|
|
373
|
-
requestId: number | string
|
|
374
|
-
): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
|
|
375
|
-
return this.storefrontV2Request(
|
|
376
|
-
`/storefront_change_requests/${String(requestId)}/`,
|
|
377
|
-
'GET'
|
|
378
|
-
) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
public runStorefrontChangeRequest = (
|
|
382
|
-
requestId: number | string,
|
|
383
|
-
payload?: Record<string, any>
|
|
384
|
-
): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
|
|
385
|
-
return this.storefrontV2Request(
|
|
386
|
-
`/storefront_change_requests/${String(requestId)}/run/`,
|
|
387
|
-
'POST',
|
|
388
|
-
payload
|
|
389
|
-
) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
public approveStorefrontChangeRequest = (
|
|
393
|
-
requestId: number | string,
|
|
394
|
-
payload?: Record<string, any>
|
|
395
|
-
): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
|
|
396
|
-
return this.storefrontV2Request(
|
|
397
|
-
`/storefront_change_requests/${String(requestId)}/approve/`,
|
|
398
|
-
'POST',
|
|
399
|
-
payload
|
|
400
|
-
) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
public rejectStorefrontChangeRequest = (
|
|
404
|
-
requestId: number | string,
|
|
405
|
-
payload?: Record<string, any>
|
|
406
|
-
): Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}> => {
|
|
407
|
-
return this.storefrontV2Request(
|
|
408
|
-
`/storefront_change_requests/${String(requestId)}/reject/`,
|
|
409
|
-
'POST',
|
|
410
|
-
payload
|
|
411
|
-
) as Promise<{storefrontChangeRequest: StorefrontV2ChangeRequest}>;
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
public getStorefrontV2Deployments = () => {
|
|
415
|
-
return this.storefrontV2Request(
|
|
416
|
-
this.storefrontV2DomainResource('deployments/'),
|
|
417
|
-
'GET'
|
|
418
|
-
);
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
public getStorefrontV2DeploymentLogs = (deploymentId: number | string) => {
|
|
422
|
-
return this.storefrontV2Request(
|
|
423
|
-
this.storefrontV2DomainResource(`deployments/${String(deploymentId)}/logs/`),
|
|
424
|
-
'GET'
|
|
425
|
-
);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
public rollbackStorefrontV2 = (payload?: Record<string, any>) => {
|
|
429
|
-
return this.storefrontV2Request(
|
|
430
|
-
this.storefrontV2DomainResource('rollback/'),
|
|
431
|
-
'POST',
|
|
432
|
-
payload
|
|
433
|
-
);
|
|
434
|
-
};
|
|
435
294
|
}
|
|
@@ -22,3 +22,37 @@ test('can list notifications with notificationSender filter', () => {
|
|
|
22
22
|
expect(query).toContainEqual(['notification_sender', '123']);
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
test('list forwards skipCount=true as skip_count query param', () => {
|
|
27
|
+
const merchi = new Merchi();
|
|
28
|
+
const fetch = mockFetch(true, {
|
|
29
|
+
'notifications': [],
|
|
30
|
+
// backend returns ``available: null`` when skip_count was honoured
|
|
31
|
+
'available': null,
|
|
32
|
+
'count': 0
|
|
33
|
+
}, 200);
|
|
34
|
+
const options = { skipCount: true };
|
|
35
|
+
return merchi.Notification.list(options).then((response) => {
|
|
36
|
+
const query = fetch.mock.calls[0][1]['query'];
|
|
37
|
+
expect(query).toContainEqual(['skip_count', 'true']);
|
|
38
|
+
// ``available`` must round-trip as ``null``; the typed metadata
|
|
39
|
+
// is now ``number | null``, so consumers can detect the absence.
|
|
40
|
+
expect(response.metadata.available).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('list omits skip_count when skipCount is not set', () => {
|
|
45
|
+
const merchi = new Merchi();
|
|
46
|
+
const fetch = mockFetch(true, {
|
|
47
|
+
'notifications': [],
|
|
48
|
+
'available': 0,
|
|
49
|
+
'count': 0
|
|
50
|
+
}, 200);
|
|
51
|
+
return merchi.Notification.list({}).then(() => {
|
|
52
|
+
const query = fetch.mock.calls[0][1]['query'];
|
|
53
|
+
const skipCountEntries = query.filter(
|
|
54
|
+
(entry: [string, string]) => entry[0] === 'skip_count'
|
|
55
|
+
);
|
|
56
|
+
expect(skipCountEntries).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Entity } from '../entity.js';
|
|
2
|
+
import type { RequestOptions } from '../request.js';
|
|
3
|
+
import type { Domain } from './domain.js';
|
|
4
|
+
import type { Job } from './job.js';
|
|
5
|
+
import type { Product } from './product.js';
|
|
6
|
+
|
|
7
|
+
/** Create payload; the API expects ``jobId`` — pass ``job`` with at least ``id``. */
|
|
8
|
+
export interface ProductReviewCreateInput {
|
|
9
|
+
job: { id: number };
|
|
10
|
+
rating: number;
|
|
11
|
+
title?: string | null;
|
|
12
|
+
content?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ProductReviewPatchInput {
|
|
16
|
+
status?: string;
|
|
17
|
+
rejectionReason?: string | null;
|
|
18
|
+
rating?: number;
|
|
19
|
+
title?: string | null;
|
|
20
|
+
content?: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Post-purchase product review. List/create are nested under ``/products/:id/reviews/``;
|
|
25
|
+
* updates use ``/product-reviews/:id/``. Do not use :meth:`Entity.create` / :meth:`Entity.list`
|
|
26
|
+
* for this resource.
|
|
27
|
+
*
|
|
28
|
+
* API responses use ``jobId``, ``productId``, and ``domainId``; this entity maps those onto
|
|
29
|
+
* ``job``, ``product``, and ``domain`` (minimal ``{ id }`` objects unless the API nests more).
|
|
30
|
+
*/
|
|
31
|
+
export class ProductReview extends Entity {
|
|
32
|
+
protected static resourceName = 'product-reviews';
|
|
33
|
+
protected static singularName = 'productReview';
|
|
34
|
+
protected static pluralName = 'productReviews';
|
|
35
|
+
|
|
36
|
+
@ProductReview.property()
|
|
37
|
+
public id?: number;
|
|
38
|
+
|
|
39
|
+
@ProductReview.property({ type: 'Product' })
|
|
40
|
+
public product?: Product;
|
|
41
|
+
|
|
42
|
+
@ProductReview.property({ type: 'Domain' })
|
|
43
|
+
public domain?: Domain;
|
|
44
|
+
|
|
45
|
+
@ProductReview.property({ type: Number })
|
|
46
|
+
public authorUserId?: number;
|
|
47
|
+
|
|
48
|
+
@ProductReview.property({ type: String })
|
|
49
|
+
public authorName?: string | null;
|
|
50
|
+
|
|
51
|
+
@ProductReview.property({ type: 'Job' })
|
|
52
|
+
public job?: Job;
|
|
53
|
+
|
|
54
|
+
@ProductReview.property({ type: Number })
|
|
55
|
+
public rating?: number;
|
|
56
|
+
|
|
57
|
+
@ProductReview.property({ type: String })
|
|
58
|
+
public title?: string | null;
|
|
59
|
+
|
|
60
|
+
@ProductReview.property({ type: String })
|
|
61
|
+
public content?: string | null;
|
|
62
|
+
|
|
63
|
+
@ProductReview.property({ type: String })
|
|
64
|
+
public status?: string;
|
|
65
|
+
|
|
66
|
+
@ProductReview.property({ type: Number })
|
|
67
|
+
public purchaseInvoiceId?: number | null;
|
|
68
|
+
|
|
69
|
+
/** ISO 8601 string from the API. */
|
|
70
|
+
@ProductReview.property({ type: String })
|
|
71
|
+
public submittedAt?: string | null;
|
|
72
|
+
|
|
73
|
+
/** ISO 8601 string from the API. */
|
|
74
|
+
@ProductReview.property({ type: String })
|
|
75
|
+
public updatedAt?: string | null;
|
|
76
|
+
|
|
77
|
+
@ProductReview.property({ type: String })
|
|
78
|
+
public rejectionReason?: string | null;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Map flat API fields (``jobId``, ``productId``, ``domainId``) to nested
|
|
82
|
+
* ``job`` / ``product`` / ``domain`` objects before :meth:`Entity.fromJson`.
|
|
83
|
+
*/
|
|
84
|
+
public static normalizeApiJson(json: any): any {
|
|
85
|
+
if (json === null || json === undefined || typeof json !== 'object') {
|
|
86
|
+
return json;
|
|
87
|
+
}
|
|
88
|
+
const row: any = { ...json };
|
|
89
|
+
if (row.jobId !== undefined && row.job === undefined) {
|
|
90
|
+
if (row.jobId != null) {
|
|
91
|
+
row.job = { id: row.jobId };
|
|
92
|
+
}
|
|
93
|
+
delete row.jobId;
|
|
94
|
+
}
|
|
95
|
+
if (row.productId !== undefined && row.product === undefined) {
|
|
96
|
+
if (row.productId != null) {
|
|
97
|
+
row.product = { id: row.productId };
|
|
98
|
+
}
|
|
99
|
+
delete row.productId;
|
|
100
|
+
}
|
|
101
|
+
if (row.domainId !== undefined && row.domain === undefined) {
|
|
102
|
+
if (row.domainId != null) {
|
|
103
|
+
row.domain = { id: row.domainId };
|
|
104
|
+
}
|
|
105
|
+
delete row.domainId;
|
|
106
|
+
}
|
|
107
|
+
return row;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Domain managers/admins: list reviews for a product.
|
|
112
|
+
*/
|
|
113
|
+
public static listForProduct(
|
|
114
|
+
this: typeof ProductReview,
|
|
115
|
+
productId: number,
|
|
116
|
+
options?: { withRights?: boolean }
|
|
117
|
+
): Promise<ProductReview[]> {
|
|
118
|
+
const Constructor = this;
|
|
119
|
+
const resource = `/products/${productId}/reviews/`;
|
|
120
|
+
const fetchOptions: RequestOptions = { method: 'GET' };
|
|
121
|
+
fetchOptions.query = [];
|
|
122
|
+
if (!(options && options.withRights)) {
|
|
123
|
+
fetchOptions.query.push(['skip_rights', 'y']);
|
|
124
|
+
}
|
|
125
|
+
return Constructor.merchi.authenticatedFetch(resource, fetchOptions).then(
|
|
126
|
+
(data: any) => {
|
|
127
|
+
const raw = data.productReviews ?? [];
|
|
128
|
+
return raw.map((row: any) => {
|
|
129
|
+
const e: ProductReview = new Constructor() as ProductReview;
|
|
130
|
+
e.fromJson(ProductReview.normalizeApiJson(row));
|
|
131
|
+
return e;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Buyer: create a review for the catalog ``productId`` (master product).
|
|
139
|
+
* ``body.job.id`` must be a completed job whose line item resolves to this product.
|
|
140
|
+
*/
|
|
141
|
+
public static createForProduct(
|
|
142
|
+
this: typeof ProductReview,
|
|
143
|
+
productId: number,
|
|
144
|
+
body: ProductReviewCreateInput,
|
|
145
|
+
options?: { withRights?: boolean }
|
|
146
|
+
): Promise<ProductReview> {
|
|
147
|
+
const Constructor = this;
|
|
148
|
+
const resource = `/products/${productId}/reviews/`;
|
|
149
|
+
const wire: Record<string, unknown> = {
|
|
150
|
+
jobId: body.job.id,
|
|
151
|
+
rating: body.rating,
|
|
152
|
+
};
|
|
153
|
+
if (body.title !== undefined) {
|
|
154
|
+
wire.title = body.title;
|
|
155
|
+
}
|
|
156
|
+
if (body.content !== undefined) {
|
|
157
|
+
wire.content = body.content;
|
|
158
|
+
}
|
|
159
|
+
const fetchOptions: RequestOptions = {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: { 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify(wire),
|
|
163
|
+
};
|
|
164
|
+
fetchOptions.query = [];
|
|
165
|
+
if (!(options && options.withRights)) {
|
|
166
|
+
fetchOptions.query.push(['skip_rights', 'y']);
|
|
167
|
+
}
|
|
168
|
+
return Constructor.merchi.authenticatedFetch(resource, fetchOptions).then(
|
|
169
|
+
(data: any) => {
|
|
170
|
+
const e: ProductReview = new Constructor() as ProductReview;
|
|
171
|
+
e.fromJson(ProductReview.normalizeApiJson(data.productReview));
|
|
172
|
+
return e;
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Partial update (moderation or author edit). Uses ``multipart/form-data`` fields
|
|
179
|
+
* matching the Merchi API.
|
|
180
|
+
*/
|
|
181
|
+
public patch(
|
|
182
|
+
payload: ProductReviewPatchInput,
|
|
183
|
+
options?: { withRights?: boolean }
|
|
184
|
+
): Promise<this> {
|
|
185
|
+
const id = this.id;
|
|
186
|
+
if (id === undefined) {
|
|
187
|
+
throw new Error('ProductReview.id is required to patch');
|
|
188
|
+
}
|
|
189
|
+
const resource = `/product-reviews/${String(id)}/`;
|
|
190
|
+
const form = new FormData();
|
|
191
|
+
if (payload.status !== undefined) {
|
|
192
|
+
form.set('status', payload.status);
|
|
193
|
+
}
|
|
194
|
+
if (payload.rejectionReason !== undefined) {
|
|
195
|
+
form.set('rejectionReason', payload.rejectionReason ?? '');
|
|
196
|
+
}
|
|
197
|
+
if (payload.rating !== undefined) {
|
|
198
|
+
form.set('rating', String(payload.rating));
|
|
199
|
+
}
|
|
200
|
+
if (payload.title !== undefined) {
|
|
201
|
+
form.set('title', payload.title ?? '');
|
|
202
|
+
}
|
|
203
|
+
if (payload.content !== undefined) {
|
|
204
|
+
form.set('content', payload.content ?? '');
|
|
205
|
+
}
|
|
206
|
+
const fetchOptions: RequestOptions = { method: 'PATCH', body: form };
|
|
207
|
+
fetchOptions.query = [];
|
|
208
|
+
if (!(options && options.withRights)) {
|
|
209
|
+
fetchOptions.query.push(['skip_rights', 'y']);
|
|
210
|
+
}
|
|
211
|
+
const singularName = (this.constructor as typeof ProductReview)
|
|
212
|
+
.singularName;
|
|
213
|
+
return this.merchi.authenticatedFetch(resource, fetchOptions).then(
|
|
214
|
+
(data: any) => {
|
|
215
|
+
this.fromJson(ProductReview.normalizeApiJson(data[singularName]));
|
|
216
|
+
this.cleanDirty();
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/entity.ts
CHANGED
|
@@ -159,11 +159,22 @@ interface ListOptions {
|
|
|
159
159
|
teamOnly?: boolean;
|
|
160
160
|
turnaroundTimeDays?: number;
|
|
161
161
|
withRights?: boolean;
|
|
162
|
+
// Opt out of the unbounded ``SELECT count(*)`` the API runs to
|
|
163
|
+
// populate ``available``. When ``skipCount`` is true the response's
|
|
164
|
+
// ``available`` is ``null``. Use this whenever the caller only
|
|
165
|
+
// renders the visible page of rows (search modals, autocomplete,
|
|
166
|
+
// navbar dropdowns, infinite scroll, ...) and never reads
|
|
167
|
+
// ``metadata.available``. Paginated tables that show "Showing X
|
|
168
|
+
// of Y" or compute a page count must NOT set this.
|
|
169
|
+
skipCount?: boolean;
|
|
162
170
|
}
|
|
163
171
|
|
|
164
172
|
export interface ListMetadata {
|
|
165
173
|
canCreate?: boolean;
|
|
166
|
-
|
|
174
|
+
// ``null`` when the caller passed ``skipCount: true``: the API
|
|
175
|
+
// intentionally skipped the ``COUNT(*)`` query. JSON consumers
|
|
176
|
+
// can detect the absence rather than mistaking it for zero.
|
|
177
|
+
available: number | null;
|
|
167
178
|
count: number;
|
|
168
179
|
limit: number;
|
|
169
180
|
offset: number;
|
|
@@ -417,6 +428,16 @@ export class Entity {
|
|
|
417
428
|
if (options.limit !== undefined) {
|
|
418
429
|
fetchOptions.query.push(['limit', options.limit.toString()]);
|
|
419
430
|
}
|
|
431
|
+
if (options.skipCount !== undefined) {
|
|
432
|
+
// Snake-case is the canonical form on the server; the API
|
|
433
|
+
// also accepts ``skipCount`` via its camelCase alias, but
|
|
434
|
+
// matching the wire shape of the other ``*_only`` /
|
|
435
|
+
// ``*_filter`` flags keeps logs and traffic captures
|
|
436
|
+
// consistent.
|
|
437
|
+
fetchOptions.query.push(
|
|
438
|
+
['skip_count', options.skipCount.toString()]
|
|
439
|
+
);
|
|
440
|
+
}
|
|
420
441
|
if (options.q !== undefined) {
|
|
421
442
|
fetchOptions.query.push(['q', options.q]);
|
|
422
443
|
}
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,11 @@ import { Payment } from './entities/payment.js';
|
|
|
51
51
|
import { PaymentDevice } from './entities/payment_device.js';
|
|
52
52
|
import { PhoneNumber } from './entities/phone_number.js';
|
|
53
53
|
import { Product } from './entities/product.js';
|
|
54
|
+
import { ProductReview } from './entities/product_review.js';
|
|
55
|
+
export type {
|
|
56
|
+
ProductReviewCreateInput,
|
|
57
|
+
ProductReviewPatchInput,
|
|
58
|
+
} from './entities/product_review.js';
|
|
54
59
|
import { ProductionComment } from './entities/production_comment.js';
|
|
55
60
|
import { Quote } from './entities/quote.js';
|
|
56
61
|
import { QuoteItem } from './entities/quote_item.js';
|
|
@@ -142,6 +147,7 @@ export {
|
|
|
142
147
|
PaymentDevice,
|
|
143
148
|
PhoneNumber,
|
|
144
149
|
Product,
|
|
150
|
+
ProductReview,
|
|
145
151
|
ProductionComment,
|
|
146
152
|
Quote,
|
|
147
153
|
QuoteItem,
|
package/src/merchi.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { VariationField } from './entities/variation_field.js';
|
|
|
14
14
|
import { VariationOption } from './entities/variation_option.js';
|
|
15
15
|
import { ProductionComment } from './entities/production_comment.js';
|
|
16
16
|
import { Product } from './entities/product.js';
|
|
17
|
+
import { ProductReview } from './entities/product_review.js';
|
|
17
18
|
import { InternalTag } from './entities/internal_tag.js';
|
|
18
19
|
import { Inventory } from './entities/inventory.js';
|
|
19
20
|
import { InventoryGroup } from './entities/inventory_group.js';
|
|
@@ -155,6 +156,7 @@ export class Merchi {
|
|
|
155
156
|
public Job: typeof Job;
|
|
156
157
|
public DomainInvitation: typeof DomainInvitation;
|
|
157
158
|
public Product: typeof Product;
|
|
159
|
+
public ProductReview: typeof ProductReview;
|
|
158
160
|
public DomainTag: typeof DomainTag;
|
|
159
161
|
public EmailAddress: typeof EmailAddress;
|
|
160
162
|
public PhoneNumber: typeof PhoneNumber;
|
|
@@ -330,6 +332,9 @@ export class Merchi {
|
|
|
330
332
|
this.CountryTax = this.setupClass(CountryTax) as typeof CountryTax;
|
|
331
333
|
this.ShortUrl = this.setupClass(ShortUrl) as typeof ShortUrl;
|
|
332
334
|
this.Product = this.setupClass(Product) as typeof Product;
|
|
335
|
+
this.ProductReview = this.setupClass(
|
|
336
|
+
ProductReview
|
|
337
|
+
) as typeof ProductReview;
|
|
333
338
|
this.SystemRole = this.setupClass(SystemRole) as typeof SystemRole;
|
|
334
339
|
this.CartItem = this.setupClass(CartItem) as typeof CartItem;
|
|
335
340
|
this.UserCompany = this.setupClass(UserCompany) as typeof UserCompany;
|