Skip to content

Commit

Permalink
Merge branch 'master' into minor
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Sep 10, 2024
2 parents 1bbc127 + 589d54d commit 6a11b30
Show file tree
Hide file tree
Showing 40 changed files with 387 additions and 130 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## <small>3.0.2 (2024-09-10)</small>


#### Fixes

* **admin-ui** Fix removing coupon code from draft order ([04340f1](https://github.com/vendure-ecommerce/vendure/commit/04340f1)), closes [#2969](https://github.com/vendure-ecommerce/vendure/issues/2969)
* **core** Fix search indexing issue when working with multiple channels (#3041) ([75ed6e1](https://github.com/vendure-ecommerce/vendure/commit/75ed6e1)), closes [#3041](https://github.com/vendure-ecommerce/vendure/issues/3041) [#3012](https://github.com/vendure-ecommerce/vendure/issues/3012)
* **core** Prevent exposure of private custom fields via JSON type ([042abdb](https://github.com/vendure-ecommerce/vendure/commit/042abdb)), closes [#3049](https://github.com/vendure-ecommerce/vendure/issues/3049)
* **elasticsearch-plugin** Fix search multichannel indexing issue ([9d6f9cf](https://github.com/vendure-ecommerce/vendure/commit/9d6f9cf)), closes [#3012](https://github.com/vendure-ecommerce/vendure/issues/3012)

#### Perf

* **core** Fix slow `order` query for postgres v16 ([1baa8e7](https://github.com/vendure-ecommerce/vendure/commit/1baa8e7)), closes [#3037](https://github.com/vendure-ecommerce/vendure/issues/3037)
* **core** Omit ID encode/decode step if default EntityIdStrategy used ([ad30b55](https://github.com/vendure-ecommerce/vendure/commit/ad30b55))
* **core** Optimizations to the addItemToOrder path ([70ad853](https://github.com/vendure-ecommerce/vendure/commit/70ad853))
* **core** Optimize order operations ([e3d6c21](https://github.com/vendure-ecommerce/vendure/commit/e3d6c21))
* **core** Optimize resolution of featuredAsset fields ([d7bd446](https://github.com/vendure-ecommerce/vendure/commit/d7bd446))
* **core** Optimize setting active order on session ([c591432](https://github.com/vendure-ecommerce/vendure/commit/c591432))

## <small>3.0.1 (2024-08-21)</small>


Expand Down
12 changes: 11 additions & 1 deletion docs/docs/guides/deployment/deploying-admin-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ title: "Deploying the Admin UI"
showtoc: true
---

## Compiling the Admin UI

If you have customized the Admin UI with extensions, you should [compile your extensions ahead of time as part of the deployment process](/guides/extending-the-admin-ui/getting-started/#compiling-as-a-deployment-step).
If you have customized the Admin UI with extensions, you should compile your custom Admin UI app ahead of time
before deploying it. This will bundle the app into a set of static files which are then served by the AdminUiPlugin.

- [Guide: Compiling the Admin UI as a deployment step](/guides/extending-the-admin-ui/getting-started/#compiling-as-a-deployment-step).

:::warning

It is not recommended to compile the Admin UI on the server at runtime, as this can be slow and resource-intensive.
Instead, compile the Admin UI ahead of time and deploy the compiled assets, as covered in the guide linked above.
:::

## Setting the API host & port

Expand Down
15 changes: 9 additions & 6 deletions docs/docs/guides/developer-guide/testing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,24 @@ All that's left is to run your tests to find out whether your code behaves as ex

:::caution
**Note:** When using **Vitest** with multiple test suites (multiple `.e2e-spec.ts` files), it will attempt to run them in parallel. If all the test servers are running
on the same port (the default in the `testConfig` is `3050`), then this will cause a port conflict. To avoid this, you can manually set a unique port for each test suite:
on the same port (the default in the `testConfig` is `3050`), then this will cause a port conflict. To avoid this, you can manually set a unique port for each test suite. Be aware that `mergeConfig` is used here:

```ts title="src/plugins/my-plugin/e2e/my-plugin.e2e-spec.ts"
import { createTestEnvironment, testConfig } from '@vendure/testing';
import { mergeConfig } from "@vendure/core";
import { describe } from 'vitest';
import { MyPlugin } from '../my-plugin.ts';

describe('my plugin', () => {

const {server, adminClient, shopClient} = createTestEnvironment({
...testConfig,
// highlight-next-line
port: 3051,
const {server, adminClient, shopClient} = createTestEnvironment(mergeConfig(testConfig, {
// highlight-start
apiOptions: {
port: 3051,
},
// highlight-end
plugins: [MyPlugin],
});
}));

});
```
Expand Down
25 changes: 11 additions & 14 deletions docs/docs/guides/how-to/publish-plugin/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,21 @@ There are a couple of ways you can structure your plugin project:

### Repo structure

#### Stand-alone repo
We recommend that you use a "monorepo" structure to develop your plugins. This means that you have a single repository
which contains all your plugins, each in its own subdirectory. This makes it easy to manage dependencies between plugins,
and to share common code such as utility functions & dev tooling.

You can have a single repository for your plugin. For this scenario you can use
the [Vendure Plugin Template](https://github.com/vendure-ecommerce/plugin-template) as a starting point.
Even if you only have a single plugin at the moment, it's a good idea to set up your project in this way from the start.

**Pros**: simple to set up.
To that end, we provide a [monorepo plugin starter template](https://github.com/vendure-ecommerce/plugin-template)
which you can use as a starting point for your plugin development.

**Cons**: if you have multiple plugins, you'll have multiple repositories to manage with duplicated setup and configuration.
This starter template includes support for:

#### Monorepo

If you have multiple plugins, you can use a monorepo setup. Tools such as [Lerna](https://lerna.js.org/) or
[Nx](https://nx.dev/) can help you manage multiple packages in a single repository. A good example of this approach
can be found in the [Pinelab plugins repo](https://github.com/Pinelab-studio/pinelab-vendure-plugins).

**Pros**: single repository to manage; can scale to any number of plugins; can share configuration and tooling.

**Cons**: Initial setup is more complex.
- Development & build scripts already set up
- Admin UI extensions already configured
- End-to-end testing infrastructure fully configured
- Code generation for your schema extensions

### Plugin naming

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/guides/storefront/checkout-flow/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ query GetShippingMethods {
The results can then be displayed to the customer so they can choose the desired shipping method. If there is only a single
result, then it can be automatically selected.

The desired shipping method's id is the passed to the [`setOrderShippingMethod`](/reference/graphql-api/shop/mutations/#setordershippingmethod) mutation.
The desired shipping method's id is then passed to the [`setOrderShippingMethod`](/reference/graphql-api/shop/mutations/#setordershippingmethod) mutation.

```graphql
mutation SetShippingMethod($id: [ID!]!) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface OrderSellerStrategy extends InjectableStrategy {
This method is called whenever a new OrderLine is added to the Order via the `addItemToOrder` mutation or the
underlying `addItemToOrder()` method of the <a href='/reference/typescript-api/services/order-service#orderservice'>OrderService</a>.

It should return the ID of the Channel to which this OrderLine will be assigned, which will be used to set the
It should return the Channel to which this OrderLine will be assigned, which will be used to set the
<a href='/reference/typescript-api/entities/order-line#orderline'>OrderLine</a> `sellerChannel` property.
### splitOrder

Expand Down
24 changes: 24 additions & 0 deletions license/signatures/version1/cla.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@
"created_at": "2024-08-13T15:47:42Z",
"repoId": 136938012,
"pullRequestNo": 3009
},
{
"name": "sphade",
"id": 85949974,
"comment_id": 2304127063,
"created_at": "2024-08-22T08:51:51Z",
"repoId": 136938012,
"pullRequestNo": 3027
},
{
"name": "hsensh",
"id": 23084617,
"comment_id": 2324473830,
"created_at": "2024-09-02T11:14:15Z",
"repoId": 136938012,
"pullRequestNo": 3041
},
{
"name": "neokim",
"id": 3601028,
"comment_id": 2339775064,
"created_at": "2024-09-10T06:26:52Z",
"repoId": 136938012,
"pullRequestNo": 3052
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[typeahead]="couponCodeInput$"
[formControl]="control"
(add)="addCouponCode.emit($event.code)"
(remove)="removeCouponCode.emit($event.value?.code)"
(remove)="remove($event.code)"
>
<ng-template ng-option-tmp let-item="item">
<div class="flex items-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@ export class CouponCodeSelectorComponent implements OnInit {
this.control = new UntypedFormControl(this.couponCodes ?? []);
}
}

remove(code: string) {
this.removeCouponCode.emit(code);
}
}
36 changes: 26 additions & 10 deletions packages/core/e2e/custom-fields.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ const customConfig = mergeConfig(testConfig(), {
readonly: true,
},
],
Collection: [
{ name: 'secretKey1', type: 'string', defaultValue: '', public: false, internal: true },
{ name: 'secretKey2', type: 'string', defaultValue: '', public: false, internal: false },
],
OrderLine: [{ name: 'validateInt', type: 'int', min: 0, max: 10 }],
} as CustomFields,
});
Expand Down Expand Up @@ -942,6 +946,20 @@ describe('Custom fields', () => {
`);
}, 'Cannot query field "internalString" on type "ProductCustomFields"'),
);

// https://github.com/vendure-ecommerce/vendure/issues/3049
it('does not leak private fields via JSON type', async () => {
const { collection } = await shopClient.query(gql`
query {
collection(id: "T_1") {
id
customFields
}
}
`);

expect(collection.customFields).toBe(null);
});
});

describe('sort & filter', () => {
Expand Down Expand Up @@ -1087,18 +1105,16 @@ describe('Custom fields', () => {

describe('unique constraint', () => {
it('setting unique value works', async () => {
const result = await adminClient.query(
gql`
mutation {
updateProduct(input: { id: "T_1", customFields: { uniqueString: "foo" } }) {
id
customFields {
uniqueString
}
const result = await adminClient.query(gql`
mutation {
updateProduct(input: { id: "T_1", customFields: { uniqueString: "foo" } }) {
id
customFields {
uniqueString
}
}
`,
);
}
`);

expect(result.updateProduct.customFields.uniqueString).toBe('foo');
});
Expand Down
16 changes: 5 additions & 11 deletions packages/core/e2e/draft-order.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { LanguageCode } from '@vendure/common/lib/generated-types';
import {
DefaultLogger,
DefaultOrderPlacedStrategy,
mergeConfig,
Order,
Expand All @@ -15,7 +14,7 @@ import path from 'path';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';

import { singleStageRefundablePaymentMethod } from './fixtures/test-payment-methods';
import { ORDER_WITH_LINES_FRAGMENT } from './graphql/fragments';
Expand All @@ -24,14 +23,12 @@ import {
AddManualPaymentDocument,
AdminTransitionDocument,
CanceledOrderFragment,
GetOrderDocument,
GetOrderPlacedAtDocument,
OrderWithLinesFragment,
} from './graphql/generated-e2e-admin-types';
import {
GetActiveCustomerOrdersQuery,
TestOrderFragmentFragment,
TransitionToStateDocument,
UpdatedOrderFragment,
} from './graphql/generated-e2e-shop-types';
import { CREATE_PROMOTION, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
Expand All @@ -53,7 +50,6 @@ class TestOrderPlacedStrategy extends DefaultOrderPlacedStrategy {
describe('Draft Orders resolver', () => {
const { server, adminClient, shopClient } = createTestEnvironment(
mergeConfig(testConfig(), {
logger: new DefaultLogger(),
paymentOptions: {
paymentMethodHandlers: [singleStageRefundablePaymentMethod],
},
Expand Down Expand Up @@ -125,9 +121,8 @@ describe('Draft Orders resolver', () => {
});

it('create draft order', async () => {
const { createDraftOrder } = await adminClient.query<Codegen.CreateDraftOrderMutation>(
CREATE_DRAFT_ORDER,
);
const { createDraftOrder } =
await adminClient.query<Codegen.CreateDraftOrderMutation>(CREATE_DRAFT_ORDER);

expect(createDraftOrder.state).toBe('Draft');
expect(createDraftOrder.active).toBe(false);
Expand Down Expand Up @@ -213,9 +208,8 @@ describe('Draft Orders resolver', () => {
it('custom does not see draft orders in history', async () => {
await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');

const { activeCustomer } = await shopClient.query<GetActiveCustomerOrdersQuery>(
GET_ACTIVE_CUSTOMER_ORDERS,
);
const { activeCustomer } =
await shopClient.query<GetActiveCustomerOrdersQuery>(GET_ACTIVE_CUSTOMER_ORDERS);

expect(activeCustomer?.orders.totalItems).toBe(0);
expect(activeCustomer?.orders.items.length).toBe(0);
Expand Down
33 changes: 27 additions & 6 deletions packages/core/src/api/config/configure-graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from 'path';

import { ConfigModule } from '../../config/config.module';
import { ConfigService } from '../../config/config.service';
import { AutoIncrementIdStrategy, EntityIdStrategy, UuidIdStrategy } from '../../config/index';
import { I18nModule } from '../../i18n/i18n.module';
import { I18nService } from '../../i18n/i18n.service';
import { getPluginAPIExtensions } from '../../plugin/plugin-metadata';
Expand Down Expand Up @@ -96,6 +97,24 @@ async function createGraphQLOptions(
options.apiType,
builtSchema,
);

const apolloServerPlugins = [
new TranslateErrorsPlugin(i18nService),
new AssetInterceptorPlugin(configService),
...configService.apiOptions.apolloServerPlugins,
];
// We only need to add the IdCodecPlugin if the user has configured
// a non-default EntityIdStrategy. This is a performance optimization
// that prevents unnecessary traversal of each response when no
// actual encoding/decoding is taking place.
if (
!isUsingDefaultEntityIdStrategy(
configService.entityOptions.entityIdStrategy ?? configService.entityIdStrategy,
)
) {
apolloServerPlugins.unshift(new IdCodecPlugin(idCodecService));
}

return {
path: '/' + options.apiPath,
typeDefs: printSchema(builtSchema),
Expand All @@ -112,12 +131,7 @@ async function createGraphQLOptions(
context: (req: any) => req,
// This is handled by the Express cors plugin
cors: false,
plugins: [
new IdCodecPlugin(idCodecService),
new TranslateErrorsPlugin(i18nService),
new AssetInterceptorPlugin(configService),
...configService.apiOptions.apolloServerPlugins,
],
plugins: apolloServerPlugins,
validationRules: options.validationRules,
introspection: configService.apiOptions.introspection ?? true,
} as ApolloDriverConfig;
Expand Down Expand Up @@ -165,3 +179,10 @@ async function createGraphQLOptions(
return schema;
}
}

function isUsingDefaultEntityIdStrategy(entityIdStrategy: EntityIdStrategy<any>): boolean {
return (
entityIdStrategy.constructor === AutoIncrementIdStrategy ||
entityIdStrategy.constructor === UuidIdStrategy
);
}
10 changes: 10 additions & 0 deletions packages/core/src/api/config/generate-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ function generateCustomFieldRelationResolvers(
} as any;
}
}
const allCustomFieldsAreNonPublic =
customFields.length && customFields.every(f => f.public === false || f.internal === true);
if (allCustomFieldsAreNonPublic) {
// When an entity has only non-public custom fields, the GraphQL type used for the
// customFields field is `JSON`. This type will simply return the full object, which
// will cause a leak of private data unless we force a `null` return value in the case
// that there are no public fields.
// See https://github.com/vendure-ecommerce/vendure/issues/3049
shopResolvers[entityName] = { customFields: () => null };
}
}
return { adminResolvers, shopResolvers };
}
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/api/resolvers/admin/order.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ import { Transaction } from '../../decorators/transaction.decorator';

@Resolver()
export class OrderResolver {
constructor(private orderService: OrderService, private connection: TransactionalConnection) {}
constructor(
private orderService: OrderService,
private connection: TransactionalConnection,
) {}

@Query()
@Allow(Permission.ReadOrder)
Expand All @@ -63,7 +66,8 @@ export class OrderResolver {
async order(
@Ctx() ctx: RequestContext,
@Args() args: QueryOrderArgs,
@Relations(Order) relations: RelationPaths<Order>,
@Relations({ entity: Order, omit: ['aggregateOrder', 'sellerOrders'] })
relations: RelationPaths<Order>,
): Promise<Order | undefined> {
return this.orderService.findOne(ctx, args.id, relations);
}
Expand Down
Loading

0 comments on commit 6a11b30

Please sign in to comment.