@unchainedshop/api 4.8.25 → 4.8.27

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.
Files changed (51) hide show
  1. package/lib/errors.d.ts +1 -0
  2. package/lib/errors.js +1 -0
  3. package/lib/mcp/tools/assortment/schemas.d.ts +13 -13
  4. package/lib/mcp/tools/filter/schemas.d.ts +2 -2
  5. package/lib/mcp/tools/localization/schemas.d.ts +5 -5
  6. package/lib/mcp/tools/order/schemas.d.ts +7 -7
  7. package/lib/mcp/tools/product/schemas.d.ts +44 -44
  8. package/lib/mcp/tools/quotation/schemas.d.ts +2 -2
  9. package/lib/mcp/tools/system/handlers/allocateWork.js +2 -2
  10. package/lib/mcp/tools/system/schemas.d.ts +6 -6
  11. package/lib/mcp/tools/users/schemas.d.ts +10 -10
  12. package/lib/mcp/utils/sharedSchemas.d.ts +4 -4
  13. package/lib/middleware/createAuthMiddleware.js +2 -0
  14. package/lib/resolvers/mutations/accounts/logoutAllSessions.d.ts +3 -1
  15. package/lib/resolvers/mutations/accounts/logoutAllSessions.js +10 -4
  16. package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.d.ts +10 -0
  17. package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.js +18 -0
  18. package/lib/resolvers/mutations/bulk/bulkOperation.d.ts +12 -0
  19. package/lib/resolvers/mutations/bulk/bulkOperation.js +14 -0
  20. package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.d.ts +9 -0
  21. package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.js +7 -0
  22. package/lib/resolvers/mutations/bulk/bulkRemoveFilters.d.ts +9 -0
  23. package/lib/resolvers/mutations/bulk/bulkRemoveFilters.js +9 -0
  24. package/lib/resolvers/mutations/bulk/bulkRemoveProducts.d.ts +9 -0
  25. package/lib/resolvers/mutations/bulk/bulkRemoveProducts.js +9 -0
  26. package/lib/resolvers/mutations/bulk/bulkRemoveUsers.d.ts +9 -0
  27. package/lib/resolvers/mutations/bulk/bulkRemoveUsers.js +7 -0
  28. package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.d.ts +10 -0
  29. package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.js +9 -0
  30. package/lib/resolvers/mutations/bulk/bulkSetFilterActive.d.ts +10 -0
  31. package/lib/resolvers/mutations/bulk/bulkSetFilterActive.js +10 -0
  32. package/lib/resolvers/mutations/bulk/bulkSetProductStatus.d.ts +10 -0
  33. package/lib/resolvers/mutations/bulk/bulkSetProductStatus.js +17 -0
  34. package/lib/resolvers/mutations/bulk/bulkSetUserRoles.d.ts +10 -0
  35. package/lib/resolvers/mutations/bulk/bulkSetUserRoles.js +14 -0
  36. package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.d.ts +11 -0
  37. package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.js +10 -0
  38. package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.d.ts +11 -0
  39. package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.js +7 -0
  40. package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.d.ts +11 -0
  41. package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.js +7 -0
  42. package/lib/resolvers/mutations/users/removeUser.js +9 -2
  43. package/lib/resolvers/mutations/users/setRoles.js +9 -2
  44. package/lib/resolvers/mutations/worker/allocateWork.d.ts +1 -1
  45. package/lib/resolvers/mutations/worker/allocateWork.js +4 -3
  46. package/lib/resolvers/queries/search/globalSearch.d.ts +36 -0
  47. package/lib/resolvers/queries/search/globalSearch.js +200 -0
  48. package/lib/resolvers/type/global-search-result-types.d.ts +3 -0
  49. package/lib/resolvers/type/global-search-result-types.js +15 -0
  50. package/lib/schema/types/common.js +7 -0
  51. package/package.json +24 -24
@@ -0,0 +1,18 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { InvalidIdError, AssortmentNotFoundError } from "../../../errors.js";
3
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
4
+ export default async function bulkAssignProductsToAssortment(root, { productIds, assortmentId }, { modules, services, userId }) {
5
+ log(`mutation bulkAssignProductsToAssortment ${assortmentId} for ${productIds.length} products`, {
6
+ userId,
7
+ });
8
+ const normalizedProductIds = normalizeBulkIds(productIds);
9
+ if (!assortmentId)
10
+ throw new InvalidIdError({ assortmentId });
11
+ if (!(await modules.assortments.assortmentExists({ assortmentId })))
12
+ throw new AssortmentNotFoundError({ assortmentId });
13
+ const result = await services.assortments.bulkAssignProductsToAssortment({
14
+ assortmentId,
15
+ productIds: normalizedProductIds,
16
+ });
17
+ return createBulkOperationResult(result);
18
+ }
@@ -0,0 +1,12 @@
1
+ export declare const MAX_BULK_OPERATION_SIZE = 1000;
2
+ export interface BulkOperationIds {
3
+ successIds: string[];
4
+ failedIds: string[];
5
+ }
6
+ export declare const normalizeBulkIds: (ids: readonly string[]) => string[];
7
+ export declare const createBulkOperationResult: ({ successIds, failedIds }: BulkOperationIds) => {
8
+ successIds: string[];
9
+ successCount: number;
10
+ failedIds: string[];
11
+ failedCount: number;
12
+ };
@@ -0,0 +1,14 @@
1
+ import { BulkOperationTooLargeError } from "../../../errors.js";
2
+ export const MAX_BULK_OPERATION_SIZE = 1000;
3
+ export const normalizeBulkIds = (ids) => {
4
+ if (ids.length > MAX_BULK_OPERATION_SIZE) {
5
+ throw new BulkOperationTooLargeError({ limit: MAX_BULK_OPERATION_SIZE });
6
+ }
7
+ return [...new Set(ids)];
8
+ };
9
+ export const createBulkOperationResult = ({ successIds, failedIds }) => ({
10
+ successIds,
11
+ successCount: successIds.length,
12
+ failedIds,
13
+ failedCount: failedIds.length,
14
+ });
@@ -0,0 +1,9 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkRemoveAssortments(root: never, { assortmentIds }: {
3
+ assortmentIds: string[];
4
+ }, { modules, userId }: Context): Promise<{
5
+ successIds: string[];
6
+ successCount: number;
7
+ failedIds: string[];
8
+ failedCount: number;
9
+ }>;
@@ -0,0 +1,7 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkRemoveAssortments(root, { assortmentIds }, { modules, userId }) {
4
+ log(`mutation bulkRemoveAssortments for ${assortmentIds.length} assortments`, { userId });
5
+ const result = await modules.assortments.bulkDelete(normalizeBulkIds(assortmentIds));
6
+ return createBulkOperationResult(result);
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkRemoveFilters(root: never, { filterIds }: {
3
+ filterIds: string[];
4
+ }, { services, userId }: Context): Promise<{
5
+ successIds: string[];
6
+ successCount: number;
7
+ failedIds: string[];
8
+ failedCount: number;
9
+ }>;
@@ -0,0 +1,9 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkRemoveFilters(root, { filterIds }, { services, userId }) {
4
+ log(`mutation bulkRemoveFilters for ${filterIds.length} filters`, { userId });
5
+ const result = await services.filters.bulkRemoveFilters({
6
+ filterIds: normalizeBulkIds(filterIds),
7
+ });
8
+ return createBulkOperationResult(result);
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkRemoveProducts(root: never, { productIds }: {
3
+ productIds: string[];
4
+ }, { services, userId }: Context): Promise<{
5
+ successIds: string[];
6
+ successCount: number;
7
+ failedIds: string[];
8
+ failedCount: number;
9
+ }>;
@@ -0,0 +1,9 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkRemoveProducts(root, { productIds }, { services, userId }) {
4
+ log(`mutation bulkRemoveProducts for ${productIds.length} products`, { userId });
5
+ const result = await services.products.bulkRemoveProducts({
6
+ productIds: normalizeBulkIds(productIds),
7
+ });
8
+ return createBulkOperationResult(result);
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkRemoveUsers(root: never, { userIds }: {
3
+ userIds: string[];
4
+ }, { services, userId }: Context): Promise<{
5
+ successIds: string[];
6
+ successCount: number;
7
+ failedIds: string[];
8
+ failedCount: number;
9
+ }>;
@@ -0,0 +1,7 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkRemoveUsers(root, { userIds }, { services, userId }) {
4
+ log(`mutation bulkRemoveUsers for ${userIds.length} users`, { userId });
5
+ const result = await services.users.bulkDeleteUsers({ userIds: normalizeBulkIds(userIds) });
6
+ return createBulkOperationResult(result);
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkSetAssortmentActive(root: never, { assortmentIds, isActive }: {
3
+ assortmentIds: string[];
4
+ isActive: boolean;
5
+ }, { modules, userId }: Context): Promise<{
6
+ successIds: string[];
7
+ successCount: number;
8
+ failedIds: string[];
9
+ failedCount: number;
10
+ }>;
@@ -0,0 +1,9 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkSetAssortmentActive(root, { assortmentIds, isActive }, { modules, userId }) {
4
+ log(`mutation bulkSetAssortmentActive ${isActive} for ${assortmentIds.length} assortments`, {
5
+ userId,
6
+ });
7
+ const result = await modules.assortments.bulkSetActive(normalizeBulkIds(assortmentIds), isActive);
8
+ return createBulkOperationResult(result);
9
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkSetFilterActive(root: never, { filterIds, isActive }: {
3
+ filterIds: string[];
4
+ isActive: boolean;
5
+ }, { services, userId }: Context): Promise<{
6
+ successIds: string[];
7
+ successCount: number;
8
+ failedIds: string[];
9
+ failedCount: number;
10
+ }>;
@@ -0,0 +1,10 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkSetFilterActive(root, { filterIds, isActive }, { services, userId }) {
4
+ log(`mutation bulkSetFilterActive ${isActive} for ${filterIds.length} filters`, { userId });
5
+ const result = await services.filters.bulkSetFilterActive({
6
+ filterIds: normalizeBulkIds(filterIds),
7
+ isActive,
8
+ });
9
+ return createBulkOperationResult(result);
10
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkSetProductStatus(root: never, { productIds, status }: {
3
+ productIds: string[];
4
+ status: string;
5
+ }, { modules, userId }: Context): Promise<{
6
+ successIds: string[];
7
+ successCount: number;
8
+ failedIds: string[];
9
+ failedCount: number;
10
+ }>;
@@ -0,0 +1,17 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkSetProductStatus(root, { productIds, status }, { modules, userId }) {
4
+ log(`mutation bulkSetProductStatus ${status} for ${productIds.length} products`, { userId });
5
+ const normalizedProductIds = normalizeBulkIds(productIds);
6
+ let result;
7
+ if (status === 'ACTIVE') {
8
+ result = await modules.products.bulkPublish(normalizedProductIds);
9
+ }
10
+ else if (status === 'DRAFT') {
11
+ result = await modules.products.bulkUnpublish(normalizedProductIds);
12
+ }
13
+ else {
14
+ result = { successIds: [], failedIds: normalizedProductIds };
15
+ }
16
+ return createBulkOperationResult(result);
17
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkSetUserRoles(root: never, { userIds, roles }: {
3
+ userIds: string[];
4
+ roles: string[];
5
+ }, context: Context): Promise<{
6
+ successIds: string[];
7
+ successCount: number;
8
+ failedIds: string[];
9
+ failedCount: number;
10
+ }>;
@@ -0,0 +1,14 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { getPublicRoles } from "../../../roles/index.js";
3
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
4
+ export default async function bulkSetUserRoles(root, { userIds, roles }, context) {
5
+ const { modules, userId } = context;
6
+ log(`mutation bulkSetUserRoles for ${userIds.length} users`, { userId });
7
+ const publicRoles = getPublicRoles(context.roles);
8
+ const invalidRoles = roles.filter((r) => !publicRoles.includes(r));
9
+ if (invalidRoles.length) {
10
+ throw new Error(`Invalid role names: ${invalidRoles.join(', ')}`);
11
+ }
12
+ const result = await modules.users.bulkUpdateRoles(normalizeBulkIds(userIds), roles);
13
+ return createBulkOperationResult(result);
14
+ }
@@ -0,0 +1,11 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkUpdateAssortmentTags(root: never, { assortmentIds, add, remove }: {
3
+ assortmentIds: string[];
4
+ add?: string[];
5
+ remove?: string[];
6
+ }, { modules, userId }: Context): Promise<{
7
+ successIds: string[];
8
+ successCount: number;
9
+ failedIds: string[];
10
+ failedCount: number;
11
+ }>;
@@ -0,0 +1,10 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkUpdateAssortmentTags(root, { assortmentIds, add, remove }, { modules, userId }) {
4
+ log(`mutation bulkUpdateAssortmentTags for ${assortmentIds.length} assortments`, { userId });
5
+ const result = await modules.assortments.bulkUpdateTags(normalizeBulkIds(assortmentIds), {
6
+ add,
7
+ remove,
8
+ });
9
+ return createBulkOperationResult(result);
10
+ }
@@ -0,0 +1,11 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkUpdateProductTags(root: never, { productIds, add, remove }: {
3
+ productIds: string[];
4
+ add?: string[];
5
+ remove?: string[];
6
+ }, { modules, userId }: Context): Promise<{
7
+ successIds: string[];
8
+ successCount: number;
9
+ failedIds: string[];
10
+ failedCount: number;
11
+ }>;
@@ -0,0 +1,7 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkUpdateProductTags(root, { productIds, add, remove }, { modules, userId }) {
4
+ log(`mutation bulkUpdateProductTags for ${productIds.length} products`, { userId });
5
+ const result = await modules.products.bulkUpdateTags(normalizeBulkIds(productIds), { add, remove });
6
+ return createBulkOperationResult(result);
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkUpdateUserTags(root: never, { userIds, add, remove }: {
3
+ userIds: string[];
4
+ add?: string[];
5
+ remove?: string[];
6
+ }, { modules, userId }: Context): Promise<{
7
+ successIds: string[];
8
+ successCount: number;
9
+ failedIds: string[];
10
+ failedCount: number;
11
+ }>;
@@ -0,0 +1,7 @@
1
+ import { log } from '@unchainedshop/logger';
2
+ import { createBulkOperationResult, normalizeBulkIds } from "./bulkOperation.js";
3
+ export default async function bulkUpdateUserTags(root, { userIds, add, remove }, { modules, userId }) {
4
+ log(`mutation bulkUpdateUserTags for ${userIds.length} users`, { userId });
5
+ const result = await modules.users.bulkUpdateTags(normalizeBulkIds(userIds), { add, remove });
6
+ return createBulkOperationResult(result);
7
+ }
@@ -1,5 +1,5 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- import { UserNotFoundError } from "../../../errors.js";
2
+ import { LastAdminError, UserNotFoundError } from "../../../errors.js";
3
3
  export default async function removeUser(root, params, { modules, services, userId: currentUserId }) {
4
4
  const { userId: paramUserId, removeUserReviews = false } = params;
5
5
  const normalizedUserId = paramUserId || currentUserId;
@@ -9,5 +9,12 @@ export default async function removeUser(root, params, { modules, services, user
9
9
  if (removeUserReviews) {
10
10
  await modules.products.reviews.deleteByAuthorId(normalizedUserId);
11
11
  }
12
- return services.users.deleteUser({ userId: normalizedUserId });
12
+ try {
13
+ return await services.users.deleteUser({ userId: normalizedUserId });
14
+ }
15
+ catch (e) {
16
+ if (e.cause === 'LAST_ADMIN')
17
+ throw new LastAdminError({ userId: normalizedUserId });
18
+ throw e;
19
+ }
13
20
  }
@@ -1,6 +1,6 @@
1
1
  import { log } from '@unchainedshop/logger';
2
2
  import { Roles } from '@unchainedshop/roles';
3
- import { InvalidIdError, UserNotFoundError } from "../../../errors.js";
3
+ import { InvalidIdError, LastAdminError, UserNotFoundError } from "../../../errors.js";
4
4
  export default async function setRoles(root, params, { modules, userId }) {
5
5
  const foreignUserId = params.userId;
6
6
  log(`mutation setRoles ${foreignUserId}`, { userId });
@@ -12,5 +12,12 @@ export default async function setRoles(root, params, { modules, userId }) {
12
12
  if (invalidRoles.length > 0) {
13
13
  throw new Error(`Invalid role names: ${invalidRoles.join(', ')}`);
14
14
  }
15
- return modules.users.updateRoles(foreignUserId, params.roles);
15
+ try {
16
+ return await modules.users.updateRoles(foreignUserId, params.roles);
17
+ }
18
+ catch (e) {
19
+ if (e.cause === 'LAST_ADMIN')
20
+ throw new LastAdminError({ userId: foreignUserId });
21
+ throw e;
22
+ }
16
23
  }
@@ -2,4 +2,4 @@ import type { Context } from '../../../context.ts';
2
2
  export default function allocateWork(root: never, { types, worker }: {
3
3
  types: string[];
4
4
  worker: string;
5
- }, { modules, userId }: Context): Promise<import("mongodb").WithId<import("@unchainedshop/core-worker").Work> | null>;
5
+ }, context: Context): Promise<import("@unchainedshop/core-worker").Work | null>;
@@ -1,7 +1,8 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- export default async function allocateWork(root, { types, worker }, { modules, userId }) {
2
+ import { WorkerDirector } from '@unchainedshop/core';
3
+ export default async function allocateWork(root, { types, worker }, context) {
3
4
  log(`mutation allocateWork ${(types || []).join(',')} ${worker}`, {
4
- userId,
5
+ userId: context.userId,
5
6
  });
6
- return modules.worker.allocateWork({ types, worker });
7
+ return WorkerDirector.allocateWork(context, { types, worker });
7
8
  }
@@ -0,0 +1,36 @@
1
+ import type { Context } from '../../../context.ts';
2
+ import type { Product } from '@unchainedshop/core-products';
3
+ import type { User } from '@unchainedshop/core-users';
4
+ import type { Order } from '@unchainedshop/core-orders';
5
+ import type { Assortment } from '@unchainedshop/core-assortments';
6
+ import type { Filter } from '@unchainedshop/core-filters';
7
+ import type { Enrollment } from '@unchainedshop/core-enrollments';
8
+ import type { Quotation } from '@unchainedshop/core-quotations';
9
+ import type { Work } from '@unchainedshop/core-worker';
10
+ type SearchableEntity = 'PRODUCT' | 'USER' | 'ORDER' | 'ASSORTMENT' | 'FILTER' | 'ENROLLMENT' | 'QUOTATION' | 'WORK';
11
+ interface TypeLimitInput {
12
+ type: SearchableEntity;
13
+ limit: number;
14
+ }
15
+ type SearchResultItem = (Product | User | Order | Assortment | Filter | Enrollment | Quotation | Work) & {
16
+ __typename: string;
17
+ };
18
+ export default function globalSearch(root: never, params: {
19
+ query: string;
20
+ types?: SearchableEntity[];
21
+ limit?: number;
22
+ typeLimits?: TypeLimitInput[];
23
+ includeDraftProducts?: boolean;
24
+ includeInactiveAssortments?: boolean;
25
+ includeInactiveFilters?: boolean;
26
+ includeGuestUsers?: boolean;
27
+ includeCarts?: boolean;
28
+ }, context: Context): Promise<{
29
+ results: SearchResultItem[];
30
+ counts: {
31
+ type: SearchableEntity;
32
+ totalCount: number;
33
+ authorized: boolean;
34
+ }[];
35
+ }>;
36
+ export {};
@@ -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,3 @@
1
+ export declare const GlobalSearchResult: {
2
+ __resolveType(obj: Record<string, any>): string;
3
+ };
@@ -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
+ };
@@ -4,6 +4,13 @@ export default [
4
4
  success: Boolean
5
5
  }
6
6
 
7
+ type BulkOperationResult @cacheControl(maxAge: 0, scope: PRIVATE) {
8
+ successIds: [ID!]!
9
+ successCount: Int!
10
+ failedCount: Int!
11
+ failedIds: [ID!]!
12
+ }
13
+
7
14
  enum SortDirection {
8
15
  ASC
9
16
  DESC