@unchainedshop/api 5.0.0-alpha.4 → 5.0.0-alpha.5
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/lib/acl.js +4 -0
- package/lib/adminUiPlugins.d.ts +12 -1
- package/lib/adminUiPlugins.js +188 -26
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +2 -0
- package/lib/events.d.ts +1 -0
- package/lib/events.js +1 -0
- package/lib/express/index.js +44 -17
- package/lib/fastify/index.d.ts +2 -0
- package/lib/fastify/index.js +65 -24
- package/lib/mcp/tools/system/handlers/allocateWork.js +2 -2
- package/lib/mcp/tools/users/index.js +1 -1
- package/lib/middleware/createAuthMiddleware.js +2 -0
- package/lib/resolvers/mutations/accounts/loginWithPassword.js +16 -2
- package/lib/resolvers/mutations/accounts/logoutAllSessions.d.ts +3 -1
- package/lib/resolvers/mutations/accounts/logoutAllSessions.js +10 -4
- package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.js +18 -0
- package/lib/resolvers/mutations/bulk/bulkOperation.d.ts +12 -0
- package/lib/resolvers/mutations/bulk/bulkOperation.js +14 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveFilters.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveFilters.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveProducts.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveProducts.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveUsers.d.ts +9 -0
- package/lib/resolvers/mutations/bulk/bulkRemoveUsers.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.js +9 -0
- package/lib/resolvers/mutations/bulk/bulkSetFilterActive.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetFilterActive.js +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetProductStatus.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetProductStatus.js +17 -0
- package/lib/resolvers/mutations/bulk/bulkSetUserRoles.d.ts +10 -0
- package/lib/resolvers/mutations/bulk/bulkSetUserRoles.js +14 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.js +10 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.js +7 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.d.ts +11 -0
- package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.js +7 -0
- package/lib/resolvers/mutations/index.d.ts +12 -0
- package/lib/resolvers/mutations/index.js +25 -1
- package/lib/resolvers/mutations/users/removeUser.js +9 -2
- package/lib/resolvers/mutations/users/setRoles.js +9 -2
- package/lib/resolvers/mutations/worker/allocateWork.d.ts +1 -1
- package/lib/resolvers/mutations/worker/allocateWork.js +4 -3
- package/lib/resolvers/queries/index.d.ts +1 -0
- package/lib/resolvers/queries/index.js +2 -0
- package/lib/resolvers/queries/search/globalSearch.d.ts +36 -0
- package/lib/resolvers/queries/search/globalSearch.js +200 -0
- package/lib/resolvers/type/global-search-result-types.d.ts +3 -0
- package/lib/resolvers/type/global-search-result-types.js +15 -0
- package/lib/resolvers/type/index.d.ts +3 -0
- package/lib/resolvers/type/index.js +2 -0
- package/lib/resolvers/type/user-types.d.ts +1 -0
- package/lib/resolvers/type/user-types.js +35 -4
- package/lib/roles/all.js +12 -0
- package/lib/roles/index.js +12 -0
- package/lib/roles/loggedIn.js +1 -1
- package/lib/schema/inputTypes.js +5 -0
- package/lib/schema/mutation.js +74 -1
- package/lib/schema/query.js +15 -0
- package/lib/schema/types/common.js +7 -0
- package/lib/schema/types/search.js +36 -0
- package/lib/schema/types/user.js +4 -0
- package/package.json +25 -25
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { log } from '@unchainedshop/logger';
|
|
2
|
+
import { checkAction } from "../../../acl.js";
|
|
3
|
+
import { actions } from "../../../roles/index.js";
|
|
4
|
+
const ALL_TYPES = [
|
|
5
|
+
'PRODUCT',
|
|
6
|
+
'USER',
|
|
7
|
+
'ORDER',
|
|
8
|
+
'ASSORTMENT',
|
|
9
|
+
'FILTER',
|
|
10
|
+
'ENROLLMENT',
|
|
11
|
+
'QUOTATION',
|
|
12
|
+
'WORK',
|
|
13
|
+
];
|
|
14
|
+
const DEFAULT_LIMIT = 5;
|
|
15
|
+
const MAX_LIMIT = 50;
|
|
16
|
+
const typeActionMap = {
|
|
17
|
+
PRODUCT: actions.viewProducts,
|
|
18
|
+
USER: actions.viewUsers,
|
|
19
|
+
ORDER: actions.viewOrders,
|
|
20
|
+
ASSORTMENT: actions.viewAssortments,
|
|
21
|
+
FILTER: actions.viewFilters,
|
|
22
|
+
ENROLLMENT: actions.viewEnrollments,
|
|
23
|
+
QUOTATION: actions.viewQuotations,
|
|
24
|
+
WORK: actions.viewWorkQueue,
|
|
25
|
+
};
|
|
26
|
+
function getLimitForType(type, defaultLimit, typeLimits) {
|
|
27
|
+
const requestedLimit = typeLimits?.find((typeLimit) => typeLimit.type === type)?.limit ?? defaultLimit;
|
|
28
|
+
return Math.min(MAX_LIMIT, Math.max(1, requestedLimit));
|
|
29
|
+
}
|
|
30
|
+
function getSearchParams(type, queryString, limit, options) {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'PRODUCT':
|
|
33
|
+
return {
|
|
34
|
+
queryString,
|
|
35
|
+
limit,
|
|
36
|
+
offset: 0,
|
|
37
|
+
includeDrafts: options.includeDraftProducts,
|
|
38
|
+
};
|
|
39
|
+
case 'USER':
|
|
40
|
+
return {
|
|
41
|
+
queryString,
|
|
42
|
+
limit,
|
|
43
|
+
offset: 0,
|
|
44
|
+
includeGuests: options.includeGuestUsers,
|
|
45
|
+
};
|
|
46
|
+
case 'ORDER':
|
|
47
|
+
return {
|
|
48
|
+
queryString,
|
|
49
|
+
limit,
|
|
50
|
+
offset: 0,
|
|
51
|
+
includeCarts: options.includeCarts,
|
|
52
|
+
};
|
|
53
|
+
case 'ASSORTMENT':
|
|
54
|
+
return {
|
|
55
|
+
queryString,
|
|
56
|
+
limit,
|
|
57
|
+
offset: 0,
|
|
58
|
+
includeInactive: options.includeInactiveAssortments,
|
|
59
|
+
};
|
|
60
|
+
case 'FILTER':
|
|
61
|
+
return {
|
|
62
|
+
queryString,
|
|
63
|
+
limit,
|
|
64
|
+
offset: 0,
|
|
65
|
+
includeInactive: options.includeInactiveFilters,
|
|
66
|
+
};
|
|
67
|
+
case 'ENROLLMENT':
|
|
68
|
+
case 'QUOTATION':
|
|
69
|
+
return { queryString, limit, offset: 0 };
|
|
70
|
+
case 'WORK':
|
|
71
|
+
return { queryString, limit, skip: 0 };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const typeNameMap = {
|
|
75
|
+
PRODUCT: 'Product',
|
|
76
|
+
USER: 'User',
|
|
77
|
+
ORDER: 'Order',
|
|
78
|
+
ASSORTMENT: 'Assortment',
|
|
79
|
+
FILTER: 'Filter',
|
|
80
|
+
ENROLLMENT: 'Enrollment',
|
|
81
|
+
QUOTATION: 'Quotation',
|
|
82
|
+
WORK: 'Work',
|
|
83
|
+
};
|
|
84
|
+
async function searchType(type, queryString, limit, modules, options) {
|
|
85
|
+
let results;
|
|
86
|
+
switch (type) {
|
|
87
|
+
case 'PRODUCT':
|
|
88
|
+
results = await modules.products.findProducts({
|
|
89
|
+
queryString,
|
|
90
|
+
limit,
|
|
91
|
+
offset: 0,
|
|
92
|
+
includeDrafts: options.includeDraftProducts,
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
case 'USER':
|
|
96
|
+
results = await modules.users.findUsers({
|
|
97
|
+
queryString,
|
|
98
|
+
limit,
|
|
99
|
+
offset: 0,
|
|
100
|
+
includeGuests: options.includeGuestUsers,
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
case 'ORDER':
|
|
104
|
+
results = await modules.orders.findOrders({
|
|
105
|
+
queryString,
|
|
106
|
+
limit,
|
|
107
|
+
offset: 0,
|
|
108
|
+
includeCarts: options.includeCarts,
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
case 'ASSORTMENT':
|
|
112
|
+
results = await modules.assortments.findAssortments({
|
|
113
|
+
queryString,
|
|
114
|
+
limit,
|
|
115
|
+
offset: 0,
|
|
116
|
+
includeInactive: options.includeInactiveAssortments,
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
case 'FILTER':
|
|
120
|
+
results = await modules.filters.findFilters({
|
|
121
|
+
queryString,
|
|
122
|
+
limit,
|
|
123
|
+
offset: 0,
|
|
124
|
+
includeInactive: options.includeInactiveFilters,
|
|
125
|
+
});
|
|
126
|
+
break;
|
|
127
|
+
case 'ENROLLMENT':
|
|
128
|
+
results = await modules.enrollments.findEnrollments({ queryString, limit, offset: 0 });
|
|
129
|
+
break;
|
|
130
|
+
case 'QUOTATION':
|
|
131
|
+
results = await modules.quotations.findQuotations({ queryString, limit, offset: 0 });
|
|
132
|
+
break;
|
|
133
|
+
case 'WORK':
|
|
134
|
+
results = await modules.worker.findWorkQueue({ queryString, limit, skip: 0 });
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const __typename = typeNameMap[type];
|
|
140
|
+
return results.map((r) => ({ ...r, __typename }));
|
|
141
|
+
}
|
|
142
|
+
async function countType(type, queryString, modules, options) {
|
|
143
|
+
switch (type) {
|
|
144
|
+
case 'PRODUCT':
|
|
145
|
+
return modules.products.count({ queryString, includeDrafts: options.includeDraftProducts });
|
|
146
|
+
case 'USER':
|
|
147
|
+
return modules.users.count({ queryString, includeGuests: options.includeGuestUsers });
|
|
148
|
+
case 'ORDER':
|
|
149
|
+
return modules.orders.count({ queryString, includeCarts: options.includeCarts });
|
|
150
|
+
case 'ASSORTMENT':
|
|
151
|
+
return modules.assortments.count({
|
|
152
|
+
queryString,
|
|
153
|
+
includeInactive: options.includeInactiveAssortments,
|
|
154
|
+
});
|
|
155
|
+
case 'FILTER':
|
|
156
|
+
return modules.filters.count({ queryString, includeInactive: options.includeInactiveFilters });
|
|
157
|
+
case 'ENROLLMENT':
|
|
158
|
+
return modules.enrollments.count({ queryString });
|
|
159
|
+
case 'QUOTATION':
|
|
160
|
+
return modules.quotations.count({ queryString });
|
|
161
|
+
case 'WORK':
|
|
162
|
+
return modules.worker.count({ queryString });
|
|
163
|
+
default:
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function checkTypeAuthorization(context, root, types, queryString, defaultLimit, typeLimits, options) {
|
|
168
|
+
await Promise.all(types.map((type) => checkAction(context, typeActionMap[type], [
|
|
169
|
+
root,
|
|
170
|
+
getSearchParams(type, queryString, getLimitForType(type, defaultLimit, typeLimits), options),
|
|
171
|
+
])));
|
|
172
|
+
}
|
|
173
|
+
export default async function globalSearch(root, params, context) {
|
|
174
|
+
const { query: queryString, types, limit = DEFAULT_LIMIT, typeLimits, includeDraftProducts = true, includeInactiveAssortments = true, includeInactiveFilters = true, includeGuestUsers = false, includeCarts = false, } = params;
|
|
175
|
+
const { modules, userId } = context;
|
|
176
|
+
const sanitizedQuery = queryString?.trim().slice(0, 200) || '';
|
|
177
|
+
const requestedTypes = [...new Set(types?.length ? types : ALL_TYPES)];
|
|
178
|
+
const searchOptions = {
|
|
179
|
+
includeDraftProducts,
|
|
180
|
+
includeInactiveAssortments,
|
|
181
|
+
includeInactiveFilters,
|
|
182
|
+
includeGuestUsers,
|
|
183
|
+
includeCarts,
|
|
184
|
+
};
|
|
185
|
+
await checkTypeAuthorization(context, root, requestedTypes, sanitizedQuery, limit, typeLimits, searchOptions);
|
|
186
|
+
if (!sanitizedQuery)
|
|
187
|
+
return { results: [], counts: [] };
|
|
188
|
+
log(`query globalSearch: "${sanitizedQuery}" types: ${requestedTypes.join(',')}`, { userId });
|
|
189
|
+
const [resultsByType, countsByType] = await Promise.all([
|
|
190
|
+
Promise.all(requestedTypes.map((type) => searchType(type, sanitizedQuery, getLimitForType(type, limit, typeLimits), modules, searchOptions))),
|
|
191
|
+
Promise.all(requestedTypes.map((type) => countType(type, sanitizedQuery, modules, searchOptions))),
|
|
192
|
+
]);
|
|
193
|
+
const results = resultsByType.flat();
|
|
194
|
+
const counts = requestedTypes.map((type, i) => ({
|
|
195
|
+
type,
|
|
196
|
+
totalCount: countsByType[i],
|
|
197
|
+
authorized: true,
|
|
198
|
+
}));
|
|
199
|
+
return { results, counts };
|
|
200
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ProductType } from '@unchainedshop/core-products';
|
|
2
|
+
const productTypeMap = {
|
|
3
|
+
[ProductType.CONFIGURABLE_PRODUCT]: 'ConfigurableProduct',
|
|
4
|
+
[ProductType.BUNDLE_PRODUCT]: 'BundleProduct',
|
|
5
|
+
[ProductType.PLAN_PRODUCT]: 'PlanProduct',
|
|
6
|
+
[ProductType.TOKENIZED_PRODUCT]: 'TokenizedProduct',
|
|
7
|
+
};
|
|
8
|
+
export const GlobalSearchResult = {
|
|
9
|
+
__resolveType(obj) {
|
|
10
|
+
if (obj.__typename === 'Product') {
|
|
11
|
+
return productTypeMap[obj.type] || 'SimpleProduct';
|
|
12
|
+
}
|
|
13
|
+
return obj.__typename;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -269,6 +269,9 @@ declare const types: {
|
|
|
269
269
|
forceLocale?: string;
|
|
270
270
|
}, { locale, loaders }: import("../../context.ts").Context): Promise<import("@unchainedshop/core-filters").FilterText>;
|
|
271
271
|
};
|
|
272
|
+
GlobalSearchResult: {
|
|
273
|
+
__resolveType(obj: Record<string, any>): string;
|
|
274
|
+
};
|
|
272
275
|
FilterOption: {
|
|
273
276
|
_id(obj: import("@unchainedshop/core-filters").FilterOption): string;
|
|
274
277
|
value(obj: import("@unchainedshop/core-filters").FilterOption): string;
|
|
@@ -16,6 +16,7 @@ import { EnrollmentDelivery } from "./enrollment/enrollment-delivery-types.js";
|
|
|
16
16
|
import { EnrollmentPayment } from "./enrollment/enrollment-payment-types.js";
|
|
17
17
|
import { EnrollmentPeriod } from "./enrollment/enrollment-period-types.js";
|
|
18
18
|
import { EnrollmentPlan } from "./enrollment/enrollment-plan-tyes.js";
|
|
19
|
+
import { GlobalSearchResult } from "./global-search-result-types.js";
|
|
19
20
|
import { Filter } from "./filter/filter-types.js";
|
|
20
21
|
import { FilterOption } from "./filter/filter-option-types.js";
|
|
21
22
|
import { Language } from "./language-types.js";
|
|
@@ -91,6 +92,7 @@ const types = {
|
|
|
91
92
|
EnrollmentPeriod,
|
|
92
93
|
EnrollmentPlan,
|
|
93
94
|
Filter,
|
|
95
|
+
GlobalSearchResult,
|
|
94
96
|
FilterOption,
|
|
95
97
|
Language,
|
|
96
98
|
LoadedFilter,
|
|
@@ -64,6 +64,7 @@ export interface UserHelperTypes {
|
|
|
64
64
|
lastContact: HelperType<any, Contact>;
|
|
65
65
|
lastLogin: HelperType<any, UserType['lastLogin']>;
|
|
66
66
|
allowedActions: HelperType<any, string[]>;
|
|
67
|
+
viewerAllowedActions: HelperType<any, string[]>;
|
|
67
68
|
name: HelperType<any, string>;
|
|
68
69
|
orders: HelperType<{
|
|
69
70
|
includeCarts: boolean;
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { permissions } from '@unchainedshop/roles';
|
|
2
2
|
import { checkAction, checkTypeResolver } from "../../acl.js";
|
|
3
3
|
import { actions } from "../../roles/index.js";
|
|
4
|
-
const { viewUserPrivateInfos, viewUserPublicInfos, viewUserOrders, viewUserEnrollments, viewUserQuotations, viewUserProductReviews, } = actions;
|
|
4
|
+
const { impersonate, logoutAllSessions, manageUsers, removeUser, updateUser, updateUsername, uploadUserAvatar, viewUser, viewUserRoles, viewUserPrivateInfos, viewUserPublicInfos, viewUserOrders, viewUserEnrollments, viewUserQuotations, viewUserProductReviews, viewUserTokens, } = actions;
|
|
5
|
+
const USER_TARGET_ACTIONS = [
|
|
6
|
+
impersonate,
|
|
7
|
+
logoutAllSessions,
|
|
8
|
+
manageUsers,
|
|
9
|
+
removeUser,
|
|
10
|
+
updateUser,
|
|
11
|
+
updateUsername,
|
|
12
|
+
uploadUserAvatar,
|
|
13
|
+
viewUser,
|
|
14
|
+
viewUserEnrollments,
|
|
15
|
+
viewUserOrders,
|
|
16
|
+
viewUserPrivateInfos,
|
|
17
|
+
viewUserProductReviews,
|
|
18
|
+
viewUserPublicInfos,
|
|
19
|
+
viewUserQuotations,
|
|
20
|
+
viewUserRoles,
|
|
21
|
+
viewUserTokens,
|
|
22
|
+
];
|
|
5
23
|
export const User = {
|
|
6
24
|
_id: checkTypeResolver(viewUserPublicInfos, '_id'),
|
|
7
25
|
created: checkTypeResolver(viewUserPrivateInfos, 'created'),
|
|
@@ -12,7 +30,7 @@ export const User = {
|
|
|
12
30
|
lastContact: checkTypeResolver(viewUserPrivateInfos, 'lastContact'),
|
|
13
31
|
lastLogin: checkTypeResolver(viewUserPrivateInfos, 'lastLogin'),
|
|
14
32
|
profile: checkTypeResolver(viewUserPrivateInfos, 'profile'),
|
|
15
|
-
roles: checkTypeResolver(
|
|
33
|
+
roles: checkTypeResolver(viewUserRoles, 'roles'),
|
|
16
34
|
tags: checkTypeResolver(viewUserPrivateInfos, 'tags'),
|
|
17
35
|
username: checkTypeResolver(viewUserPrivateInfos, 'username'),
|
|
18
36
|
name: async (user, params, context) => {
|
|
@@ -58,7 +76,7 @@ export const User = {
|
|
|
58
76
|
});
|
|
59
77
|
},
|
|
60
78
|
async tokens(user, params, context) {
|
|
61
|
-
await checkAction(context,
|
|
79
|
+
await checkAction(context, viewUserTokens, [user, params]);
|
|
62
80
|
const walletAddresses = user.services?.web3?.flatMap((service) => {
|
|
63
81
|
return service.verified ? [service.address] : [];
|
|
64
82
|
}) || [];
|
|
@@ -89,10 +107,23 @@ export const User = {
|
|
|
89
107
|
return context.loaders.languageLoader.load({ isoCode: userLocale.language });
|
|
90
108
|
},
|
|
91
109
|
allowedActions: async (user, params, context) => {
|
|
92
|
-
await checkAction(context,
|
|
110
|
+
await checkAction(context, viewUserRoles, [user, params]);
|
|
93
111
|
const userRoles = context.roles.getUserRoles(user?._id, user.roles, true);
|
|
94
112
|
return permissions(userRoles, context.roles.roles);
|
|
95
113
|
},
|
|
114
|
+
viewerAllowedActions: async (user, params, context) => {
|
|
115
|
+
const targetArgs = [user, { userId: user._id }];
|
|
116
|
+
await checkAction(context, viewUser, targetArgs);
|
|
117
|
+
const allowedActions = await Promise.all(USER_TARGET_ACTIONS.map(async (action) => {
|
|
118
|
+
try {
|
|
119
|
+
return (await context.roles.userHasPermission(context, action, targetArgs)) ? action : null;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
return allowedActions.filter((action) => action !== null).toSorted();
|
|
126
|
+
},
|
|
96
127
|
orders: async (user, params, context) => {
|
|
97
128
|
await checkAction(context, viewUserOrders, [user, params]);
|
|
98
129
|
return context.modules.orders.findOrders({
|
package/lib/roles/all.js
CHANGED
|
@@ -101,6 +101,18 @@ export const all = (role, actions) => {
|
|
|
101
101
|
role.allow(actions.registerPaymentCredentials, () => false);
|
|
102
102
|
role.allow(actions.managePaymentCredentials, () => false);
|
|
103
103
|
role.allow(actions.bulkImport, () => false);
|
|
104
|
+
role.allow(actions.bulkSetProductStatus, () => false);
|
|
105
|
+
role.allow(actions.bulkUpdateProductTags, () => false);
|
|
106
|
+
role.allow(actions.bulkAssignProductsToAssortment, () => false);
|
|
107
|
+
role.allow(actions.bulkRemoveProducts, () => false);
|
|
108
|
+
role.allow(actions.bulkUpdateUserTags, () => false);
|
|
109
|
+
role.allow(actions.bulkRemoveUsers, () => false);
|
|
110
|
+
role.allow(actions.bulkSetUserRoles, () => false);
|
|
111
|
+
role.allow(actions.bulkRemoveAssortments, () => false);
|
|
112
|
+
role.allow(actions.bulkUpdateAssortmentTags, () => false);
|
|
113
|
+
role.allow(actions.bulkSetAssortmentActive, () => false);
|
|
114
|
+
role.allow(actions.bulkRemoveFilters, () => false);
|
|
115
|
+
role.allow(actions.bulkSetFilterActive, () => false);
|
|
104
116
|
role.allow(actions.viewOrder, () => false);
|
|
105
117
|
role.allow(actions.viewQuotation, () => false);
|
|
106
118
|
role.allow(actions.viewEnrollment, () => false);
|
package/lib/roles/index.js
CHANGED
|
@@ -96,6 +96,18 @@ const actions = [
|
|
|
96
96
|
'registerPaymentCredentials',
|
|
97
97
|
'managePaymentCredentials',
|
|
98
98
|
'bulkImport',
|
|
99
|
+
'bulkSetProductStatus',
|
|
100
|
+
'bulkUpdateProductTags',
|
|
101
|
+
'bulkAssignProductsToAssortment',
|
|
102
|
+
'bulkRemoveProducts',
|
|
103
|
+
'bulkUpdateUserTags',
|
|
104
|
+
'bulkRemoveUsers',
|
|
105
|
+
'bulkSetUserRoles',
|
|
106
|
+
'bulkRemoveAssortments',
|
|
107
|
+
'bulkUpdateAssortmentTags',
|
|
108
|
+
'bulkSetAssortmentActive',
|
|
109
|
+
'bulkRemoveFilters',
|
|
110
|
+
'bulkSetFilterActive',
|
|
99
111
|
'logout',
|
|
100
112
|
'logoutAllSessions',
|
|
101
113
|
'loginAsGuest',
|
package/lib/roles/loggedIn.js
CHANGED
|
@@ -184,5 +184,5 @@ export const loggedIn = (role, actions) => {
|
|
|
184
184
|
role.allow(actions.uploadUserAvatar, canUpdateAvatar);
|
|
185
185
|
role.allow(actions.uploadTempFile, canUpdateAvatar);
|
|
186
186
|
role.allow(actions.changePassword, () => true);
|
|
187
|
-
role.allow(actions.logoutAllSessions,
|
|
187
|
+
role.allow(actions.logoutAllSessions, isMyself);
|
|
188
188
|
};
|
package/lib/schema/inputTypes.js
CHANGED
package/lib/schema/mutation.js
CHANGED
|
@@ -60,8 +60,9 @@ export default [
|
|
|
60
60
|
"""
|
|
61
61
|
Log the user out of all sessions by invalidating all JWT tokens.
|
|
62
62
|
This increments the token version, making all existing tokens invalid.
|
|
63
|
+
Pass a userId to force-logout another user (requires updateUser permission).
|
|
63
64
|
"""
|
|
64
|
-
logoutAllSessions: SuccessResponse
|
|
65
|
+
logoutAllSessions(userId: ID): SuccessResponse
|
|
65
66
|
|
|
66
67
|
"""
|
|
67
68
|
Impersonate a user
|
|
@@ -858,6 +859,78 @@ export default [
|
|
|
858
859
|
invalidateToken(tokenId: ID!): Token!
|
|
859
860
|
exportToken(tokenId: ID!, quantity: Int! = 1, recipientWalletAddress: String!): Token!
|
|
860
861
|
|
|
862
|
+
"""
|
|
863
|
+
Publish or unpublish multiple products at once
|
|
864
|
+
"""
|
|
865
|
+
bulkSetProductStatus(productIds: [ID!]!, status: ProductStatus!): BulkOperationResult!
|
|
866
|
+
|
|
867
|
+
"""
|
|
868
|
+
Add or remove tags from multiple products at once
|
|
869
|
+
"""
|
|
870
|
+
bulkUpdateProductTags(
|
|
871
|
+
productIds: [ID!]!
|
|
872
|
+
add: [LowerCaseString!]
|
|
873
|
+
remove: [LowerCaseString!]
|
|
874
|
+
): BulkOperationResult!
|
|
875
|
+
|
|
876
|
+
"""
|
|
877
|
+
Assign multiple products to an assortment
|
|
878
|
+
"""
|
|
879
|
+
bulkAssignProductsToAssortment(productIds: [ID!]!, assortmentId: ID!): BulkOperationResult!
|
|
880
|
+
|
|
881
|
+
"""
|
|
882
|
+
Remove multiple products
|
|
883
|
+
"""
|
|
884
|
+
bulkRemoveProducts(productIds: [ID!]!): BulkOperationResult!
|
|
885
|
+
|
|
886
|
+
"""
|
|
887
|
+
Add or remove tags from multiple users at once
|
|
888
|
+
"""
|
|
889
|
+
bulkUpdateUserTags(
|
|
890
|
+
userIds: [ID!]!
|
|
891
|
+
add: [LowerCaseString!]
|
|
892
|
+
remove: [LowerCaseString!]
|
|
893
|
+
): BulkOperationResult!
|
|
894
|
+
|
|
895
|
+
"""
|
|
896
|
+
Remove multiple users
|
|
897
|
+
"""
|
|
898
|
+
bulkRemoveUsers(userIds: [ID!]!): BulkOperationResult!
|
|
899
|
+
|
|
900
|
+
"""
|
|
901
|
+
Set roles for multiple users at once
|
|
902
|
+
"""
|
|
903
|
+
bulkSetUserRoles(userIds: [ID!]!, roles: [String!]!): BulkOperationResult!
|
|
904
|
+
|
|
905
|
+
"""
|
|
906
|
+
Remove multiple assortments
|
|
907
|
+
"""
|
|
908
|
+
bulkRemoveAssortments(assortmentIds: [ID!]!): BulkOperationResult!
|
|
909
|
+
|
|
910
|
+
"""
|
|
911
|
+
Add or remove tags from multiple assortments at once
|
|
912
|
+
"""
|
|
913
|
+
bulkUpdateAssortmentTags(
|
|
914
|
+
assortmentIds: [ID!]!
|
|
915
|
+
add: [LowerCaseString!]
|
|
916
|
+
remove: [LowerCaseString!]
|
|
917
|
+
): BulkOperationResult!
|
|
918
|
+
|
|
919
|
+
"""
|
|
920
|
+
Activate or deactivate multiple assortments at once
|
|
921
|
+
"""
|
|
922
|
+
bulkSetAssortmentActive(assortmentIds: [ID!]!, isActive: Boolean!): BulkOperationResult!
|
|
923
|
+
|
|
924
|
+
"""
|
|
925
|
+
Remove multiple filters
|
|
926
|
+
"""
|
|
927
|
+
bulkRemoveFilters(filterIds: [ID!]!): BulkOperationResult!
|
|
928
|
+
|
|
929
|
+
"""
|
|
930
|
+
Activate or deactivate multiple filters at once
|
|
931
|
+
"""
|
|
932
|
+
bulkSetFilterActive(filterIds: [ID!]!, isActive: Boolean!): BulkOperationResult!
|
|
933
|
+
|
|
861
934
|
"""
|
|
862
935
|
Store user W3C Push subscription object
|
|
863
936
|
"""
|
package/lib/schema/query.js
CHANGED
|
@@ -415,6 +415,21 @@ export default [
|
|
|
415
415
|
"""
|
|
416
416
|
enrollment(enrollmentId: ID!): Enrollment
|
|
417
417
|
|
|
418
|
+
"""
|
|
419
|
+
Search across multiple entity types in a single request
|
|
420
|
+
"""
|
|
421
|
+
globalSearch(
|
|
422
|
+
query: String!
|
|
423
|
+
types: [SearchableEntity!]
|
|
424
|
+
limit: Int = 5
|
|
425
|
+
typeLimits: [GlobalSearchTypeLimitInput!]
|
|
426
|
+
includeDraftProducts: Boolean = true
|
|
427
|
+
includeInactiveAssortments: Boolean = true
|
|
428
|
+
includeInactiveFilters: Boolean = true
|
|
429
|
+
includeGuestUsers: Boolean = false
|
|
430
|
+
includeCarts: Boolean = false
|
|
431
|
+
): GlobalSearchResponse! @cacheControl(scope: PRIVATE, maxAge: 0)
|
|
432
|
+
|
|
418
433
|
"""
|
|
419
434
|
Search products
|
|
420
435
|
"""
|
|
@@ -28,5 +28,41 @@ export default [
|
|
|
28
28
|
assortmentsCount: Int!
|
|
29
29
|
assortments(limit: Int = 10, offset: Int = 0): [Assortment!]!
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
enum SearchableEntity {
|
|
33
|
+
PRODUCT
|
|
34
|
+
USER
|
|
35
|
+
ORDER
|
|
36
|
+
ASSORTMENT
|
|
37
|
+
FILTER
|
|
38
|
+
ENROLLMENT
|
|
39
|
+
QUOTATION
|
|
40
|
+
WORK
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
union GlobalSearchResult =
|
|
44
|
+
| SimpleProduct
|
|
45
|
+
| ConfigurableProduct
|
|
46
|
+
| BundleProduct
|
|
47
|
+
| PlanProduct
|
|
48
|
+
| TokenizedProduct
|
|
49
|
+
| User
|
|
50
|
+
| Order
|
|
51
|
+
| Assortment
|
|
52
|
+
| Filter
|
|
53
|
+
| Enrollment
|
|
54
|
+
| Quotation
|
|
55
|
+
| Work
|
|
56
|
+
|
|
57
|
+
type GlobalSearchTypeCount {
|
|
58
|
+
type: SearchableEntity!
|
|
59
|
+
totalCount: Int!
|
|
60
|
+
authorized: Boolean!
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type GlobalSearchResponse {
|
|
64
|
+
results: [GlobalSearchResult!]!
|
|
65
|
+
counts: [GlobalSearchTypeCount!]!
|
|
66
|
+
}
|
|
31
67
|
`,
|
|
32
68
|
];
|
package/lib/schema/types/user.js
CHANGED
|
@@ -134,6 +134,10 @@ export default [
|
|
|
134
134
|
sort: [SortOptionInput!]
|
|
135
135
|
): [Enrollment!]!
|
|
136
136
|
allowedActions: [RoleAction!]!
|
|
137
|
+
"""
|
|
138
|
+
Built-in user-target actions the current viewer can perform on this user.
|
|
139
|
+
"""
|
|
140
|
+
viewerAllowedActions: [RoleAction!]! @cacheControl(maxAge: 0, scope: PRIVATE)
|
|
137
141
|
tokens: [Token!]!
|
|
138
142
|
reviews(limit: Int = 10, offset: Int = 0, sort: [SortOptionInput!]): [ProductReview!]!
|
|
139
143
|
reviewsCount: Int!
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@unchainedshop/api",
|
|
3
3
|
"description": "GraphQL API layer for the Unchained Engine with Express/Fastify adapters and MCP server",
|
|
4
4
|
"mcpName": "io.github.unchainedshop/unchained",
|
|
5
|
-
"version": "5.0.0-alpha.
|
|
5
|
+
"version": "5.0.0-alpha.5",
|
|
6
6
|
"main": "lib/api-index.js",
|
|
7
7
|
"types": "lib/api-index.d.ts",
|
|
8
8
|
"type": "module",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@fastify/multipart": ">= 10 < 11",
|
|
67
67
|
"@fastify/static": ">= 10.1.3 < 11",
|
|
68
68
|
"@modelcontextprotocol/server": ">= 2 < 3",
|
|
69
|
-
"@unchainedshop/admin-ui": "^5.0.0-alpha.
|
|
69
|
+
"@unchainedshop/admin-ui": "^5.0.0-alpha.5",
|
|
70
70
|
"ai": ">= 7 < 8",
|
|
71
71
|
"cookie-parser": ">= 1.4 < 2",
|
|
72
72
|
"express": ">= 5 < 6",
|
|
@@ -118,29 +118,29 @@
|
|
|
118
118
|
},
|
|
119
119
|
"dependencies": {
|
|
120
120
|
"@envelop/core": "^5.6.0",
|
|
121
|
-
"@unchainedshop/core": "^5.0.0-alpha.
|
|
122
|
-
"@unchainedshop/core-assortments": "^5.0.0-alpha.
|
|
123
|
-
"@unchainedshop/core-bookmarks": "^5.0.0-alpha.
|
|
124
|
-
"@unchainedshop/core-countries": "^5.0.0-alpha.
|
|
125
|
-
"@unchainedshop/core-currencies": "^5.0.0-alpha.
|
|
126
|
-
"@unchainedshop/core-delivery": "^5.0.0-alpha.
|
|
127
|
-
"@unchainedshop/core-enrollments": "^5.0.0-alpha.
|
|
128
|
-
"@unchainedshop/core-events": "^5.0.0-alpha.
|
|
129
|
-
"@unchainedshop/core-files": "^5.0.0-alpha.
|
|
130
|
-
"@unchainedshop/core-filters": "^5.0.0-alpha.
|
|
131
|
-
"@unchainedshop/core-languages": "^5.0.0-alpha.
|
|
132
|
-
"@unchainedshop/core-orders": "^5.0.0-alpha.
|
|
133
|
-
"@unchainedshop/core-payment": "^5.0.0-alpha.
|
|
134
|
-
"@unchainedshop/core-products": "^5.0.0-alpha.
|
|
135
|
-
"@unchainedshop/core-quotations": "^5.0.0-alpha.
|
|
136
|
-
"@unchainedshop/core-users": "^5.0.0-alpha.
|
|
137
|
-
"@unchainedshop/core-warehousing": "^5.0.0-alpha.
|
|
138
|
-
"@unchainedshop/core-worker": "^5.0.0-alpha.
|
|
139
|
-
"@unchainedshop/events": "^5.0.0-alpha.
|
|
140
|
-
"@unchainedshop/logger": "^5.0.0-alpha.
|
|
141
|
-
"@unchainedshop/mongodb": "^5.0.0-alpha.
|
|
142
|
-
"@unchainedshop/roles": "^5.0.0-alpha.
|
|
143
|
-
"@unchainedshop/utils": "^5.0.0-alpha.
|
|
121
|
+
"@unchainedshop/core": "^5.0.0-alpha.5",
|
|
122
|
+
"@unchainedshop/core-assortments": "^5.0.0-alpha.5",
|
|
123
|
+
"@unchainedshop/core-bookmarks": "^5.0.0-alpha.5",
|
|
124
|
+
"@unchainedshop/core-countries": "^5.0.0-alpha.5",
|
|
125
|
+
"@unchainedshop/core-currencies": "^5.0.0-alpha.5",
|
|
126
|
+
"@unchainedshop/core-delivery": "^5.0.0-alpha.5",
|
|
127
|
+
"@unchainedshop/core-enrollments": "^5.0.0-alpha.5",
|
|
128
|
+
"@unchainedshop/core-events": "^5.0.0-alpha.5",
|
|
129
|
+
"@unchainedshop/core-files": "^5.0.0-alpha.5",
|
|
130
|
+
"@unchainedshop/core-filters": "^5.0.0-alpha.5",
|
|
131
|
+
"@unchainedshop/core-languages": "^5.0.0-alpha.5",
|
|
132
|
+
"@unchainedshop/core-orders": "^5.0.0-alpha.5",
|
|
133
|
+
"@unchainedshop/core-payment": "^5.0.0-alpha.5",
|
|
134
|
+
"@unchainedshop/core-products": "^5.0.0-alpha.5",
|
|
135
|
+
"@unchainedshop/core-quotations": "^5.0.0-alpha.5",
|
|
136
|
+
"@unchainedshop/core-users": "^5.0.0-alpha.5",
|
|
137
|
+
"@unchainedshop/core-warehousing": "^5.0.0-alpha.5",
|
|
138
|
+
"@unchainedshop/core-worker": "^5.0.0-alpha.5",
|
|
139
|
+
"@unchainedshop/events": "^5.0.0-alpha.5",
|
|
140
|
+
"@unchainedshop/logger": "^5.0.0-alpha.5",
|
|
141
|
+
"@unchainedshop/mongodb": "^5.0.0-alpha.5",
|
|
142
|
+
"@unchainedshop/roles": "^5.0.0-alpha.5",
|
|
143
|
+
"@unchainedshop/utils": "^5.0.0-alpha.5",
|
|
144
144
|
"@whatwg-node/server": "^0.11.0",
|
|
145
145
|
"dataloader": "^2.2.3",
|
|
146
146
|
"graphql-scalars": "^2.0.0",
|