VYPR
High severityNVD Advisory· Published Jan 13, 2024· Updated Jun 3, 2025

CVE-2023-46942

CVE-2023-46942

Description

Lack of authentication in NPM's package @evershop/evershop before version 1.0.0-rc.8, allows remote attackers to obtain sensitive information via improper authorization in GraphQL endpoints.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Improper authorization in GraphQL endpoints in @evershop/evershop before 1.0.0-rc.8 allows remote attackers to obtain sensitive information.

The vulnerability is a lack of authentication in GraphQL endpoints in the NPM package @evershop/evershop before version 1.0.0-rc.8 [1]. This improper authorization allows unauthenticated remote attackers to access sensitive information [1][4].

An attacker can exploit this vulnerability by sending GraphQL queries to the application without any authentication, bypassing security controls [4]. No special network access is required; the attacker only needs to reach the exposed GraphQL endpoints.

Successful exploitation leads to exposure of potentially sensitive data, including customer information or internal application details [1][4]. The impact is information disclosure, which could facilitate further attacks or privacy violations.

The issue is fixed in version 1.0.0-rc.8 and later [2]. Users are advised to upgrade or apply appropriate access controls to GraphQL endpoints if upgrading is not immediately possible.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@evershop/evershopnpm
< 1.0.0-rc.91.0.0-rc.9

Affected products

2

Patches

1
6e16f046e0b9

Merge pull request #342 from evershopcommerce/dev

91 files changed · +1372 981
  • packages/evershop/bin/lib/bootstrap/migrate.js+13 14 modified
    @@ -8,26 +8,27 @@ const {
       rollback
     } = require('@evershop/postgres-query-builder');
     const {
    -  getConnection
    +  getConnection,
    +  pool
     } = require('@evershop/evershop/src/lib/postgres/connection');
     const { existsSync, readdirSync } = require('fs');
     const { error } = require('@evershop/evershop/src/lib/log/debuger');
     const { createMigrationTable } = require('../../install/createMigrationTable');
     
    -async function getCurrentInstalledVersion(module, connection) {
    +async function getCurrentInstalledVersion(module) {
       /** Check for current installed version */
       const check = await select()
         .from('migration')
         .where('module', '=', module)
    -    .load(connection);
    +    .load(pool);
       if (!check) {
         return '0.0.1';
       } else {
         return check.version;
       }
     }
     
    -async function migrateModule(module, connection) {
    +async function migrateModule(module) {
       /** Check if the module has migration folder, if not ignore it */
       if (!existsSync(path.resolve(module.path, 'migration'))) {
         return;
    @@ -42,17 +43,16 @@ async function migrateModule(module, connection) {
         )
         .map((dirent) => dirent.name.replace('Version-', '').replace('.js', ''))
         .sort((first, second) => semver.lt(first, second));
    -  const currentInstalledVersion = await getCurrentInstalledVersion(
    -    module.name,
    -    connection
    -  );
    +
    +  const currentInstalledVersion = await getCurrentInstalledVersion(module.name);
       // eslint-disable-next-line no-restricted-syntax
       for (const version of migrations) {
         /** If the version is lower or equal the installed version, ignore it */
         if (semver.lte(version, currentInstalledVersion)) {
           continue;
         }
    -
    +    const connection = await getConnection();
    +    await startTransaction(connection);
         // eslint-disable-next-line no-await-in-loop
         // eslint-disable-next-line global-require
         /** We expect the migration script to provide a function as a default export */
    @@ -69,7 +69,9 @@ async function migrateModule(module, connection) {
               version
             })
             .execute(connection, false);
    +      await commit(connection);
         } catch (e) {
    +      await rollback(connection);
           throw new Error(
             `Migration failed for module ${module.name}, version ${version}\n${e}`
           );
    @@ -78,18 +80,15 @@ async function migrateModule(module, connection) {
     }
     
     module.exports.migrate = async function migrate(modules) {
    -  const connection = await getConnection();
    -  await startTransaction(connection);
       try {
    +    const connection = await getConnection();
         // Create a migration table if not exists. This is for the first time installation
         await createMigrationTable(connection);
         // eslint-disable-next-line no-restricted-syntax
         for (const module of modules) {
    -      await migrateModule(module, connection);
    +      await migrateModule(module);
         }
    -    await commit(connection);
       } catch (e) {
    -    await rollback(connection);
         error(e);
         process.exit(0);
       }
    
  • packages/evershop/src/components/admin/oms/orderEdit/Shipment.jsx+1 1 modified
    @@ -224,7 +224,7 @@ Shipment.propTypes = {
     
     export const layout = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           orderId
           shippingNote
           shippingMethod
    
  • packages/evershop/src/lib/webpack/createBaseConfig.js+4 8 modified
    @@ -155,18 +155,16 @@ module.exports.createBaseConfig = function createBaseConfig(isServer) {
       };
     
       config.optimization = {};
    +
    +  // Check if the flag --skip-minify is set
    +  const skipMinify = process.argv.includes('--skip-minify');
       if (isProductionMode()) {
         config.optimization = Object.assign(config.optimization, {
    -      minimize: true,
    +      minimize: !skipMinify,
           minimizer: [
             new TerserPlugin({
               terserOptions: {
                 parse: {
    -              // We want uglify-js to parse ecma 8 code. However, we don't want it
    -              // to apply any minification steps that turns valid ecma 5 code
    -              // into invalid ecma 5 code. This is why the 'compress' and 'output'
    -              // sections only apply transformations that are ecma 5 safe
    -              // https://github.com/facebook/create-react-app/pull/4234
                   ecma: 2020
                 },
                 compress: false,
    @@ -176,8 +174,6 @@ module.exports.createBaseConfig = function createBaseConfig(isServer) {
                 output: {
                   ecma: 5,
                   comments: false,
    -              // Turned on because emoji and regex is not minified properly using
    -              // default. See https://github.com/facebook/create-react-app/issues/2488
                   ascii_only: true
                 }
               }
    
  • packages/evershop/src/modules/auth/graphql/types/AdminUser/AdminUser.admin.graphql+0 0 renamed
  • packages/evershop/src/modules/auth/graphql/types/AdminUser/AdminUser.admin.resolvers.js+2 9 renamed
    @@ -3,22 +3,15 @@ const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     
     module.exports = {
       Query: {
    -    adminUser: async (root, { id }, { pool, user }) => {
    -      if (!user) {
    -        return null;
    -      }
    +    adminUser: async (root, { id }, { pool }) => {
           const query = select().from('admin_user');
           query.where('admin_user_id', '=', id);
     
           const adminUser = await query.load(pool);
           return adminUser ? camelCase(adminUser) : null;
         },
         currentAdminUser: (root, args, { user }) => (user ? camelCase(user) : null),
    -    adminUsers: async (_, { filters = [] }, { pool, user }) => {
    -      // This field is for admin only
    -      if (!user) {
    -        return [];
    -      }
    +    adminUsers: async (_, { filters = [] }, { pool }) => {
           const query = select().from('admin_user');
           const currentFilters = [];
     
    
  • packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql+42 0 added
    @@ -0,0 +1,42 @@
    +"""
    +Represents a single attribute group
    +"""
    +type AttributeGroup {
    +  attributeGroupId: ID!
    +  uuid: String!
    +  groupName: String!
    +  updateApi: String!
    +  attributes: [Attribute]
    +}
    +
    +extend type Attribute {
    +  groups: [AttributeGroup]
    +  editUrl: String!
    +  updateApi: String!
    +  deleteApi: String!
    +}
    +
    +"""
    +Represents a collection of attributes
    +"""
    +type AttributeCollection {
    +  items: [Attribute]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
    +"""
    +Represents a collection of attribute groups
    +"""
    +type AttributeGroupCollection {
    +  items: [AttributeGroup]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
    +extend type Query {
    +  attributes(filters: [FilterInput]): AttributeCollection
    +  attributeGroups(filters: [FilterInput]): AttributeGroupCollection
    +}
    
  • packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js+296 0 added
    @@ -0,0 +1,296 @@
    +/* eslint-disable no-param-reassign */
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +
    +module.exports = {
    +  Query: {
    +    attributes: async (_, { filters: requestedFilters = [] }, { pool }) => {
    +      const query = select().from('attribute');
    +
    +      const currentFilters = [];
    +
    +      const filters = requestedFilters.map((filter) => {
    +        if (filter.operation.toUpperCase() === 'LIKE') {
    +          filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
    +        } else {
    +          filter.valueRaw = filter.value;
    +        }
    +        if (filter.operation.toUpperCase() === 'IN') {
    +          filter.value = filter.value.split(',');
    +        }
    +        return filter;
    +      });
    +
    +      // Name filter
    +      const nameFilter = filters.find((f) => f.key === 'name');
    +      if (nameFilter) {
    +        query.andWhere(
    +          'attribute.attribute_name',
    +          'LIKE',
    +          `%${nameFilter.value}%`
    +        );
    +        currentFilters.push({
    +          key: 'name',
    +          operation: '=',
    +          value: nameFilter.value
    +        });
    +      }
    +
    +      // Code filter
    +      const codeFilter = filters.find((f) => f.key === 'code');
    +      if (codeFilter) {
    +        query.andWhere(
    +          'attribute.attribute_code',
    +          codeFilter.operation,
    +          codeFilter.value
    +        );
    +        currentFilters.push({
    +          key: 'code',
    +          operation: codeFilter.operation,
    +          value: codeFilter.valueRaw
    +        });
    +      }
    +
    +      // Attribute group filter
    +      const groupFilter = filters.find((f) => f.key === 'group');
    +      if (groupFilter) {
    +        const attributes = await select()
    +          .from('attribute_group_link')
    +          .where('group_id', groupFilter.operation, groupFilter.value)
    +          .execute(pool);
    +
    +        query.andWhere(
    +          'attribute.attribute_id',
    +          'IN',
    +          attributes.map((a) => a.attribute_id)
    +        );
    +        currentFilters.push({
    +          key: 'group',
    +          operation: groupFilter.operation,
    +          value: groupFilter.valueRaw
    +        });
    +      }
    +
    +      // Type filter
    +      const typeFilter = filters.find((f) => f.key === 'type');
    +      if (typeFilter) {
    +        query.andWhere(
    +          'attribute.type',
    +          typeFilter.operation,
    +          typeFilter.value
    +        );
    +        currentFilters.push({
    +          key: 'type',
    +          operation: typeFilter.operation,
    +          value: typeFilter.valueRaw
    +        });
    +      }
    +
    +      // isRequired filter
    +      const isRequiredFilter = filters.find((f) => f.key === 'isRequired');
    +      if (isRequiredFilter) {
    +        query.andWhere(
    +          'attribute.is_required',
    +          isRequiredFilter.operation,
    +          isRequiredFilter.value
    +        );
    +        currentFilters.push({
    +          key: 'isRequired',
    +          operation: isRequiredFilter.operation,
    +          value: isRequiredFilter.valueRaw
    +        });
    +      }
    +
    +      // isFilterable filter
    +      const isFilterableFilter = filters.find((f) => f.key === 'isFilterable');
    +      if (isFilterableFilter) {
    +        query.andWhere(
    +          'attribute.is_filterable',
    +          isFilterableFilter.operation,
    +          isFilterableFilter.value
    +        );
    +        currentFilters.push({
    +          key: 'isFilterable',
    +          operation: isFilterableFilter.operation,
    +          value: isFilterableFilter.valueRaw
    +        });
    +      }
    +
    +      const sortBy = filters.find((f) => f.key === 'sortBy');
    +      const sortOrder = filters.find(
    +        (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
    +      ) || { value: 'ASC' };
    +      if (sortBy && sortBy.value === 'name') {
    +        query.orderBy('attribute.attribute_name', sortOrder.value);
    +        currentFilters.push({
    +          key: 'sortBy',
    +          operation: '=',
    +          value: sortBy.value
    +        });
    +      } else {
    +        query.orderBy('attribute.attribute_id', 'DESC');
    +      }
    +      if (sortOrder.key) {
    +        currentFilters.push({
    +          key: 'sortOrder',
    +          operation: '=',
    +          value: sortOrder.value
    +        });
    +      }
    +      // Clone the main query for getting total right before doing the paging
    +      const cloneQuery = query.clone();
    +      cloneQuery.select('COUNT(*)', 'total');
    +      cloneQuery.removeOrderBy();
    +      // Paging
    +      const page = filters.find((f) => f.key === 'page') || { value: 1 };
    +      const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from config
    +      currentFilters.push({
    +        key: 'page',
    +        operation: '=',
    +        value: page.value
    +      });
    +      currentFilters.push({
    +        key: 'limit',
    +        operation: '=',
    +        value: limit.value
    +      });
    +      query.limit(
    +        (page.value - 1) * parseInt(limit.value, 10),
    +        parseInt(limit.value, 10)
    +      );
    +      return {
    +        items: (await query.execute(pool)).map((row) => camelCase(row)),
    +        total: (await cloneQuery.load(pool)).total,
    +        currentFilters
    +      };
    +    },
    +    attributeGroups: async (
    +      _,
    +      { filters: requestedFilters = [] },
    +      { pool }
    +    ) => {
    +      const query = select().from('attribute_group');
    +
    +      const currentFilters = [];
    +
    +      const filters = requestedFilters.map((filter) => {
    +        if (filter.operation.toUpperCase() === 'LIKE') {
    +          filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
    +        } else {
    +          filter.valueRaw = filter.value;
    +        }
    +        if (filter.operation.toUpperCase() === 'IN') {
    +          filter.value = filter.value.split(',');
    +        }
    +        return filter;
    +      });
    +
    +      // Name filter
    +      const nameFilter = filters.find((f) => f.key === 'name');
    +      if (nameFilter) {
    +        query.andWhere(
    +          'attribute_group.group_name',
    +          'LIKE',
    +          `%${nameFilter.value}%`
    +        );
    +        currentFilters.push({
    +          key: 'name',
    +          operation: '=',
    +          value: nameFilter.value
    +        });
    +      }
    +
    +      const sortBy = filters.find((f) => f.key === 'sortBy');
    +      const sortOrder = filters.find(
    +        (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
    +      ) || { value: 'ASC' };
    +      if (sortBy && sortBy.value === 'name') {
    +        query.orderBy('attribute_group.group_name', sortOrder.value);
    +        currentFilters.push({
    +          key: 'sortBy',
    +          operation: '=',
    +          value: sortBy.value
    +        });
    +      } else {
    +        query.orderBy('attribute_group.attribute_group_id', 'DESC');
    +      }
    +      if (sortOrder.key) {
    +        currentFilters.push({
    +          key: 'sortOrder',
    +          operation: '=',
    +          value: sortOrder.value
    +        });
    +      }
    +      // Clone the main query for getting total right before doing the paging
    +      const cloneQuery = query.clone();
    +      cloneQuery.select('COUNT(*)', 'total');
    +      cloneQuery.removeOrderBy();
    +      // Paging
    +      const page = filters.find((f) => f.key === 'page') || { value: 1 };
    +      const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from config
    +      currentFilters.push({
    +        key: 'page',
    +        operation: '=',
    +        value: page.value
    +      });
    +      currentFilters.push({
    +        key: 'limit',
    +        operation: '=',
    +        value: limit.value
    +      });
    +      query.limit(
    +        (page.value - 1) * parseInt(limit.value, 10),
    +        parseInt(limit.value, 10)
    +      );
    +      return {
    +        items: (await query.execute(pool)).map((row) => camelCase(row)),
    +        total: (await cloneQuery.load(pool)).total,
    +        currentFilters
    +      };
    +    }
    +  },
    +  AttributeGroup: {
    +    attributes: async (group, _, { pool }) => {
    +      const rows = await select()
    +        .from('attribute')
    +        .where(
    +          'attribute_id',
    +          'IN',
    +          (
    +            await select('attribute_id')
    +              .from('attribute_group_link')
    +              .where('group_id', '=', group.attributeGroupId)
    +              .execute(pool)
    +          ).map((a) => a.attribute_id)
    +        )
    +        .execute(pool);
    +      return rows.map((row) => camelCase(row));
    +    },
    +    updateApi: (group) => buildUrl('updateAttributeGroup', { id: group.uuid })
    +  },
    +
    +  Attribute: {
    +    groups: async (attribute, _, { pool }) => {
    +      const results = await select()
    +        .from('attribute_group')
    +        .where(
    +          'attribute_group_id',
    +          'IN',
    +          (
    +            await select('group_id')
    +              .from('attribute_group_link')
    +              .where('attribute_id', '=', attribute.attributeId)
    +              .execute(pool)
    +          ).map((g) => g.group_id)
    +        )
    +        .execute(pool);
    +      return results.map((result) => camelCase(result));
    +    },
    +    editUrl: ({ uuid }) => buildUrl('attributeEdit', { id: uuid }),
    +    updateApi: (attribute) =>
    +      buildUrl('updateAttribute', { id: attribute.uuid }),
    +    deleteApi: (attribute) =>
    +      buildUrl('deleteAttribute', { id: attribute.uuid })
    +  }
    +};
    
  • packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.graphql+0 37 modified
    @@ -1,14 +1,3 @@
    -"""
    -Represents a single attribute group
    -"""
    -type AttributeGroup {
    -  attributeGroupId: ID!
    -  uuid: String!
    -  groupName: String!
    -  updateApi: String!
    -  attributes: [Attribute]
    -}
    -
     """
     Represents a single attribute option
     """
    @@ -32,34 +21,8 @@ type Attribute {
       sortOrder: Int!
       isFilterable: Int!
       options: [AttributeOption]
    -  groups: [AttributeGroup]
    -  editUrl: String!
    -  updateApi: String!
    -  deleteApi: String!
    -}
    -
    -"""
    -Represents a collection of attributes
    -"""
    -type AttributeCollection {
    -  items: [Attribute]
    -  currentPage: Int!
    -  total: Int!
    -  currentFilters: [Filter]
    -}
    -
    -"""
    -Represents a collection of attribute groups
    -"""
    -type AttributeGroupCollection {
    -  items: [AttributeGroup]
    -  currentPage: Int!
    -  total: Int!
    -  currentFilters: [Filter]
     }
     
     extend type Query {
       attribute(id: Int): Attribute
    -  attributes(filters: [FilterInput]): AttributeCollection
    -  attributeGroups(filters: [FilterInput]): AttributeGroupCollection
     }
    
  • packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.resolvers.js+1 287 modified
    @@ -1,6 +1,5 @@
     /* eslint-disable no-param-reassign */
     const { select } = require('@evershop/postgres-query-builder');
    -const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     
     module.exports = {
    @@ -15,300 +14,15 @@ module.exports = {
           } else {
             return camelCase(attribute);
           }
    -    },
    -    attributes: async (_, { filters: requestedFilters = [] }, { pool }) => {
    -      const query = select().from('attribute');
    -
    -      const currentFilters = [];
    -
    -      const filters = requestedFilters.map((filter) => {
    -        if (filter.operation.toUpperCase() === 'LIKE') {
    -          filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
    -        } else {
    -          filter.valueRaw = filter.value;
    -        }
    -        if (filter.operation.toUpperCase() === 'IN') {
    -          filter.value = filter.value.split(',');
    -        }
    -        return filter;
    -      });
    -
    -      // Name filter
    -      const nameFilter = filters.find((f) => f.key === 'name');
    -      if (nameFilter) {
    -        query.andWhere(
    -          'attribute.attribute_name',
    -          'LIKE',
    -          `%${nameFilter.value}%`
    -        );
    -        currentFilters.push({
    -          key: 'name',
    -          operation: '=',
    -          value: nameFilter.value
    -        });
    -      }
    -
    -      // Code filter
    -      const codeFilter = filters.find((f) => f.key === 'code');
    -      if (codeFilter) {
    -        query.andWhere(
    -          'attribute.attribute_code',
    -          codeFilter.operation,
    -          codeFilter.value
    -        );
    -        currentFilters.push({
    -          key: 'code',
    -          operation: codeFilter.operation,
    -          value: codeFilter.valueRaw
    -        });
    -      }
    -
    -      // Attribute group filter
    -      const groupFilter = filters.find((f) => f.key === 'group');
    -      if (groupFilter) {
    -        const attributes = await select()
    -          .from('attribute_group_link')
    -          .where('group_id', groupFilter.operation, groupFilter.value)
    -          .execute(pool);
    -
    -        query.andWhere(
    -          'attribute.attribute_id',
    -          'IN',
    -          attributes.map((a) => a.attribute_id)
    -        );
    -        currentFilters.push({
    -          key: 'group',
    -          operation: groupFilter.operation,
    -          value: groupFilter.valueRaw
    -        });
    -      }
    -
    -      // Type filter
    -      const typeFilter = filters.find((f) => f.key === 'type');
    -      if (typeFilter) {
    -        query.andWhere(
    -          'attribute.type',
    -          typeFilter.operation,
    -          typeFilter.value
    -        );
    -        currentFilters.push({
    -          key: 'type',
    -          operation: typeFilter.operation,
    -          value: typeFilter.valueRaw
    -        });
    -      }
    -
    -      // isRequired filter
    -      const isRequiredFilter = filters.find((f) => f.key === 'isRequired');
    -      if (isRequiredFilter) {
    -        query.andWhere(
    -          'attribute.is_required',
    -          isRequiredFilter.operation,
    -          isRequiredFilter.value
    -        );
    -        currentFilters.push({
    -          key: 'isRequired',
    -          operation: isRequiredFilter.operation,
    -          value: isRequiredFilter.valueRaw
    -        });
    -      }
    -
    -      // isFilterable filter
    -      const isFilterableFilter = filters.find((f) => f.key === 'isFilterable');
    -      if (isFilterableFilter) {
    -        query.andWhere(
    -          'attribute.is_filterable',
    -          isFilterableFilter.operation,
    -          isFilterableFilter.value
    -        );
    -        currentFilters.push({
    -          key: 'isFilterable',
    -          operation: isFilterableFilter.operation,
    -          value: isFilterableFilter.valueRaw
    -        });
    -      }
    -
    -      const sortBy = filters.find((f) => f.key === 'sortBy');
    -      const sortOrder = filters.find(
    -        (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
    -      ) || { value: 'ASC' };
    -      if (sortBy && sortBy.value === 'name') {
    -        query.orderBy('attribute.attribute_name', sortOrder.value);
    -        currentFilters.push({
    -          key: 'sortBy',
    -          operation: '=',
    -          value: sortBy.value
    -        });
    -      } else {
    -        query.orderBy('attribute.attribute_id', 'DESC');
    -      }
    -      if (sortOrder.key) {
    -        currentFilters.push({
    -          key: 'sortOrder',
    -          operation: '=',
    -          value: sortOrder.value
    -        });
    -      }
    -      // Clone the main query for getting total right before doing the paging
    -      const cloneQuery = query.clone();
    -      cloneQuery.select('COUNT(*)', 'total');
    -      cloneQuery.removeOrderBy();
    -      // Paging
    -      const page = filters.find((f) => f.key === 'page') || { value: 1 };
    -      const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from config
    -      currentFilters.push({
    -        key: 'page',
    -        operation: '=',
    -        value: page.value
    -      });
    -      currentFilters.push({
    -        key: 'limit',
    -        operation: '=',
    -        value: limit.value
    -      });
    -      query.limit(
    -        (page.value - 1) * parseInt(limit.value, 10),
    -        parseInt(limit.value, 10)
    -      );
    -      return {
    -        items: (await query.execute(pool)).map((row) => camelCase(row)),
    -        total: (await cloneQuery.load(pool)).total,
    -        currentFilters
    -      };
    -    },
    -    attributeGroups: async (
    -      _,
    -      { filters: requestedFilters = [] },
    -      { pool }
    -    ) => {
    -      const query = select().from('attribute_group');
    -
    -      const currentFilters = [];
    -
    -      const filters = requestedFilters.map((filter) => {
    -        if (filter.operation.toUpperCase() === 'LIKE') {
    -          filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
    -        } else {
    -          filter.valueRaw = filter.value;
    -        }
    -        if (filter.operation.toUpperCase() === 'IN') {
    -          filter.value = filter.value.split(',');
    -        }
    -        return filter;
    -      });
    -
    -      // Name filter
    -      const nameFilter = filters.find((f) => f.key === 'name');
    -      if (nameFilter) {
    -        query.andWhere(
    -          'attribute_group.group_name',
    -          'LIKE',
    -          `%${nameFilter.value}%`
    -        );
    -        currentFilters.push({
    -          key: 'name',
    -          operation: '=',
    -          value: nameFilter.value
    -        });
    -      }
    -
    -      const sortBy = filters.find((f) => f.key === 'sortBy');
    -      const sortOrder = filters.find(
    -        (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
    -      ) || { value: 'ASC' };
    -      if (sortBy && sortBy.value === 'name') {
    -        query.orderBy('attribute_group.group_name', sortOrder.value);
    -        currentFilters.push({
    -          key: 'sortBy',
    -          operation: '=',
    -          value: sortBy.value
    -        });
    -      } else {
    -        query.orderBy('attribute_group.attribute_group_id', 'DESC');
    -      }
    -      if (sortOrder.key) {
    -        currentFilters.push({
    -          key: 'sortOrder',
    -          operation: '=',
    -          value: sortOrder.value
    -        });
    -      }
    -      // Clone the main query for getting total right before doing the paging
    -      const cloneQuery = query.clone();
    -      cloneQuery.select('COUNT(*)', 'total');
    -      cloneQuery.removeOrderBy();
    -      // Paging
    -      const page = filters.find((f) => f.key === 'page') || { value: 1 };
    -      const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from config
    -      currentFilters.push({
    -        key: 'page',
    -        operation: '=',
    -        value: page.value
    -      });
    -      currentFilters.push({
    -        key: 'limit',
    -        operation: '=',
    -        value: limit.value
    -      });
    -      query.limit(
    -        (page.value - 1) * parseInt(limit.value, 10),
    -        parseInt(limit.value, 10)
    -      );
    -      return {
    -        items: (await query.execute(pool)).map((row) => camelCase(row)),
    -        total: (await cloneQuery.load(pool)).total,
    -        currentFilters
    -      };
         }
       },
    -  AttributeGroup: {
    -    attributes: async (group, _, { pool }) => {
    -      const rows = await select()
    -        .from('attribute')
    -        .where(
    -          'attribute_id',
    -          'IN',
    -          (
    -            await select('attribute_id')
    -              .from('attribute_group_link')
    -              .where('group_id', '=', group.attributeGroupId)
    -              .execute(pool)
    -          ).map((a) => a.attribute_id)
    -        )
    -        .execute(pool);
    -      return rows.map((row) => camelCase(row));
    -    },
    -    updateApi: (group) => buildUrl('updateAttributeGroup', { id: group.uuid })
    -  },
    -
       Attribute: {
    -    groups: async (attribute, _, { pool }) => {
    -      const results = await select()
    -        .from('attribute_group')
    -        .where(
    -          'attribute_group_id',
    -          'IN',
    -          (
    -            await select('group_id')
    -              .from('attribute_group_link')
    -              .where('attribute_id', '=', attribute.attributeId)
    -              .execute(pool)
    -          ).map((g) => g.group_id)
    -        )
    -        .execute(pool);
    -      return results.map((result) => camelCase(result));
    -    },
         options: async (attribute, _, { pool }) => {
           const results = await select()
             .from('attribute_option')
             .where('attribute_id', '=', attribute.attributeId)
             .execute(pool);
           return results.map((result) => camelCase(result));
    -    },
    -    editUrl: ({ uuid }) => buildUrl('attributeEdit', { id: uuid }),
    -    updateApi: (attribute) =>
    -      buildUrl('updateAttribute', { id: attribute.uuid }),
    -    deleteApi: (attribute) =>
    -      buildUrl('deleteAttribute', { id: attribute.uuid })
    +    }
       }
     };
    
  • packages/evershop/src/modules/catalog/graphql/types/Category/Category.admin.graphql+10 0 added
    @@ -0,0 +1,10 @@
    +extend type Category {
    +  editUrl: String
    +  updateApi: String!
    +  deleteApi: String!
    +  addProductUrl: String
    +}
    +
    +extend type Product {
    +  removeFromCategoryUrl: String
    +}
    \ No newline at end of file
    
  • packages/evershop/src/modules/catalog/graphql/types/Category/Category.admin.resolvers.js+28 0 added
    @@ -0,0 +1,28 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +
    +module.exports = {
    +  Category: {
    +    editUrl: (category) => buildUrl('categoryEdit', { id: category.uuid }),
    +    updateApi: (category) => buildUrl('updateCategory', { id: category.uuid }),
    +    deleteApi: (category) => buildUrl('deleteCategory', { id: category.uuid }),
    +    addProductUrl: (category) =>
    +      buildUrl('addProductToCategory', { category_id: category.uuid })
    +  },
    +  Product: {
    +    removeFromCategoryUrl: async (product, _, { pool }) => {
    +      if (!product.categoryId) {
    +        return null;
    +      } else {
    +        const category = await select()
    +          .from('category')
    +          .where('category_id', '=', product.categoryId)
    +          .load(pool);
    +        return buildUrl('removeProductFromCategory', {
    +          category_id: category.uuid,
    +          product_id: product.uuid
    +        });
    +      }
    +    }
    +  }
    +};
    
  • packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql+1 6 modified
    @@ -18,12 +18,8 @@ type Category {
       parent: Category
       path: [Category]
       url: String
    -  editUrl: String
    -  updateApi: String!
    -  deleteApi: String!
       availableAttributes: [FilterAttribute]
       priceRange: PriceRange
    -  addProductUrl: String
     }
     
     """
    @@ -86,8 +82,7 @@ type PriceRange {
     }
     
     extend type Product {
    -  collections: [Collection],
    -  removeFromCategoryUrl: String
    +  category: Category,
     }
     
     extend type Query {
    
  • packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.js+5 14 modified
    @@ -76,11 +76,6 @@ module.exports = {
             return urlRewrite.request_path;
           }
         },
    -    editUrl: (category) => buildUrl('categoryEdit', { id: category.uuid }),
    -    updateApi: (category) => buildUrl('updateCategory', { id: category.uuid }),
    -    deleteApi: (category) => buildUrl('deleteCategory', { id: category.uuid }),
    -    addProductUrl: (category) =>
    -      buildUrl('addProductToCategory', { category_id: category.uuid }),
         image: (category) => {
           const { image } = category;
           if (!image) {
    @@ -153,18 +148,14 @@ module.exports = {
         }
       },
       Product: {
    -    removeFromCategoryUrl: async (product, _, { pool }) => {
    +    category: async (product, _, { pool }) => {
           if (!product.categoryId) {
             return null;
           } else {
    -        const category = await select()
    -          .from('category')
    -          .where('category_id', '=', product.categoryId)
    -          .load(pool);
    -        return buildUrl('removeProductFromCategory', {
    -          category_id: category.uuid,
    -          product_id: product.uuid
    -        });
    +        const categoryQuery = getCategoriesBaseQuery();
    +        categoryQuery.where('category_id', '=', product.categoryId);
    +        const category = await categoryQuery.load(pool);
    +        return camelCase(category);
           }
         }
       }
    
  • packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.admin.graphql+10 0 added
    @@ -0,0 +1,10 @@
    +extend type Collection {
    +  editUrl: String
    +  addProductUrl: String
    +  updateApi: String!
    +  deleteApi: String!
    +}
    +
    +extend type Product {
    +  removeFromCollectionUrl: String
    +}
    
  • packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.admin.resolvers.js+31 0 added
    @@ -0,0 +1,31 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +
    +module.exports = {
    +  Collection: {
    +    editUrl: (collection) =>
    +      buildUrl('collectionEdit', { id: collection.uuid }),
    +    addProductUrl: (collection) =>
    +      buildUrl('addProductToCollection', { collection_id: collection.uuid }),
    +    updateApi: (collection) =>
    +      buildUrl('updateCollection', { id: collection.uuid }),
    +    deleteApi: (collection) =>
    +      buildUrl('deleteCollection', { id: collection.uuid })
    +  },
    +  Product: {
    +    removeFromCollectionUrl: async (product, _, { pool }) => {
    +      if (!product.collectionId) {
    +        return null;
    +      } else {
    +        const collection = await select()
    +          .from('collection')
    +          .where('collection_id', '=', product.collectionId)
    +          .load(pool);
    +        return buildUrl('removeProductFromCollection', {
    +          collection_id: collection.uuid,
    +          product_id: product.uuid
    +        });
    +      }
    +    }
    +  }
    +};
    
  • packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.graphql+2 7 modified
    @@ -1,5 +1,5 @@
     """
    -The `Collection` type represents a category object.
    +The `Collection` type represents a product collection.
     """
     type Collection {
       collectionId: ID!
    @@ -8,14 +8,10 @@ type Collection {
       description: String
       code: String!
       products(filters: [FilterInput]): ProductCollection
    -  editUrl: String
    -  addProductUrl: String
    -  updateApi: String!
    -  deleteApi: String!
     }
     
     """
    -Returns a collection of categories.
    +Returns a collection of product collection.
     """
     type CollectionCollection {
       items: [Collection]
    @@ -26,7 +22,6 @@ type CollectionCollection {
     
     extend type Product {
       collections: [Collection],
    -  removeFromCollectionUrl: String
     }
     
     extend type Query {
    
  • packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js+1 24 modified
    @@ -1,5 +1,4 @@
     const { select } = require('@evershop/postgres-query-builder');
    -const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     const { ProductCollection } = require('../../../services/ProductCollection');
     const {
    @@ -33,15 +32,7 @@ module.exports = {
           const root = new ProductCollection(query);
           await root.init(collection, { filters }, { user });
           return root;
    -    },
    -    editUrl: (collection) =>
    -      buildUrl('collectionEdit', { id: collection.uuid }),
    -    addProductUrl: (collection) =>
    -      buildUrl('addProductToCollection', { collection_id: collection.uuid }),
    -    updateApi: (collection) =>
    -      buildUrl('updateCollection', { id: collection.uuid }),
    -    deleteApi: (collection) =>
    -      buildUrl('deleteCollection', { id: collection.uuid })
    +    }
       },
       Product: {
         collections: async (product, _, { pool }) => {
    @@ -55,20 +46,6 @@ module.exports = {
             );
           query.where('product_id', '=', product.productId);
           return (await query.execute(pool)).map((row) => camelCase(row));
    -    },
    -    removeFromCollectionUrl: async (product, _, { pool }) => {
    -      if (!product.collectionId) {
    -        return null;
    -      } else {
    -        const collection = await select()
    -          .from('collection')
    -          .where('collection_id', '=', product.collectionId)
    -          .load(pool);
    -        return buildUrl('removeProductFromCollection', {
    -          collection_id: collection.uuid,
    -          product_id: product.uuid
    -        });
    -      }
         }
       }
     };
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.admin.graphql+3 0 added
    @@ -0,0 +1,3 @@
    +extend type Inventory {
    +  qty: Int!
    +}
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.admin.resolvers.js+5 0 added
    @@ -0,0 +1,5 @@
    +module.exports = {
    +  Inventory: {
    +    qty: (inventory) => inventory.qty || 0
    +  }
    +};
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Inventory/Inventory.graphql+0 1 modified
    @@ -2,7 +2,6 @@
     The `Inventory` type represents a product's inventory information.
     """
     type Inventory {
    -  qty: Int!
       isInStock: Boolean!
       stockAvailability: Int!
       manageStock: Int!
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Product.admin.graphql+5 0 added
    @@ -0,0 +1,5 @@
    +extend type Product {
    +  editUrl: String
    +  updateApi: String!
    +  deleteApi: String!
    +}
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Product.admin.resolvers.js+9 0 added
    @@ -0,0 +1,9 @@
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +
    +module.exports = {
    +  Product: {
    +    editUrl: (product) => buildUrl('productEdit', { id: product.uuid }),
    +    updateApi: (product) => buildUrl('updateProduct', { id: product.uuid }),
    +    deleteApi: (product) => buildUrl('deleteProduct', { id: product.uuid })
    +  }
    +};
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Product.graphql+1 5 modified
    @@ -1,5 +1,5 @@
     """
    -Look up for a Product
    +Represents a product.
     """
     type Product {
       productId: Int!
    @@ -17,11 +17,7 @@ type Product {
       variantGroupId: ID
       visibility: Int
       groupId: ID
    -  category: Category
       url: String
    -  editUrl: String
    -  updateApi: String!
    -  deleteApi: String!
     }
     
     """
    
  • packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.js+1 17 modified
    @@ -5,22 +5,9 @@ const {
       getProductsBaseQuery
     } = require('../../../services/getProductsBaseQuery');
     const { ProductCollection } = require('../../../services/ProductCollection');
    -const {
    -  getCategoriesBaseQuery
    -} = require('../../../services/getCategoriesBaseQuery');
     
     module.exports = {
       Product: {
    -    category: async (product, _, { pool }) => {
    -      const query = getCategoriesBaseQuery();
    -      query.where('category_id', '=', product.categoryId);
    -      const result = await query.load(pool);
    -      if (!result) {
    -        return null;
    -      } else {
    -        return camelCase(result);
    -      }
    -    },
         url: async (product, _, { pool }) => {
           // Get the url rewrite for this product
           const urlRewrite = await select()
    @@ -33,10 +20,7 @@ module.exports = {
           } else {
             return urlRewrite.request_path;
           }
    -    },
    -    editUrl: (product) => buildUrl('productEdit', { id: product.uuid }),
    -    updateApi: (product) => buildUrl('updateProduct', { id: product.uuid }),
    -    deleteApi: (product) => buildUrl('deleteProduct', { id: product.uuid })
    +    }
       },
       Query: {
         product: async (_, { id }, { pool }) => {
    
  • packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/General.jsx+18 11 modified
    @@ -163,7 +163,7 @@ export default function General({
       uploadApi,
       folderCreateApi,
       setting,
    -  taxClasses
    +  productTaxClasses: { items: taxClasses }
     }) {
       return (
         <Card title="General">
    @@ -278,16 +278,21 @@ General.propTypes = {
         storeCurrency: PropTypes.string,
         weightUnit: PropTypes.string
       }).isRequired,
    -  taxClasses: PropTypes.arrayOf(
    -    PropTypes.shape({
    -      value: PropTypes.number,
    -      text: PropTypes.string
    -    })
    -  ).isRequired
    +  productTaxClasses: PropTypes.shape({
    +    items: PropTypes.arrayOf(
    +      PropTypes.shape({
    +        value: PropTypes.number,
    +        text: PropTypes.string
    +      })
    +    )
    +  })
     };
     
     General.defaultProps = {
    -  product: undefined
    +  product: undefined,
    +  productTaxClasses: {
    +    items: []
    +  }
     };
     
     export const layout = {
    @@ -328,9 +333,11 @@ export const query = `
         deleteApi: url(routeId: "fileDelete", params: [{key: "0", value: ""}])
         uploadApi: url(routeId: "imageUpload", params: [{key: "0", value: ""}])
         folderCreateApi: url(routeId: "folderCreate")
    -    taxClasses {
    -      value: taxClassId
    -      text: name
    +    productTaxClasses: taxClasses {
    +      items {
    +        value: taxClassId
    +        text: name
    +      }
         }
       }
     `;
    
  • packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/CustomerInfo.jsx+1 1 modified
    @@ -130,7 +130,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order (id: getContextValue('orderId')) {
    +    order (uuid: getContextValue('orderId')) {
           orderNumber
           customerFullName
           customerEmail
    
  • packages/evershop/src/modules/checkout/pages/frontStore/checkoutSuccess/Summary.jsx+1 1 modified
    @@ -67,7 +67,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order (id: getContextValue('orderId')) {
    +    order (uuid: getContextValue('orderId')) {
           orderNumber
           discountAmount {
             value
    
  • packages/evershop/src/modules/cod/pages/admin/orderEdit/CaptureButton.jsx+1 1 modified
    @@ -60,7 +60,7 @@ export const layout = {
     export const query = `
       query Query {
         captureAPI: url(routeId: "codCapturePayment")
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           uuid
           paymentStatus {
             code
    
  • packages/evershop/src/modules/customer/api/deleteCustomer/route.json+1 1 modified
    @@ -1,5 +1,5 @@
     {
       "methods": ["DELETE"],
       "path": "/customers/:id",
    -  "access": "public"
    +  "access": "private"
     }
    
  • packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.graphql+20 0 added
    @@ -0,0 +1,20 @@
    +extend type Customer {
    +  editUrl: String!
    +  updateApi: String!
    +  deleteApi: String!
    +}
    +
    +"""
    +Return a collection of customers
    +"""
    +type CustomerCollection {
    +  items: [Customer]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
    +extend type Query {
    +  customer(id: String): Customer
    +  customers(filters: [FilterInput]): CustomerCollection
    +}
    
  • packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js+30 0 added
    @@ -0,0 +1,30 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +const {
    +  getCustomersBaseQuery
    +} = require('../../../services/getCustomersBaseQuery');
    +const { CustomerCollection } = require('../../../services/CustomerCollection');
    +
    +module.exports = {
    +  Query: {
    +    customer: async (root, { id }, { pool }) => {
    +      const query = select().from('customer');
    +      query.where('uuid', '=', id);
    +
    +      const customer = await query.load(pool);
    +      return customer ? camelCase(customer) : null;
    +    },
    +    customers: async (_, { filters = [] }) => {
    +      const query = getCustomersBaseQuery();
    +      const root = new CustomerCollection(query);
    +      await root.init({}, { filters });
    +      return root;
    +    }
    +  },
    +  Customer: {
    +    editUrl: ({ uuid }) => buildUrl('customerEdit', { id: uuid }),
    +    updateApi: (customer) => buildUrl('updateCustomer', { id: customer.uuid }),
    +    deleteApi: (customer) => buildUrl('deleteCustomer', { id: customer.uuid })
    +  }
    +};
    
  • packages/evershop/src/modules/customer/graphql/types/Customer/Customer.graphql+1 20 modified
    @@ -1,5 +1,5 @@
     """
    -Look up a customer by ID
    +Represents a customer
     """
     type Customer {
       customerId: Int!
    @@ -8,27 +8,8 @@ type Customer {
       email: String!
       fullName: String!
       createdAt: Date!
    -  url: String!
    -  editUrl: String!
    -  logoutApi: String!
    -  updateApi: String!
    -  deleteApi: String!
    -  group: CustomerGroup
    -  orders: [Order]
    -}
    -
    -"""
    -Return a collection of customers
    -"""
    -type CustomerCollection {
    -  items: [Customer]
    -  currentPage: Int!
    -  total: Int!
    -  currentFilters: [Filter]
     }
     
     extend type Query {
    -  customer(id: String): Customer
       currentCustomer: Customer
    -  customers(filters: [FilterInput]): CustomerCollection
     }
    
  • packages/evershop/src/modules/customer/graphql/types/Customer/Customer.resolvers.js+2 45 modified
    @@ -1,51 +1,8 @@
    -const { select } = require('@evershop/postgres-query-builder');
    -const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    -const {
    -  getCustomersBaseQuery
    -} = require('../../../services/getCustomersBaseQuery');
    -const { CustomerCollection } = require('../../../services/CustomerCollection');
     
     module.exports = {
       Query: {
    -    customer: async (root, { id }, { pool }) => {
    -      const query = select().from('customer');
    -      query.where('uuid', '=', id);
    -
    -      const customer = await query.load(pool);
    -      return customer ? camelCase(customer) : null;
    -    },
    -    currentCustomer: async (root, args, { customer }) => customer ? camelCase(customer) : null,
    -    customers: async (_, { filters = [] }, { user }) => {
    -      // This field is for admin only
    -      if (!user) {
    -        return [];
    -      }
    -      const query = getCustomersBaseQuery();
    -      const root = new CustomerCollection(query);
    -      await root.init({}, { filters });
    -      return root;
    -    }
    -  },
    -  Customer: {
    -    url: ({ urlKey }) => buildUrl('customerView', { url_key: urlKey }),
    -    editUrl: ({ uuid }) => buildUrl('customerEdit', { id: uuid }),
    -    logoutApi: ({ uuid }) => buildUrl('deleteCustomerSession', { id: uuid }),
    -    updateApi: (customer) => buildUrl('updateCustomer', { id: customer.uuid }),
    -    deleteApi: (customer) => buildUrl('deleteCustomer', { id: customer.uuid }),
    -    group: async ({ groupId }, _, { pool }) => {
    -      const group = await select()
    -        .from('customer_group')
    -        .where('customer_group.customer_group_id', '=', groupId)
    -        .load(pool);
    -      return group ? camelCase(group) : null;
    -    },
    -    orders: async ({ customerId }, _, { pool }) => {
    -      const orders = await select()
    -        .from('order')
    -        .where('order.customer_id', '=', customerId)
    -        .execute(pool);
    -      return orders.map((row) => camelCase(row));
    -    }
    +    currentCustomer: async (root, args, { customer }) =>
    +      customer ? camelCase(customer) : null
       }
     };
    
  • packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.admin.graphql+20 0 added
    @@ -0,0 +1,20 @@
    +extend type CustomerGroup {
    +  editUrl: String!
    +  customers: [Customer]
    +}
    +
    +"""
    +Represents a collection of customer groups
    +"""
    +type CustomerGroupCollection {
    +  items: [CustomerGroup]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
    +
    +extend type Query {
    +  customerGroup: CustomerGroup
    +  customerGroups: CustomerGroupCollection
    +}
    
  • packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.admin.resolvers.js+39 0 added
    @@ -0,0 +1,39 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +const {
    +  getCustomerGroupsBaseQuery
    +} = require('../../../services/getCustomerGroupsBaseQuery');
    +const {
    +  CustomerGroupCollection
    +} = require('../../../services/CustomerGroupCollection');
    +
    +module.exports = {
    +  Query: {
    +    customerGroup: async (root, { id }, { pool }) => {
    +      const group = await select()
    +        .from('customer_group')
    +        .where('customer_group.customer_group_id', '=', id)
    +        .load(pool);
    +
    +      return group ? camelCase(group) : null;
    +    },
    +    customerGroups: async (_, { filters = [] }) => {
    +      const query = getCustomerGroupsBaseQuery();
    +      const root = new CustomerGroupCollection(query);
    +      await root.init({}, { filters });
    +      return root;
    +    }
    +  },
    +  CustomerGroup: {
    +    customers: async (group, _, { pool }) => {
    +      const customers = await select()
    +        .from('customer')
    +        .where('customer.group_id', '=', group.customerGroupId)
    +        .execute(pool);
    +      return customers.map((customer) => camelCase(customer));
    +    },
    +    editUrl: (group) =>
    +      buildUrl('customerGroupEdit', { id: group.customerGroupId })
    +  }
    +};
    
  • packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.graphql+3 6 modified
    @@ -4,11 +4,8 @@ Represents a customer group.
     type CustomerGroup {
       customerGroupId: Int!
       groupName: String!
    -  editUrl: String!
    -  customers: [Customer]
     }
     
    -extend type Query {
    -  customerGroup: CustomerGroup
    -  customerGroups: [CustomerGroup]
    -}
    +extend type Customer {
    +  group: CustomerGroup
    +}
    \ No newline at end of file
    
  • packages/evershop/src/modules/customer/graphql/types/CustomerGroup/CustomerGroup.resolvers.js+3 26 modified
    @@ -1,37 +1,14 @@
     const { select } = require('@evershop/postgres-query-builder');
    -const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     
     module.exports = {
    -  Query: {
    -    customerGroup: async (root, { id }, { pool, user }) => {
    -      if (!user) {
    -        return null;
    -      }
    +  Customer: {
    +    group: async ({ groupId }, _, { pool }) => {
           const group = await select()
             .from('customer_group')
    -        .where('customer_group.customer_group_id', '=', id)
    +        .where('customer_group.customer_group_id', '=', groupId)
             .load(pool);
    -
           return group ? camelCase(group) : null;
    -    },
    -    customerGroups: async (root, _, { pool, user }) => {
    -      if (!user) {
    -        return [];
    -      }
    -      const groups = await select().from('customer_group').execute(pool);
    -      return groups.map((group) => camelCase(group));
         }
    -  },
    -  CustomerGroup: {
    -    customers: async (group, _, { pool }) => {
    -      const customers = await select()
    -        .from('customer')
    -        .where('customer.group_id', '=', group.customerGroupId)
    -        .execute(pool);
    -      return customers.map((customer) => camelCase(customer));
    -    },
    -    editUrl: (group) =>
    -      buildUrl('customerGroupEdit', { id: group.customerGroupId })
       }
     };
    
  • packages/evershop/src/modules/customer/pages/frontStore/checkout/CustomerInfoStep.jsx+6 6 modified
    @@ -10,7 +10,7 @@ import { _ } from '@evershop/evershop/src/lib/locale/translate';
     
     export default function ContactInformationStep({
       cart: { customerEmail, addContactInfoApi },
    -  customer,
    +  currentCustomer,
       loginUrl
     }) {
       const steps = useCheckoutSteps();
    @@ -28,7 +28,7 @@ export default function ContactInformationStep({
           isCompleted: !!customerEmail,
           preview: customerEmail || '',
           sortOrder: 5,
    -      editable: !customer
    +      editable: !currentCustomer
         });
       }, []);
     
    @@ -42,7 +42,7 @@ export default function ContactInformationStep({
         <div className="checkout-contact checkout-step">
           {display && (
             <Edit
    -          customer={customer}
    +          customer={currentCustomer}
               step={step}
               cartId={cartId}
               email={email}
    @@ -57,7 +57,7 @@ export default function ContactInformationStep({
     
     ContactInformationStep.propTypes = {
       loginUrl: PropTypes.string.isRequired,
    -  customer: PropTypes.shape({
    +  currentCustomer: PropTypes.shape({
         email: PropTypes.string.isRequired
       }),
       cart: PropTypes.shape({
    @@ -67,7 +67,7 @@ ContactInformationStep.propTypes = {
     };
     
     ContactInformationStep.defaultProps = {
    -  customer: null
    +  currentCustomer: null
     };
     
     export const layout = {
    @@ -81,7 +81,7 @@ export const query = `
           customerEmail
           addContactInfoApi
         }
    -    customer(id: getContextValue("customerId", null)) {
    +    currentCustomer {
           email
         }
         loginUrl: url(routeId: "login")
    
  • packages/evershop/src/modules/customer/services/CustomerGroupCollection.js+108 0 added
    @@ -0,0 +1,108 @@
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
    +
    +class CustomerGroupCollection {
    +  constructor(baseQuery) {
    +    this.baseQuery = baseQuery;
    +  }
    +
    +  async init(args, { filters = [] }) {
    +    const currentFilters = [];
    +
    +    // Name filter
    +    const nameFilter = filters.find((f) => f.key === 'group_name');
    +    if (nameFilter) {
    +      this.baseQuery.andWhere(
    +        'customer_group.group_name',
    +        'ILIKE',
    +        `%${nameFilter.value}%`
    +      );
    +      currentFilters.push({
    +        key: 'group_name',
    +        operation: '=',
    +        value: nameFilter.value
    +      });
    +    }
    +
    +    // Keyword search
    +    const keywordFilter = filters.find((f) => f.key === 'keyword');
    +    if (keywordFilter) {
    +      this.baseQuery.andWhere(
    +        'customer_group.group_name',
    +        'ILIKE',
    +        `%${keywordFilter.value}%`
    +      );
    +      currentFilters.push({
    +        key: 'keyword',
    +        operation: '=',
    +        value: keywordFilter.value
    +      });
    +    }
    +
    +    const sortBy = filters.find((f) => f.key === 'sortBy');
    +    const sortOrder = filters.find(
    +      (f) =>
    +        f.key === 'sortOrder' &&
    +        ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
    +    ) || { value: 'DESC' };
    +
    +    if (sortBy && sortBy.value === 'group_name') {
    +      this.baseQuery.orderBy('customer_group.group_name', sortOrder.value);
    +      currentFilters.push({
    +        key: 'sortBy',
    +        operation: '=',
    +        value: sortBy.value
    +      });
    +    } else {
    +      this.baseQuery.orderBy('customer_group.customer_group_id', 'DESC');
    +    }
    +    if (sortOrder.key) {
    +      currentFilters.push({
    +        key: 'sortOrder',
    +        operation: '=',
    +        value: sortOrder.value
    +      });
    +    }
    +
    +    // Clone the main query for getting total right before doing the paging
    +    const totalQuery = this.baseQuery.clone();
    +    totalQuery.select('COUNT(*)', 'total');
    +    totalQuery.removeOrderBy();
    +    // Paging
    +    const page = filters.find((f) => f.key === 'page') || { value: 1 };
    +    const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from the config
    +    currentFilters.push({
    +      key: 'page',
    +      operation: '=',
    +      value: page.value
    +    });
    +    currentFilters.push({
    +      key: 'limit',
    +      operation: '=',
    +      value: limit.value
    +    });
    +    this.baseQuery.limit(
    +      (page.value - 1) * parseInt(limit.value, 10),
    +      parseInt(limit.value, 10)
    +    );
    +    this.currentFilters = currentFilters;
    +    this.totalQuery = totalQuery;
    +  }
    +
    +  async items() {
    +    const items = await this.baseQuery.execute(pool);
    +    return items.map((row) => camelCase(row));
    +  }
    +
    +  async total() {
    +    // Call items to get the total
    +    const total = await this.totalQuery.execute(pool);
    +    return total[0].total;
    +  }
    +
    +  currentFilters() {
    +    return this.currentFilters;
    +  }
    +}
    +
    +module.exports.CustomerGroupCollection = CustomerGroupCollection;
    
  • packages/evershop/src/modules/customer/services/getCustomerGroupsBaseQuery.js+7 0 added
    @@ -0,0 +1,7 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +
    +module.exports.getCustomerGroupsBaseQuery = () => {
    +  const query = select().from('customer_group');
    +
    +  return query;
    +};
    
  • packages/evershop/src/modules/graphql/api/adminGraphql/[bodyParser]graphql.js+4 2 modified
    @@ -1,3 +1,5 @@
    -const graphqlMiddleware = require('@evershop/evershop/src/modules/graphql/api/graphql/[bodyParser]graphql');
    +const schema = require('../../services/buildSchema');
    +const { graphqlMiddleware } = require('../../services/graphqlMiddleware');
     
    -module.exports = graphqlMiddleware;
    +const middleware = graphqlMiddleware(schema);
    +module.exports = middleware;
    
  • packages/evershop/src/modules/graphql/api/graphql/[bodyParser]graphql.js+4 55 modified
    @@ -1,56 +1,5 @@
    -const { execute } = require('graphql');
    -const { validate } = require('graphql/validation');
    -const { parse } = require('graphql');
    -const isDevelopmentMode = require('@evershop/evershop/src/lib/util/isDevelopmentMode');
    -const { OK } = require('@evershop/evershop/src/lib/util/httpStatus');
    -let schema = require('../../services/buildSchema');
    -const { getContext } = require('../../services/contextHelper');
    +const schema = require('../../services/buildStoreFrontSchema');
    +const { graphqlMiddleware } = require('../../services/graphqlMiddleware');
     
    -module.exports = async function graphql(request, response, delegate, next) {
    -  const { body } = request;
    -  const { query, variables } = body;
    -  try {
    -    const promises = [];
    -    Object.keys(delegate).forEach((id) => {
    -      // Check if middleware is async
    -      if (delegate[id] instanceof Promise) {
    -        promises.push(delegate[id]);
    -      }
    -    });
    -
    -    if (!query) {
    -      response.status(OK).json({
    -        data: {}
    -      });
    -      return;
    -    }
    -
    -    const document = parse(query);
    -    // Validate the query
    -    const validationErrors = validate(schema, document);
    -    if (validationErrors.length > 0) {
    -      next(new Error(validationErrors[0].message));
    -    } else {
    -      if (isDevelopmentMode()) {
    -        // eslint-disable-next-line global-require
    -        schema = require('../../services/buildSchema');
    -      }
    -      const data = await execute({
    -        schema,
    -        contextValue: getContext(request),
    -        document,
    -        variableValues: variables
    -      });
    -      if (data.errors) {
    -        // Create an Error instance with message and stack trace
    -        next(new Error(data.errors[0].message));
    -      } else {
    -        response.status(OK).json({
    -          data: data.data
    -        });
    -      }
    -    }
    -  } catch (error) {
    -    next(error);
    -  }
    -};
    +const middleware = graphqlMiddleware(schema);
    +module.exports = middleware;
    
  • packages/evershop/src/modules/graphql/pages/global/[buildQuery]graphql[notification].js+6 6 modified
    @@ -1,15 +1,18 @@
     const { execute } = require('graphql');
     const { parse } = require('graphql');
     const { validate } = require('graphql/validation');
    -const isDevelopmentMode = require('@evershop/evershop/src/lib/util/isDevelopmentMode');
     const { debug } = require('@evershop/evershop/src/lib/log/debuger');
    -let schema = require('../../services/buildSchema');
    +const adminSchema = require('../../services/buildSchema');
    +const storeFrontSchema = require('../../services/buildStoreFrontSchema');
     const { getContext } = require('../../services/contextHelper');
     const {
       graphqlErrorMessageFormat
     } = require('../../services/graphqlErrorMessageFormat');
     
     module.exports = async function graphql(request, response, delegate, next) {
    +  const {currentRoute} = request;
    +  const schema =
    +    currentRoute && currentRoute.isAdmin ? adminSchema : storeFrontSchema;
       // TODO: Should we wait for previous async middlewares?
       try {
         const { body } = request;
    @@ -25,6 +28,7 @@ module.exports = async function graphql(request, response, delegate, next) {
           } else {
             const document = parse(graphqlQuery);
             // Validate the query
    +
             const validationErrors = validate(schema, document);
             if (validationErrors.length > 0) {
               const formatedErrorMessage = graphqlErrorMessageFormat(
    @@ -38,10 +42,6 @@ module.exports = async function graphql(request, response, delegate, next) {
               );
               next(validationErrors[0]);
             } else {
    -          if (isDevelopmentMode()) {
    -            // eslint-disable-next-line global-require
    -            schema = require('../../services/buildSchema');
    -          }
               const context = getContext(request);
               // Add current user to context
               context.user = request.locals.user;
    
  • packages/evershop/src/modules/graphql/services/buildResolvers.js+8 3 modified
    @@ -4,18 +4,23 @@ const { mergeResolvers } = require('@graphql-tools/merge');
     const { CONSTANTS } = require('@evershop/evershop/src/lib/helpers');
     const { getEnabledExtensions } = require('../../../../bin/extension');
     
    -module.exports.buildResolvers = function buildResolvers() {
    +module.exports.buildResolvers = function buildResolvers(isAdmin = false) {
       const typeSources = [
         path.join(CONSTANTS.MOLDULESPATH, '*/graphql/types/**/*.resolvers.js')
       ];
    +
       const extensions = getEnabledExtensions();
       extensions.forEach((extension) => {
         typeSources.push(
    -      path.join(extension.path, 'graphql', 'types', '**', '*.resolvers.js')
    +      path.join(extension.path, 'graphql/types/**/*.resolvers.js')
         );
       });
       const resolvers = mergeResolvers(
    -    typeSources.map((source) => loadFilesSync(source))
    +    typeSources.map((source) =>
    +      loadFilesSync(source, {
    +        ignoredExtensions: isAdmin ? [] : ['.admin.resolvers.js']
    +      })
    +    )
       );
     
       return resolvers;
    
  • packages/evershop/src/modules/graphql/services/buildSchema.js+2 2 modified
    @@ -3,8 +3,8 @@ const { buildTypeDefs } = require('./buildTypes');
     const { buildResolvers } = require('./buildResolvers');
     
     const schema = makeExecutableSchema({
    -  typeDefs: buildTypeDefs(),
    -  resolvers: buildResolvers()
    +  typeDefs: buildTypeDefs(true),
    +  resolvers: buildResolvers(true)
     });
     
     module.exports = schema;
    
  • packages/evershop/src/modules/graphql/services/buildStoreFrontSchema.js+10 0 added
    @@ -0,0 +1,10 @@
    +const { makeExecutableSchema } = require('@graphql-tools/schema');
    +const { buildTypeDefs } = require('./buildTypes');
    +const { buildResolvers } = require('./buildResolvers');
    +
    +const schema = makeExecutableSchema({
    +  typeDefs: buildTypeDefs(),
    +  resolvers: buildResolvers()
    +});
    +
    +module.exports = schema;
    
  • packages/evershop/src/modules/graphql/services/buildTypes.js+8 5 modified
    @@ -4,18 +4,21 @@ const { mergeTypeDefs } = require('@graphql-tools/merge');
     const { CONSTANTS } = require('@evershop/evershop/src/lib/helpers');
     const { getEnabledExtensions } = require('../../../../bin/extension');
     
    -module.exports.buildTypeDefs = function buildTypeDefs() {
    +module.exports.buildTypeDefs = function buildTypeDefs(isAdmin = false) {
       const typeSources = [
         path.join(CONSTANTS.MOLDULESPATH, '*/graphql/types/**/*.graphql')
       ];
    +
       const extensions = getEnabledExtensions();
       extensions.forEach((extension) => {
    -    typeSources.push(
    -      path.join(extension.path, 'graphql', 'types', '**', '*.graphql')
    -    );
    +    typeSources.push(path.join(extension.path, 'graphql/types/**/*.graphql'));
       });
       const typeDefs = mergeTypeDefs(
    -    typeSources.map((source) => loadFilesSync(source))
    +    typeSources.map((source) =>
    +      loadFilesSync(source, {
    +        ignoredExtensions: isAdmin ? [] : ['.admin.graphql']
    +      })
    +    )
       );
     
       return typeDefs;
    
  • packages/evershop/src/modules/graphql/services/graphqlMiddleware.js+56 0 added
    @@ -0,0 +1,56 @@
    +const { execute } = require('graphql');
    +const { validate } = require('graphql/validation');
    +const { parse } = require('graphql');
    +const { OK } = require('@evershop/evershop/src/lib/util/httpStatus');
    +const isDevelopmentMode = require('@evershop/evershop/src/lib/util/isDevelopmentMode');
    +const { getContext } = require('./contextHelper');
    +
    +module.exports.graphqlMiddleware = (schema) =>
    +  async function graphqlMiddleware(request, response, delegate, next) {
    +    const { body } = request;
    +    const { query, variables } = body;
    +    try {
    +      const promises = [];
    +      Object.keys(delegate).forEach((id) => {
    +        // Check if middleware is async
    +        if (delegate[id] instanceof Promise) {
    +          promises.push(delegate[id]);
    +        }
    +      });
    +
    +      if (!query) {
    +        response.status(OK).json({
    +          data: {}
    +        });
    +        return;
    +      }
    +
    +      const document = parse(query);
    +      // Validate the query
    +      const validationErrors = validate(schema, document);
    +      if (validationErrors.length > 0) {
    +        next(new Error(validationErrors[0].message));
    +      } else {
    +        if (isDevelopmentMode()) {
    +          // eslint-disable-next-line global-require, no-param-reassign
    +          schema = require('../../services/buildSchema');
    +        }
    +        const data = await execute({
    +          schema,
    +          contextValue: getContext(request),
    +          document,
    +          variableValues: variables
    +        });
    +        if (data.errors) {
    +          // Create an Error instance with message and stack trace
    +          next(new Error(data.errors[0].message));
    +        } else {
    +          response.status(OK).json({
    +            data: data.data
    +          });
    +        }
    +      }
    +    } catch (error) {
    +      next(error);
    +    }
    +  };
    
  • packages/evershop/src/modules/oms/graphql/types/BestSeller/BestSeller.admin.graphql+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/BestSeller/BestSeller.admin.resolvers.js+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/Carrier/Carrier.admin.graphql+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/Carrier/Carrier.admin.resolvers.js+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.graphql+14 0 added
    @@ -0,0 +1,14 @@
    +extend type Order {
    +  customerUrl: String
    +  editUrl: String!
    +  createShipmentApi: String!
    +  shipment: Shipment
    +}
    +
    +extend type Shipment {
    +  updateShipmentApi: String!
    +}
    +
    +extend type Query {
    +  orders(filters: [FilterInput]): OrderCollection
    +}
    
  • packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js+30 0 added
    @@ -0,0 +1,30 @@
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { getOrdersBaseQuery } = require('../../../services/getOrdersBaseQuery');
    +const { OrderCollection } = require('../../../services/OrderCollection');
    +
    +module.exports = {
    +  Query: {
    +    orders: async (_, { filters = [] }) => {
    +      const query = getOrdersBaseQuery();
    +      const root = new OrderCollection(query);
    +      await root.init({}, { filters });
    +      return root;
    +    }
    +  },
    +  Order: {
    +    editUrl: ({ uuid }) => buildUrl('orderEdit', { id: uuid }),
    +    createShipmentApi: ({ uuid }) => buildUrl('createShipment', { id: uuid }),
    +    customerUrl: async ({ customerId }, _, { pool }) => {
    +      const customer = await select()
    +        .from('customer')
    +        .where('customer_id', '=', customerId)
    +        .load(pool);
    +      return customer ? buildUrl('customerEdit', { id: customer.uuid }) : null;
    +    }
    +  },
    +  Shipment: {
    +    updateShipmentApi: ({ orderUuid, uuid }) =>
    +      buildUrl('updateShipment', { order_id: orderUuid, shipment_id: uuid })
    +  }
    +};
    
  • packages/evershop/src/modules/oms/graphql/types/Order/Order.graphql+10 11 modified
    @@ -1,5 +1,5 @@
     """
    -Retrieve an Order Address.
    +Represents an Order Address.
     """
     type OrderAddress implements Address {
       orderAddressId: Int!
    @@ -15,7 +15,7 @@ type OrderAddress implements Address {
     }
     
     """
    -Retrieve an Order Item.
    +Represents an Order Item.
     """
     type OrdertItem implements ShoppingCartItem {
       orderItemId: ID!
    @@ -42,7 +42,7 @@ type OrdertItem implements ShoppingCartItem {
     }
     
     """
    -Retrieve an Order.
    +Represents an Order.
     """
     type Order implements ShoppingCart {
       orderId: ID!
    @@ -53,7 +53,6 @@ type Order implements ShoppingCart {
       billingAddress: OrderAddress
       currency: String!
       customerId: Int
    -  customerUrl: String
       customerGroupId: Int
       customerEmail: String
       customerFullName: String
    @@ -78,14 +77,12 @@ type Order implements ShoppingCart {
       shippingNote: String
       createdAt: Date!
       updatedAt: String!
    -  editUrl: String!
    -  createShipmentApi: String!
       activities: [Activity]
       shipment: Shipment
     }
     
     """
    -Retrieve an Order Activity.
    +Represents an Order Activity.
     """
     type Activity {
       orderActivityId: Int!
    @@ -96,7 +93,7 @@ type Activity {
     }
     
     """
    -Retrieve an Order Shipment.
    +Represents a Shipment.
     """
     type Shipment {
       shipmentId: Int!
    @@ -105,7 +102,6 @@ type Shipment {
       trackingNumber: String
       createdAt: DateTime!
       updatedAt: DateTime
    -  updateShipmentApi: String!
     }
     
     """
    @@ -118,7 +114,10 @@ type OrderCollection {
       currentFilters: [Filter]
     }
     
    +extend type Customer {
    +  orders: [Order]
    +}
    +
     extend type Query {
    -  order(id: String!): Order
    -  orders(filters: [FilterInput]): OrderCollection
    +  order(uuid: String!): Order
     }
    
  • packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js+10 29 modified
    @@ -1,32 +1,19 @@
     const { select } = require('@evershop/postgres-query-builder');
    -const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
    -const { getOrdersBaseQuery } = require('../../../services/getOrdersBaseQuery');
    -const { OrderCollection } = require('../../../services/OrderCollection');
     
     module.exports = {
       Query: {
    -    order: async (_, { id }, { pool }) => {
    +    order: async (_, { uuid }, { pool }) => {
           const query = select().from('order');
    -      query.where('uuid', '=', id);
    +      query.where('uuid', '=', uuid);
           const order = await query.load(pool);
           if (!order) {
             return null;
           } else {
             return camelCase(order);
           }
         },
    -    orders: async (_, { filters = [] }, { user }) => {
    -      // This field is for admin only
    -      if (!user) {
    -        return [];
    -      }
    -      const query = getOrdersBaseQuery();
    -      const root = new OrderCollection(query);
    -      await root.init({}, { filters });
    -      return root;
    -    },
         shipmentStatusList: () => getConfig('oms.order.shipmentStatus', {}),
         paymentStatusList: () => getConfig('oms.order.paymentStatus', {})
       },
    @@ -68,17 +55,6 @@ module.exports = {
             .load(pool);
           return shipment ? { ...camelCase(shipment), orderUuid: uuid } : null;
         },
    -    editUrl: ({ uuid }) => buildUrl('orderEdit', { id: uuid }),
    -    createShipmentApi: ({ uuid }) => buildUrl('createShipment', { id: uuid }),
    -    customerUrl: async ({ customerId }, _, { pool }) => {
    -      const customer = await select()
    -        .from('customer')
    -        .where('customer_id', '=', customerId)
    -        .load(pool);
    -      return customer
    -        ? buildUrl('customerEdit', { id: customer.uuid })
    -        : null;
    -    },
         shipmentStatus: ({ shipmentStatus }) => {
           const statusList = getConfig('oms.order.shipmentStatus', {});
           const status = statusList[shipmentStatus] || {
    @@ -108,8 +84,13 @@ module.exports = {
           };
         }
       },
    -  Shipment: {
    -    updateShipmentApi: ({ orderUuid, uuid }) =>
    -      buildUrl('updateShipment', { order_id: orderUuid, shipment_id: uuid })
    +  Customer: {
    +    orders: async ({ customerId }, _, { pool }) => {
    +      const orders = await select()
    +        .from('order')
    +        .where('order.customer_id', '=', customerId)
    +        .execute(pool);
    +      return orders.map((row) => camelCase(row));
    +    }
       }
     };
    
  • packages/evershop/src/modules/oms/graphql/types/PaymentTransaction/PaymentTransaction.admin.graphql+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/PaymentTransaction/PaymentTransaction.admin.resolvers.js+0 0 renamed
  • packages/evershop/src/modules/oms/graphql/types/Status/Status.graphql+2 2 modified
    @@ -1,5 +1,5 @@
     """
    -The `PaymentStatus` type defines the status of a payment.
    +Represents a payment status.
     """
     type PaymentStatus {
       name: String
    @@ -9,7 +9,7 @@ type PaymentStatus {
     }
     
     """
    -The `ShipmentStatus` type defines the status of a shipment.
    +Represents a shipment status.
     """
     type ShipmentStatus {
       name: String
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/Activities.jsx+1 1 modified
    @@ -97,7 +97,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           activities {
             comment
             customerNotified
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/AddTrackingButton.jsx+1 1 modified
    @@ -116,7 +116,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           shipment {
             shipmentId
             carrier
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/Customer.jsx+1 1 modified
    @@ -117,7 +117,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           customerFullName
           customerEmail
           customerUrl
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/CustomerNotes.jsx+1 1 modified
    @@ -48,7 +48,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           shippingNote
         }
       }
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/Items.jsx+1 1 modified
    @@ -121,7 +121,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           currency
           shipment {
             shipmentId
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/MarkDeliveredButton.jsx+1 1 modified
    @@ -64,7 +64,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           orderId
           shipmentStatus {
             code
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/PageHeading.jsx+1 1 modified
    @@ -22,7 +22,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId", null)) {
    +    order(uuid: getContextValue("orderId", null)) {
           orderNumber
         }
         backUrl: url(routeId: "orderGrid")
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/Payment.jsx+1 1 modified
    @@ -141,7 +141,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           orderId
           totalQty
           coupon
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/PaymentStatus.jsx+1 1 modified
    @@ -33,7 +33,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           paymentStatus {
             code
             badge
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/ShipButton.jsx+1 1 modified
    @@ -122,7 +122,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           shipment {
             shipmentId
             carrier
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/ShipmentStatus.jsx+1 1 modified
    @@ -33,7 +33,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           shipmentStatus {
             code
             badge
    
  • packages/evershop/src/modules/oms/pages/admin/orderEdit/TrackingButton.jsx+1 1 modified
    @@ -55,7 +55,7 @@ export const layout = {
     
     export const query = `
       query Query {
    -    order(id: getContextValue("orderId")) {
    +    order(uuid: getContextValue("orderId")) {
           shipment {
             shipmentId
             carrier
    
  • packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.admin.graphql+7 0 added
    @@ -0,0 +1,7 @@
    +extend type Setting {
    +  paypalPaymentStatus: Int
    +  paypalClientId: String
    +  paypalClientSecret: String
    +  paypalWebhookSecret: String
    +  paypalPaymentIntent: String
    +}
    
  • packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.admin.resolvers.js+78 0 added
    @@ -0,0 +1,78 @@
    +const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
    +
    +module.exports = {
    +  Setting: {
    +    paypalPaymentStatus: (setting) => {
    +      const paypalConfig = getConfig('system.paypal', {});
    +      if (paypalConfig.status) {
    +        return paypalConfig.status;
    +      }
    +      const paypalPaymentStatus = setting.find(
    +        (s) => s.name === 'paypalPaymentStatus'
    +      );
    +      if (paypalPaymentStatus) {
    +        return parseInt(paypalPaymentStatus.value, 10);
    +      } else {
    +        return 0;
    +      }
    +    },
    +    paypalPaymentIntent: (setting) => {
    +      const paypalPaymentIntent = setting.find(
    +        (s) => s.name === 'paypalPaymentIntent'
    +      );
    +      if (paypalPaymentIntent) {
    +        return paypalPaymentIntent.value;
    +      } else {
    +        return 'CAPTURE';
    +      }
    +    },
    +    paypalClientId: (setting) => {
    +      const paypalConfig = getConfig('system.paypal', {});
    +      if (paypalConfig.clientId) {
    +        return paypalConfig.clientId;
    +      }
    +      const paypalClientId = setting.find((s) => s.name === 'paypalClientId');
    +      if (paypalClientId) {
    +        return paypalClientId.value;
    +      } else {
    +        return null;
    +      }
    +    },
    +    paypalClientSecret: (setting, _, { user }) => {
    +      const paypalConfig = getConfig('system.paypal', {});
    +      if (paypalConfig.clientSecret) {
    +        return '*******************************';
    +      }
    +      if (user) {
    +        const paypalClientSecret = setting.find(
    +          (s) => s.name === 'paypalClientSecret'
    +        );
    +        if (paypalClientSecret) {
    +          return paypalClientSecret.value;
    +        } else {
    +          return null;
    +        }
    +      } else {
    +        return null;
    +      }
    +    },
    +    paypalWebhookSecret: (setting, _, { user }) => {
    +      const paypalConfig = getConfig('system.paypal', {});
    +      if (paypalConfig.webhookSecret) {
    +        return '*******************************';
    +      }
    +      if (user) {
    +        const paypalWebhookSecret = setting.find(
    +          (s) => s.name === 'paypalWebhookSecret'
    +        );
    +        if (paypalWebhookSecret) {
    +          return paypalWebhookSecret.value;
    +        } else {
    +          return null;
    +        }
    +      } else {
    +        return null;
    +      }
    +    }
    +  }
    +};
    
  • packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.graphql+0 5 modified
    @@ -1,9 +1,4 @@
     extend type Setting {
    -  paypalPaymentStatus: Int
       paypalDislayName: String
    -  paypalClientId: String
    -  paypalClientSecret: String
    -  paypalWebhookSecret: String
    -  paypalPaymentIntent: String
       paypalEnvironment: String
     }
    
  • packages/evershop/src/modules/paypal/graphql/types/PaypalSetting/PaypalSetting.resolvers.js+0 72 modified
    @@ -2,20 +2,6 @@ const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
     
     module.exports = {
       Setting: {
    -    paypalPaymentStatus: (setting) => {
    -      const paypalConfig = getConfig('system.paypal', {});
    -      if (paypalConfig.status) {
    -        return paypalConfig.status;
    -      }
    -      const paypalPaymentStatus = setting.find(
    -        (s) => s.name === 'paypalPaymentStatus'
    -      );
    -      if (paypalPaymentStatus) {
    -        return parseInt(paypalPaymentStatus.value, 10);
    -      } else {
    -        return 0;
    -      }
    -    },
         paypalDislayName: (setting) => {
           const paypalDislayName = setting.find(
             (s) => s.name === 'paypalDislayName'
    @@ -26,64 +12,6 @@ module.exports = {
             return 'Paypal';
           }
         },
    -    paypalPaymentIntent: (setting) => {
    -      const paypalPaymentIntent = setting.find(
    -        (s) => s.name === 'paypalPaymentIntent'
    -      );
    -      if (paypalPaymentIntent) {
    -        return paypalPaymentIntent.value;
    -      } else {
    -        return 'CAPTURE';
    -      }
    -    },
    -    paypalClientId: (setting) => {
    -      const paypalConfig = getConfig('system.paypal', {});
    -      if (paypalConfig.clientId) {
    -        return paypalConfig.clientId;
    -      }
    -      const paypalClientId = setting.find((s) => s.name === 'paypalClientId');
    -      if (paypalClientId) {
    -        return paypalClientId.value;
    -      } else {
    -        return null;
    -      }
    -    },
    -    paypalClientSecret: (setting, _, { user }) => {
    -      const paypalConfig = getConfig('system.paypal', {});
    -      if (paypalConfig.clientSecret) {
    -        return '*******************************';
    -      }
    -      if (user) {
    -        const paypalClientSecret = setting.find(
    -          (s) => s.name === 'paypalClientSecret'
    -        );
    -        if (paypalClientSecret) {
    -          return paypalClientSecret.value;
    -        } else {
    -          return null;
    -        }
    -      } else {
    -        return null;
    -      }
    -    },
    -    paypalWebhookSecret: (setting, _, { user }) => {
    -      const paypalConfig = getConfig('system.paypal', {});
    -      if (paypalConfig.webhookSecret) {
    -        return '*******************************';
    -      }
    -      if (user) {
    -        const paypalWebhookSecret = setting.find(
    -          (s) => s.name === 'paypalWebhookSecret'
    -        );
    -        if (paypalWebhookSecret) {
    -          return paypalWebhookSecret.value;
    -        } else {
    -          return null;
    -        }
    -      } else {
    -        return null;
    -      }
    -    },
         paypalEnvironment: (setting) => {
           const paypalConfig = getConfig('system.paypal', {});
           if (paypalConfig.environment) {
    
  • packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.graphql+89 0 added
    @@ -0,0 +1,89 @@
    +scalar JSON
    +
    +"""
    +Represents a coupon
    +"""
    +type Coupon {
    +  couponId: Int
    +  uuid: String!
    +  status: Int!
    +  description: String!
    +  discountAmount: Float!
    +  freeShipping: Int!
    +  discountType: String!
    +  coupon: String!
    +  usedTime: Int
    +  targetProducts: TargetProducts
    +  condition: OrderCondition
    +  userCondition: UserCondition
    +  buyxGety: [ByXGetY]
    +  maxUsesTimePerCoupon: Int
    +  maxUsesTimePerCustomer: Int
    +  startDate: DateTime
    +  endDate: DateTime
    +  editUrl: String!
    +  updateApi: String!
    +  deleteApi: String!
    +}
    +
    +"""
    +Represents a signle product used in the condition of a coupon.
    +"""
    +type MatchProductFilter {
    +  key: String!
    +  operator: String!
    +  value: JSON
    +  qty: String
    +}
    +
    +"""
    +Represents the target products of a coupon.
    +"""
    +type TargetProducts {
    +  maxQty: String
    +  products: [MatchProductFilter]
    +}
    +
    +"""
    +Represents the condition of a coupon.
    +"""
    +type OrderCondition {
    +  orderTotal: String
    +  orderQty: String
    +  requiredProducts: [MatchProductFilter]
    +}
    +
    +"""
    +Represents the buy x get y condition of a coupon.
    +"""
    +type ByXGetY {
    +  sku: String!
    +  buyQty: String
    +  getQty: String
    +  maxY: String
    +  discount: String
    +}
    +
    +"""
    +Represents the user condition of a coupon.
    +"""
    +type UserCondition {
    +  groups: [String]
    +  emails: String
    +  purchased: String
    +}
    +
    +"""
    +Returns a collection of coupons
    +"""
    +type CouponCollection {
    +  items: [Coupon]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
    +extend type Query {
    +  coupon(id: Int): Coupon
    +  coupons(filters: [FilterInput]): CouponCollection
    +}
    
  • packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js+67 0 added
    @@ -0,0 +1,67 @@
    +const { GraphQLJSON } = require('graphql-type-json');
    +const { select } = require('@evershop/postgres-query-builder');
    +const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +const {
    +  getCouponsBaseQuery
    +} = require('../../../services/getCouponsBaseQuery');
    +const { CouponCollection } = require('../../../services/CouponCollection');
    +
    +module.exports = {
    +  JSON: GraphQLJSON,
    +  Query: {
    +    coupon: async (root, { id }, { pool }) => {
    +      const query = select().from('coupon');
    +      query.where('coupon_id', '=', id);
    +      // if (admin !== true) {
    +      //   query.where('cms_page.status', '=', 1);
    +      // }
    +
    +      const coupon = await query.load(pool);
    +      return coupon ? camelCase(coupon) : null;
    +    },
    +    coupons: async (_, { filters = [] }, { user }) => {
    +      // This field is for admin only
    +      if (!user) {
    +        return [];
    +      }
    +      const query = getCouponsBaseQuery();
    +      const root = new CouponCollection(query);
    +      await root.init({}, { filters });
    +      return root;
    +    }
    +  },
    +  Coupon: {
    +    targetProducts: ({ targetProducts }) => {
    +      if (!targetProducts) {
    +        return null;
    +      } else {
    +        return camelCase(targetProducts);
    +      }
    +    },
    +    condition: ({ condition }) => {
    +      if (!condition) {
    +        return null;
    +      } else {
    +        return camelCase(condition);
    +      }
    +    },
    +    userCondition: ({ userCondition }) => {
    +      if (!userCondition) {
    +        return null;
    +      } else {
    +        return camelCase(userCondition);
    +      }
    +    },
    +    buyxGety: ({ buyxGety }) => {
    +      if (!buyxGety) {
    +        return [];
    +      } else {
    +        return buyxGety.map((item) => camelCase(item));
    +      }
    +    },
    +    editUrl: ({ uuid }) => buildUrl('couponEdit', { id: uuid }),
    +    updateApi: (coupon) => buildUrl('updateCoupon', { id: coupon.uuid }),
    +    deleteApi: (coupon) => buildUrl('deleteCoupon', { id: coupon.uuid })
    +  }
    +};
    
  • packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.graphql+0 90 modified
    @@ -1,93 +1,3 @@
    -scalar JSON
    -
    -"""
    -Represents a coupon
    -"""
    -type Coupon {
    -  couponId: Int
    -  uuid: String!
    -  status: Int!
    -  description: String!
    -  discountAmount: Float!
    -  freeShipping: Int!
    -  discountType: String!
    -  coupon: String!
    -  usedTime: Int
    -  targetProducts: TargetProducts
    -  condition: OrderCondition
    -  userCondition: UserCondition
    -  buyxGety: [ByXGetY]
    -  maxUsesTimePerCoupon: Int
    -  maxUsesTimePerCustomer: Int
    -  startDate: DateTime
    -  endDate: DateTime
    -  editUrl: String!
    -  updateApi: String!
    -  deleteApi: String!
    -}
    -
    -"""
    -Represents a signle product used in the condition of a coupon.
    -"""
    -type MatchProductFilter {
    -  key: String!
    -  operator: String!
    -  value: JSON
    -  qty: String
    -}
    -
    -"""
    -Represents the target products of a coupon.
    -"""
    -type TargetProducts {
    -  maxQty: String
    -  products: [MatchProductFilter]
    -}
    -
    -"""
    -Represents the condition of a coupon.
    -"""
    -type OrderCondition {
    -  orderTotal: String
    -  orderQty: String
    -  requiredProducts: [MatchProductFilter]
    -}
    -
    -"""
    -Represents the buy x get y condition of a coupon.
    -"""
    -type ByXGetY {
    -  sku: String!
    -  buyQty: String
    -  getQty: String
    -  maxY: String
    -  discount: String
    -}
    -
    -"""
    -Represents the user condition of a coupon.
    -"""
    -type UserCondition {
    -  groups: [String]
    -  emails: String
    -  purchased: String
    -}
    -
    -"""
    -Returns a collection of coupons
    -"""
    -type CouponCollection {
    -  items: [Coupon]
    -  currentPage: Int!
    -  total: Int!
    -  currentFilters: [Filter]
    -}
    -
     extend type Cart {
       applyCouponApi: String!
     }
    -
    -extend type Query {
    -  coupon(id: Int): Coupon
    -  coupons(filters: [FilterInput]): CouponCollection
    -}
    
  • packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.resolvers.js+0 63 modified
    @@ -1,69 +1,6 @@
    -const { GraphQLJSON } = require('graphql-type-json');
    -const { select } = require('@evershop/postgres-query-builder');
     const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    -const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    -const {
    -  getCouponsBaseQuery
    -} = require('../../../services/getCouponsBaseQuery');
    -const { CouponCollection } = require('../../../services/CouponCollection');
     
     module.exports = {
    -  JSON: GraphQLJSON,
    -  Query: {
    -    coupon: async (root, { id }, { pool }) => {
    -      const query = select().from('coupon');
    -      query.where('coupon_id', '=', id);
    -      // if (admin !== true) {
    -      //   query.where('cms_page.status', '=', 1);
    -      // }
    -
    -      const coupon = await query.load(pool);
    -      return coupon ? camelCase(coupon) : null;
    -    },
    -    coupons: async (_, { filters = [] }, { user }) => {
    -      // This field is for admin only
    -      if (!user) {
    -        return [];
    -      }
    -      const query = getCouponsBaseQuery();
    -      const root = new CouponCollection(query);
    -      await root.init({}, { filters });
    -      return root;
    -    }
    -  },
    -  Coupon: {
    -    targetProducts: ({ targetProducts }) => {
    -      if (!targetProducts) {
    -        return null;
    -      } else {
    -        return camelCase(targetProducts);
    -      }
    -    },
    -    condition: ({ condition }) => {
    -      if (!condition) {
    -        return null;
    -      } else {
    -        return camelCase(condition);
    -      }
    -    },
    -    userCondition: ({ userCondition }) => {
    -      if (!userCondition) {
    -        return null;
    -      } else {
    -        return camelCase(userCondition);
    -      }
    -    },
    -    buyxGety: ({ buyxGety }) => {
    -      if (!buyxGety) {
    -        return [];
    -      } else {
    -        return buyxGety.map((item) => camelCase(item));
    -      }
    -    },
    -    editUrl: ({ uuid }) => buildUrl('couponEdit', { id: uuid }),
    -    updateApi: (coupon) => buildUrl('updateCoupon', { id: coupon.uuid }),
    -    deleteApi: (coupon) => buildUrl('deleteCoupon', { id: coupon.uuid })
    -  },
       Cart: {
         applyCouponApi: (cart) => buildUrl('couponApply', { cart_id: cart.uuid })
       }
    
  • packages/evershop/src/modules/promotion/pages/admin/couponEdit+couponNew/CustomerCondition.jsx+24 13 modified
    @@ -11,14 +11,19 @@ const customStyles = {
       })
     };
     
    -export default function CustomerCondition({ coupon = {}, groups }) {
    +export default function CustomerCondition({
    +  coupon = {},
    +  groups: { items: customerGroups }
    +}) {
       const condition = coupon?.userCondition || {};
       const selectedGroups = (condition.groups || [])
         .filter((g) =>
    -      groups.find((group) => parseInt(group.value, 10) === parseInt(g, 10))
    +      customerGroups.find(
    +        (group) => parseInt(group.value, 10) === parseInt(g, 10)
    +      )
         )
         .map((g) => {
    -      const group = groups.find(
    +      const group = customerGroups.find(
             (e) => parseInt(e.value, 10) === parseInt(g, 10)
           );
           return {
    @@ -36,7 +41,7 @@ export default function CustomerCondition({ coupon = {}, groups }) {
                 default: (
                   <Select
                     name="user_condition[groups][]"
    -                options={groups.map((group) => ({
    +                options={customerGroups.map((group) => ({
                       value: group.value.toString(),
                       label: group.name
                     }))}
    @@ -108,17 +113,21 @@ CustomerCondition.propTypes = {
           purchased: PropTypes.number
         })
       }),
    -  groups: PropTypes.arrayOf(
    -    PropTypes.shape({
    -      value: PropTypes.number,
    -      name: PropTypes.string
    -    })
    -  )
    +  groups: PropTypes.shape({
    +    items: PropTypes.arrayOf(
    +      PropTypes.shape({
    +        value: PropTypes.number,
    +        name: PropTypes.string
    +      })
    +    )
    +  })
     };
     
     CustomerCondition.defaultProps = {
       coupon: {},
    -  groups: []
    +  groups: {
    +    items: []
    +  }
     };
     
     export const layout = {
    @@ -136,8 +145,10 @@ export const query = `
           }
         }
         groups: customerGroups {
    -      value: customerGroupId
    -      name: groupName
    +      items {
    +        value: customerGroupId
    +        name: groupName
    +      }
         }
       }
     `;
    
  • packages/evershop/src/modules/promotion/services/DiscountCalculator.js+4 0 modified
    @@ -40,6 +40,8 @@ exports.DiscountCalculator = class DiscountCalculator {
                   (cartDiscountAmount * precisionFix -
                     distributedAmount * precisionFix) /
                   precisionFix;
    +            // Fix for rounding error
    +            sharedDiscount = parseFloat(sharedDiscount.toFixed(precision));
               } else {
                 const rowTotal = item.getData('final_price') * item.getData('qty');
                 sharedDiscount = toPrice(
    @@ -87,6 +89,8 @@ exports.DiscountCalculator = class DiscountCalculator {
                   (cartDiscountAmount * precisionFix -
                     distributedAmount * precisionFix) /
                   precisionFix;
    +            // Fix for rounding error
    +            sharedDiscount = parseFloat(sharedDiscount.toFixed(precision));
               } else {
                 const rowTotal = item.getData('final_price') * item.getData('qty');
                 sharedDiscount = toPrice(
    
  • packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.admin.graphql+4 0 added
    @@ -0,0 +1,4 @@
    +extend type Setting {
    +  stripeSecretKey: String
    +  stripeEndpointSecret: String
    +}
    
  • packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.admin.resolvers.js+62 0 added
    @@ -0,0 +1,62 @@
    +const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
    +
    +module.exports = {
    +  Setting: {
    +    stripePublishableKey: (setting) => {
    +      const stripeConfig = getConfig('system.stripe', {});
    +      if (stripeConfig.publishableKey) {
    +        return stripeConfig.publishableKey;
    +      }
    +      const stripePublishableKey = setting.find(
    +        (s) => s.name === 'stripePublishableKey'
    +      );
    +      if (stripePublishableKey) {
    +        return stripePublishableKey.value;
    +      } else {
    +        return null;
    +      }
    +    },
    +    stripeSecretKey: (setting, _, { user }) => {
    +      const stripeConfig = getConfig('system.stripe', {});
    +      if (stripeConfig.secretKey) {
    +        return `${stripeConfig.secretKey.substr(
    +          0,
    +          5
    +        )}*******************************`;
    +      }
    +      if (user) {
    +        const stripeSecretKey = setting.find(
    +          (s) => s.name === 'stripeSecretKey'
    +        );
    +        if (stripeSecretKey) {
    +          return stripeSecretKey.value;
    +        } else {
    +          return null;
    +        }
    +      } else {
    +        return null;
    +      }
    +    },
    +    stripeEndpointSecret: (setting, _, { user }) => {
    +      const stripeConfig = getConfig('system.stripe', {});
    +      if (stripeConfig.endpointSecret) {
    +        return `${stripeConfig.endpointSecret.substr(
    +          0,
    +          5
    +        )}*******************************`;
    +      }
    +      if (user) {
    +        const stripeEndpointSecret = setting.find(
    +          (s) => s.name === 'stripeEndpointSecret'
    +        );
    +        if (stripeEndpointSecret) {
    +          return stripeEndpointSecret.value;
    +        } else {
    +          return null;
    +        }
    +      } else {
    +        return null;
    +      }
    +    }
    +  }
    +};
    
  • packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.graphql+0 2 modified
    @@ -2,6 +2,4 @@ extend type Setting {
       stripePaymentStatus: Int
       stripeDislayName: String
       stripePublishableKey: String
    -  stripeSecretKey: String
    -  stripeEndpointSecret: String
     }
    
  • packages/evershop/src/modules/stripe/graphql/types/StripeSetting/StripeSetting.resolvers.js+0 36 modified
    @@ -39,42 +39,6 @@ module.exports = {
           } else {
             return null;
           }
    -    },
    -    stripeSecretKey: (setting, _, { user }) => {
    -      const stripeConfig = getConfig('system.stripe', {});
    -      if (stripeConfig.secretKey) {
    -        return '*******************************';
    -      }
    -      if (user) {
    -        const stripeSecretKey = setting.find(
    -          (s) => s.name === 'stripeSecretKey'
    -        );
    -        if (stripeSecretKey) {
    -          return stripeSecretKey.value;
    -        } else {
    -          return null;
    -        }
    -      } else {
    -        return null;
    -      }
    -    },
    -    stripeEndpointSecret: (setting, _, { user }) => {
    -      const stripeConfig = getConfig('system.stripe', {});
    -      if (stripeConfig.endpointSecret) {
    -        return '*******************************';
    -      }
    -      if (user) {
    -        const stripeEndpointSecret = setting.find(
    -          (s) => s.name === 'stripeEndpointSecret'
    -        );
    -        if (stripeEndpointSecret) {
    -          return stripeEndpointSecret.value;
    -        } else {
    -          return null;
    -        }
    -      } else {
    -        return null;
    -      }
         }
       }
     };
    
  • packages/evershop/src/modules/tax/graphql/types/TaxClass/TaxClass.admin.graphql+11 1 renamed
    @@ -27,7 +27,17 @@ type TaxClass {
       addRateApi: String!
     }
     
    +"""
    +Returns a collection of tax classes.
    +"""
    +type TaxClassCollection {
    +  items: [TaxClass]
    +  currentPage: Int!
    +  total: Int!
    +  currentFilters: [Filter]
    +}
    +
     extend type Query {
    -  taxClasses: [TaxClass]
    +  taxClasses: TaxClassCollection
       taxClass(id: String!): TaxClass
     }
    \ No newline at end of file
    
  • packages/evershop/src/modules/tax/graphql/types/TaxClass/TaxClass.admin.resolvers.js+6 7 renamed
    @@ -2,16 +2,15 @@ const { select } = require('@evershop/postgres-query-builder');
     const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
     const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
     const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
    +const { TaxClassCollection } = require('../../../services/TaxClassCollection');
     
     module.exports = {
       Query: {
    -    taxClasses: async () => {
    -      const taxClasses = await select()
    -        .from('tax_class')
    -        .orderBy('tax_class_id', 'DESC')
    -        .execute(pool);
    -      // Parse the provinces field into an array
    -      return taxClasses.map((row) => camelCase(row));
    +    taxClasses: async (_, { filters }) => {
    +      const query = select().from('tax_class');
    +      const root = new TaxClassCollection(query);
    +      await root.init({}, { filters });
    +      return root;
         },
         taxClass: async (_, { id }) => {
           const taxClass = await select()
    
  • packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.admin.graphql+0 0 renamed
  • packages/evershop/src/modules/tax/graphql/types/TaxSetting/TaxSetting.admin.resolvers.js+0 0 renamed
  • packages/evershop/src/modules/tax/pages/admin/taxSetting/TaxSetting.jsx+21 17 modified
    @@ -28,23 +28,25 @@ const CountriesQuery = `
     const TaxClassesQuery = `
       query TaxClasses {
         taxClasses {
    -      taxClassId
    -      uuid
    -      name
    -      rates {
    -        taxRateId
    +      items {
    +        taxClassId
             uuid
             name
    -        rate
    -        isCompound
    -        country
    -        province
    -        postcode
    -        priority
    -        updateApi
    -        deleteApi
    +        rates {
    +          taxRateId
    +          uuid
    +          name
    +          rate
    +          isCompound
    +          country
    +          province
    +          postcode
    +          priority
    +          updateApi
    +          deleteApi
    +        }
    +        addRateApi
           }
    -      addRateApi
         }
       }
     `;
    @@ -124,10 +126,12 @@ export default function TaxSetting({
                               text: 'Higest tax rate based on cart items'
                             }
                           ].concat(
    -                        taxClassesQueryData.data.taxClasses.map((taxClass) => ({
    +                        taxClassesQueryData.data.taxClasses.items.map(
    +                          (taxClass) => ({
                                 value: taxClass.taxClassId,
                                 text: taxClass.name
    -                          })) || []
    +                          })
    +                        ) || []
                           )}
                         />
                       </div>
    @@ -159,7 +163,7 @@ export default function TaxSetting({
               </Card>
               <Card title="Tax classes">
                 <TaxClasses
    -              classes={taxClassesQueryData.data.taxClasses}
    +              classes={taxClassesQueryData.data.taxClasses.items}
                   countries={countriesQueryData.data.countries}
                   getTaxClasses={reexecuteQuery}
                 />
    
  • packages/evershop/src/modules/tax/services/TaxClassCollection.js+91 0 added
    @@ -0,0 +1,91 @@
    +const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
    +const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
    +
    +class TaxClassCollection {
    +  constructor(baseQuery) {
    +    this.baseQuery = baseQuery;
    +  }
    +
    +  async init(args, { filters = [] }) {
    +    const currentFilters = [];
    +    // Name filter
    +    const nameFilter = filters.find((f) => f.key === 'name');
    +    if (nameFilter) {
    +      this.baseQuery.andWhere(
    +        'tax_class.name',
    +        'ILIKE',
    +        `%${nameFilter.value}%`
    +      );
    +      currentFilters.push({
    +        key: 'name',
    +        operation: '=',
    +        value: nameFilter.value
    +      });
    +    }
    +
    +    const sortBy = filters.find((f) => f.key === 'sortBy');
    +    const sortOrder = filters.find(
    +      (f) =>
    +        f.key === 'sortOrder' &&
    +        ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
    +    ) || { value: 'DESC' };
    +    if (sortBy && sortBy.value === 'name') {
    +      this.baseQuery.orderBy('tax_class.name', sortOrder.value);
    +      currentFilters.push({
    +        key: 'sortBy',
    +        operation: '=',
    +        value: sortBy.value
    +      });
    +    } else {
    +      this.baseQuery.orderBy('tax_class.tax_class_id', 'DESC');
    +    }
    +    if (sortOrder.key) {
    +      currentFilters.push({
    +        key: 'sortOrder',
    +        operation: '=',
    +        value: sortOrder.value
    +      });
    +    }
    +
    +    // Clone the main query for getting total right before doing the paging
    +    const totalQuery = this.baseQuery.clone();
    +    totalQuery.select('COUNT(*)', 'total');
    +    totalQuery.removeOrderBy();
    +    // Paging
    +    const page = filters.find((f) => f.key === 'page') || { value: 1 };
    +    const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from the config
    +    currentFilters.push({
    +      key: 'page',
    +      operation: '=',
    +      value: page.value
    +    });
    +    currentFilters.push({
    +      key: 'limit',
    +      operation: '=',
    +      value: limit.value
    +    });
    +    this.baseQuery.limit(
    +      (page.value - 1) * parseInt(limit.value, 10),
    +      parseInt(limit.value, 10)
    +    );
    +    this.currentFilters = currentFilters;
    +    this.totalQuery = totalQuery;
    +  }
    +
    +  async items() {
    +    const items = await this.baseQuery.execute(pool);
    +    return items.map((row) => camelCase(row));
    +  }
    +
    +  async total() {
    +    // Call items to get the total
    +    const total = await this.totalQuery.execute(pool);
    +    return total[0].total;
    +  }
    +
    +  currentFilters() {
    +    return this.currentFilters;
    +  }
    +}
    +
    +module.exports.TaxClassCollection = TaxClassCollection;
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.