@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.
Files changed (68) hide show
  1. package/lib/acl.js +4 -0
  2. package/lib/adminUiPlugins.d.ts +12 -1
  3. package/lib/adminUiPlugins.js +188 -26
  4. package/lib/errors.d.ts +2 -0
  5. package/lib/errors.js +2 -0
  6. package/lib/events.d.ts +1 -0
  7. package/lib/events.js +1 -0
  8. package/lib/express/index.js +44 -17
  9. package/lib/fastify/index.d.ts +2 -0
  10. package/lib/fastify/index.js +65 -24
  11. package/lib/mcp/tools/system/handlers/allocateWork.js +2 -2
  12. package/lib/mcp/tools/users/index.js +1 -1
  13. package/lib/middleware/createAuthMiddleware.js +2 -0
  14. package/lib/resolvers/mutations/accounts/loginWithPassword.js +16 -2
  15. package/lib/resolvers/mutations/accounts/logoutAllSessions.d.ts +3 -1
  16. package/lib/resolvers/mutations/accounts/logoutAllSessions.js +10 -4
  17. package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.d.ts +10 -0
  18. package/lib/resolvers/mutations/bulk/bulkAssignProductsToAssortment.js +18 -0
  19. package/lib/resolvers/mutations/bulk/bulkOperation.d.ts +12 -0
  20. package/lib/resolvers/mutations/bulk/bulkOperation.js +14 -0
  21. package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.d.ts +9 -0
  22. package/lib/resolvers/mutations/bulk/bulkRemoveAssortments.js +7 -0
  23. package/lib/resolvers/mutations/bulk/bulkRemoveFilters.d.ts +9 -0
  24. package/lib/resolvers/mutations/bulk/bulkRemoveFilters.js +9 -0
  25. package/lib/resolvers/mutations/bulk/bulkRemoveProducts.d.ts +9 -0
  26. package/lib/resolvers/mutations/bulk/bulkRemoveProducts.js +9 -0
  27. package/lib/resolvers/mutations/bulk/bulkRemoveUsers.d.ts +9 -0
  28. package/lib/resolvers/mutations/bulk/bulkRemoveUsers.js +7 -0
  29. package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.d.ts +10 -0
  30. package/lib/resolvers/mutations/bulk/bulkSetAssortmentActive.js +9 -0
  31. package/lib/resolvers/mutations/bulk/bulkSetFilterActive.d.ts +10 -0
  32. package/lib/resolvers/mutations/bulk/bulkSetFilterActive.js +10 -0
  33. package/lib/resolvers/mutations/bulk/bulkSetProductStatus.d.ts +10 -0
  34. package/lib/resolvers/mutations/bulk/bulkSetProductStatus.js +17 -0
  35. package/lib/resolvers/mutations/bulk/bulkSetUserRoles.d.ts +10 -0
  36. package/lib/resolvers/mutations/bulk/bulkSetUserRoles.js +14 -0
  37. package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.d.ts +11 -0
  38. package/lib/resolvers/mutations/bulk/bulkUpdateAssortmentTags.js +10 -0
  39. package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.d.ts +11 -0
  40. package/lib/resolvers/mutations/bulk/bulkUpdateProductTags.js +7 -0
  41. package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.d.ts +11 -0
  42. package/lib/resolvers/mutations/bulk/bulkUpdateUserTags.js +7 -0
  43. package/lib/resolvers/mutations/index.d.ts +12 -0
  44. package/lib/resolvers/mutations/index.js +25 -1
  45. package/lib/resolvers/mutations/users/removeUser.js +9 -2
  46. package/lib/resolvers/mutations/users/setRoles.js +9 -2
  47. package/lib/resolvers/mutations/worker/allocateWork.d.ts +1 -1
  48. package/lib/resolvers/mutations/worker/allocateWork.js +4 -3
  49. package/lib/resolvers/queries/index.d.ts +1 -0
  50. package/lib/resolvers/queries/index.js +2 -0
  51. package/lib/resolvers/queries/search/globalSearch.d.ts +36 -0
  52. package/lib/resolvers/queries/search/globalSearch.js +200 -0
  53. package/lib/resolvers/type/global-search-result-types.d.ts +3 -0
  54. package/lib/resolvers/type/global-search-result-types.js +15 -0
  55. package/lib/resolvers/type/index.d.ts +3 -0
  56. package/lib/resolvers/type/index.js +2 -0
  57. package/lib/resolvers/type/user-types.d.ts +1 -0
  58. package/lib/resolvers/type/user-types.js +35 -4
  59. package/lib/roles/all.js +12 -0
  60. package/lib/roles/index.js +12 -0
  61. package/lib/roles/loggedIn.js +1 -1
  62. package/lib/schema/inputTypes.js +5 -0
  63. package/lib/schema/mutation.js +74 -1
  64. package/lib/schema/query.js +15 -0
  65. package/lib/schema/types/common.js +7 -0
  66. package/lib/schema/types/search.js +36 -0
  67. package/lib/schema/types/user.js +4 -0
  68. package/package.json +25 -25
@@ -1,7 +1,7 @@
1
1
  import { usersManagement, UsersManagementSchema } from "./usersManagement.js";
2
2
  export const registerUsersTools = (server, context) => {
3
3
  server.registerTool('users_management', {
4
- description: 'Comprehensive user management system for Unchained e-commerce platform. Core operations: LIST (paginated user listing with filters for guests, email verification, last login dates), COUNT (user count with same filters), GET (retrieve user by ID), CREATE (new user with optional profile), UPDATE (profile/metadata updates), REMOVE (soft delete with optional review cleanup), ENROLL (create user + send enrollment email). User administration: SET_ROLES (assign admin/editor roles), SET_TAGS (categorization tags), SET_PASSWORD (admin password reset), SET_USERNAME (username changes). Email management: ADD_EMAIL/REMOVE_EMAIL (multi-email support), SEND_ENROLLMENT_EMAIL (trigger enrollment), SEND_VERIFICATION_EMAIL (email verification). Data access: GET_ORDERS (user order history with cart inclusion), GET_ENROLLMENTS (subscription enrollments), GET_QUOTATIONS (price quotes), GET_BOOKMARKS (saved products), GET_CART (active user cart), GET_PAYMENT_CREDENTIALS (stored payment methods), GET_AVATAR (profile image), GET_REVIEWS/GET_REVIEWS_COUNT (product reviews), REMOVE_PRODUCT_REVIEWS (bulk review cleanup), GET_CURRENT_USER (current session logged in user). Supports pagination, sorting, filtering, date ranges, and proper validation for all operations.',
4
+ description: 'User management for Unchained e-commerce. Actions: LIST, COUNT, GET, CREATE, UPDATE, REMOVE, ENROLL (create + send enrollment email), SET_TAGS, SET_USERNAME, ADD_EMAIL, REMOVE_EMAIL, SEND_ENROLLMENT_EMAIL, SEND_VERIFICATION_EMAIL, GET_ORDERS, GET_ENROLLMENTS, GET_QUOTATIONS, GET_BOOKMARKS, GET_PAYMENT_CREDENTIALS, GET_AVATAR, GET_REVIEWS, GET_REVIEWS_COUNT, REMOVE_PRODUCT_REVIEWS, GET_CURRENT_USER. LIST supports pagination, sorting, search, and filters for guests, email verification, and last login; COUNT supports the same search and filter criteria.',
5
5
  inputSchema: UsersManagementSchema,
6
6
  }, async (params) => usersManagement(context, params));
7
7
  };
@@ -61,7 +61,9 @@ export async function createAuthContext(params, authConfig) {
61
61
  const tokenObject = {
62
62
  _id: crypto.randomUUID(),
63
63
  userId: user._id,
64
+ userName: user.username || user.emails?.[0]?.address,
64
65
  tokenExpires: expires,
66
+ remoteAddress: params.remoteAddress,
65
67
  };
66
68
  await emit(API_EVENTS.API_LOGIN_TOKEN_CREATED, tokenObject);
67
69
  user._inLoginMethodResponse = true;
@@ -1,5 +1,7 @@
1
1
  import { log } from '@unchainedshop/logger';
2
+ import { emit } from '@unchainedshop/events';
2
3
  import { InvalidCredentialsError, UsernameOrEmailRequiredError } from "../../../errors.js";
4
+ import { API_EVENTS } from "../../../events.js";
3
5
  export default async function loginWithPassword(root, params, context) {
4
6
  const { username, email, password } = params;
5
7
  log('mutation loginWithPassword', { username, email });
@@ -8,12 +10,24 @@ export default async function loginWithPassword(root, params, context) {
8
10
  let user = username
9
11
  ? await context.modules.users.findUserByUsername(username)
10
12
  : await context.modules.users.findUserByEmail(email);
11
- if (!user)
13
+ if (!user) {
14
+ await emit(API_EVENTS.API_LOGIN_FAILED, {
15
+ username,
16
+ email,
17
+ remoteAddress: context.remoteAddress,
18
+ });
12
19
  throw new InvalidCredentialsError({ username, email });
20
+ }
13
21
  const verified = user.services?.password &&
14
22
  (await context.modules.users.verifyPassword(user.services.password, password));
15
- if (!verified)
23
+ if (!verified) {
24
+ await emit(API_EVENTS.API_LOGIN_FAILED, {
25
+ userId: user._id,
26
+ username: user.username,
27
+ remoteAddress: context.remoteAddress,
28
+ });
16
29
  throw new InvalidCredentialsError({ username, email });
30
+ }
17
31
  if (user.guest) {
18
32
  await context.modules.users.updateGuest(user, false);
19
33
  }
@@ -1,4 +1,6 @@
1
1
  import type { Context } from '../../../context.ts';
2
- export default function logoutAllSessions(root: never, _: never, context: Context): Promise<{
2
+ export default function logoutAllSessions(root: never, params: {
3
+ userId?: string;
4
+ }, context: Context): Promise<{
3
5
  success: boolean;
4
6
  }>;
@@ -1,11 +1,17 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- export default async function logoutAllSessions(root, _, context) {
2
+ import { UserNotFoundError } from "../../../errors.js";
3
+ export default async function logoutAllSessions(root, params, context) {
3
4
  const { userId, modules } = context;
4
- log('mutation logoutAllSessions', { userId });
5
- const result = await modules.users.incrementTokenVersion(userId);
5
+ const normalizedUserId = params.userId || userId;
6
+ log(`mutation logoutAllSessions ${normalizedUserId}`, { userId });
7
+ if (!(await modules.users.userExists({ userId: normalizedUserId })))
8
+ throw new UserNotFoundError({ userId: normalizedUserId });
9
+ const result = await modules.users.incrementTokenVersion(normalizedUserId);
6
10
  if (!result) {
7
11
  throw new Error('Failed to logout all sessions', { cause: 'LOGOUT_FAILED' });
8
12
  }
9
- await context.logout();
13
+ if (normalizedUserId === userId) {
14
+ await context.logout();
15
+ }
10
16
  return { success: true };
11
17
  }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../../../context.ts';
2
+ export default function bulkAssignProductsToAssortment(root: never, { productIds, assortmentId }: {
3
+ productIds: string[];
4
+ assortmentId: string;
5
+ }, { modules, services, userId }: Context): Promise<{
6
+ successIds: string[];
7
+ successCount: number;
8
+ failedIds: string[];
9
+ failedCount: number;
10
+ }>;
@@ -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
+ }
@@ -149,5 +149,17 @@ declare const _default: {
149
149
  signPaymentProviderForCredentialRegistration: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
150
150
  signPaymentProviderForCheckout: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
151
151
  removeUserProductReviews: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
152
+ bulkSetProductStatus: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
153
+ bulkUpdateProductTags: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
154
+ bulkAssignProductsToAssortment: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
155
+ bulkRemoveProducts: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
156
+ bulkUpdateUserTags: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
157
+ bulkRemoveAssortments: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
158
+ bulkUpdateAssortmentTags: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
159
+ bulkSetAssortmentActive: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
160
+ bulkRemoveFilters: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
161
+ bulkSetFilterActive: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
162
+ bulkRemoveUsers: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
163
+ bulkSetUserRoles: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
152
164
  };
153
165
  export default _default;
@@ -150,6 +150,18 @@ import updateCartDeliveryPickUp from "./orders/updateCartDeliveryPickUp.js";
150
150
  import updateCartDeliveryShipping from "./orders/updateCartDeliveryShipping.js";
151
151
  import updateCartPaymentGeneric from "./orders/updateCartPaymentGeneric.js";
152
152
  import updateCartPaymentInvoice from "./orders/updateCartPaymentInvoice.js";
153
+ import bulkSetProductStatus from "./bulk/bulkSetProductStatus.js";
154
+ import bulkUpdateProductTags from "./bulk/bulkUpdateProductTags.js";
155
+ import bulkAssignProductsToAssortment from "./bulk/bulkAssignProductsToAssortment.js";
156
+ import bulkRemoveProducts from "./bulk/bulkRemoveProducts.js";
157
+ import bulkUpdateUserTags from "./bulk/bulkUpdateUserTags.js";
158
+ import bulkRemoveAssortments from "./bulk/bulkRemoveAssortments.js";
159
+ import bulkUpdateAssortmentTags from "./bulk/bulkUpdateAssortmentTags.js";
160
+ import bulkSetAssortmentActive from "./bulk/bulkSetAssortmentActive.js";
161
+ import bulkRemoveFilters from "./bulk/bulkRemoveFilters.js";
162
+ import bulkSetFilterActive from "./bulk/bulkSetFilterActive.js";
163
+ import bulkRemoveUsers from "./bulk/bulkRemoveUsers.js";
164
+ import bulkSetUserRoles from "./bulk/bulkSetUserRoles.js";
153
165
  export default {
154
166
  logout: acl(actions.logout)(logout),
155
167
  logoutAllSessions: acl(actions.logoutAllSessions)(logoutAllSessions),
@@ -179,7 +191,7 @@ export default {
179
191
  addEmail: acl(actions.updateUser)(addEmail),
180
192
  removeEmail: acl(actions.updateUser)(removeEmail),
181
193
  updateUserProfile: acl(actions.updateUser)(updateUserProfile),
182
- removeUser: acl(actions.updateUser)(removeUser),
194
+ removeUser: acl(actions.removeUser)(removeUser),
183
195
  setPassword: acl(actions.updateUser)(setPassword),
184
196
  prepareUserAvatarUpload: acl(actions.uploadUserAvatar)(prepareUserAvatarUpload),
185
197
  setUserTags: acl(actions.manageUsers)(setUserTags),
@@ -301,4 +313,16 @@ export default {
301
313
  signPaymentProviderForCredentialRegistration: acl(actions.registerPaymentCredentials)(signPaymentProviderForCredentialRegistration),
302
314
  signPaymentProviderForCheckout: acl(actions.updateOrderPayment)(signPaymentProviderForCheckout),
303
315
  removeUserProductReviews: acl(actions.updateUser)(removeUserProductReviews),
316
+ bulkSetProductStatus: acl(actions.bulkSetProductStatus)(bulkSetProductStatus),
317
+ bulkUpdateProductTags: acl(actions.bulkUpdateProductTags)(bulkUpdateProductTags),
318
+ bulkAssignProductsToAssortment: acl(actions.bulkAssignProductsToAssortment)(bulkAssignProductsToAssortment),
319
+ bulkRemoveProducts: acl(actions.bulkRemoveProducts)(bulkRemoveProducts),
320
+ bulkUpdateUserTags: acl(actions.bulkUpdateUserTags)(bulkUpdateUserTags),
321
+ bulkRemoveAssortments: acl(actions.bulkRemoveAssortments)(bulkRemoveAssortments),
322
+ bulkUpdateAssortmentTags: acl(actions.bulkUpdateAssortmentTags)(bulkUpdateAssortmentTags),
323
+ bulkSetAssortmentActive: acl(actions.bulkSetAssortmentActive)(bulkSetAssortmentActive),
324
+ bulkRemoveFilters: acl(actions.bulkRemoveFilters)(bulkRemoveFilters),
325
+ bulkSetFilterActive: acl(actions.bulkSetFilterActive)(bulkSetFilterActive),
326
+ bulkRemoveUsers: acl(actions.bulkRemoveUsers)(bulkRemoveUsers),
327
+ bulkSetUserRoles: acl(actions.bulkSetUserRoles)(bulkSetUserRoles),
304
328
  };
@@ -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,5 +1,5 @@
1
1
  import { log } from '@unchainedshop/logger';
2
- import { InvalidIdError, UserNotFoundError } from "../../../errors.js";
2
+ import { InvalidIdError, LastAdminError, UserNotFoundError } from "../../../errors.js";
3
3
  import { getPublicRoles } from "../../../roles/index.js";
4
4
  export default async function setRoles(root, params, context) {
5
5
  const { modules, userId } = context;
@@ -14,5 +14,12 @@ export default async function setRoles(root, params, context) {
14
14
  if (invalidRoles.length) {
15
15
  throw new Error(`Invalid role names: ${invalidRoles.join(', ')}`);
16
16
  }
17
- return modules.users.updateRoles(foreignUserId, params.roles);
17
+ try {
18
+ return await modules.users.updateRoles(foreignUserId, params.roles);
19
+ }
20
+ catch (e) {
21
+ if (e.cause === 'LAST_ADMIN')
22
+ throw new LastAdminError({ userId: foreignUserId });
23
+ throw e;
24
+ }
18
25
  }
@@ -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
  }
@@ -56,6 +56,7 @@ declare const _default: {
56
56
  quotation: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
57
57
  quotations: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
58
58
  quotationsCount: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
59
+ globalSearch: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
59
60
  searchProducts: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
60
61
  searchAssortments: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
61
62
  workQueue: (root: any, params: any, context: any, ...other: any[]) => Promise<any>;
@@ -44,6 +44,7 @@ import productsCount from "./products/productsCount.js";
44
44
  import quotation from "./quotations/quotation.js";
45
45
  import quotations from "./quotations/quotations.js";
46
46
  import quotationsCount from "./quotations/quotationsCount.js";
47
+ import globalSearch from "./search/globalSearch.js";
47
48
  import searchAssortments from "./filters/searchAssortments.js";
48
49
  import searchProducts from "./filters/searchProducts.js";
49
50
  import shopInfo from "./shopInfo.js";
@@ -129,6 +130,7 @@ export default {
129
130
  quotation: acl(actions.viewQuotation)(quotation),
130
131
  quotations: acl(actions.viewQuotations)(quotations),
131
132
  quotationsCount: acl(actions.viewQuotations)(quotationsCount),
133
+ globalSearch: acl(actions.search)(globalSearch),
132
134
  searchProducts: acl(actions.search)(searchProducts),
133
135
  searchAssortments: acl(actions.search)(searchAssortments),
134
136
  workQueue: acl(actions.viewWorkQueue)(workQueue),
@@ -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 {};