Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@
title: "RequestContextService"
generated: true
---
<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="25" packageName="@vendure/core" />
## RequestContextService

<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="58" packageName="@vendure/core" />

Creates new [RequestContext](/reference/typescript-api/request/request-context#requestcontext) instances.

```ts title="Signature"
class RequestContextService {
create(config: {
req?: Request;
apiType: ApiType;
channelOrToken?: Channel | string;
languageCode?: LanguageCode;
currencyCode?: CurrencyCode;
user?: User;
activeOrderId?: ID;
}) => Promise<RequestContext>;
createDefaultContext(config?: CreateDefaultRequestContextConfig) => Promise<RequestContext>;
create(config: CreateRequestContextConfig) => Promise<RequestContext>;
fromRequest(req: Request, info?: GraphQLResolveInfo, requiredPermissions?: Permission[], session?: CachedSession) => Promise<RequestContext>;
}
```

<div className="members-wrapper">

### createDefaultContext

<MemberInfo kind="method" type={`(config?: <a href='/reference/typescript-api/request/request-context-service#createdefaultrequestcontextconfig'>CreateDefaultRequestContextConfig</a>) => Promise<<a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>>`} since="3.6.5" />

Creates a RequestContext based on the default Channel. This is useful when a service method
must be called outside the normal request-response cycle and needs a context that has a valid
`languageCode` derived from the default Channel's configuration.
### create

<MemberInfo kind="method" type={`(config: { req?: Request; apiType: <a href='/reference/typescript-api/request/api-type#apitype'>ApiType</a>; channelOrToken?: <a href='/reference/typescript-api/entities/channel#channel'>Channel</a> | string; languageCode?: <a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>; currencyCode?: <a href='/reference/typescript-api/common/currency-code#currencycode'>CurrencyCode</a>; user?: <a href='/reference/typescript-api/entities/user#user'>User</a>; activeOrderId?: <a href='/reference/typescript-api/common/id#id'>ID</a>; }) => Promise<<a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>>`} since="1.5.0" />
<MemberInfo kind="method" type={`(config: <a href='/reference/typescript-api/request/request-context-service#createrequestcontextconfig'>CreateRequestContextConfig</a>) => Promise<<a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>>`} since="1.5.0" />

Creates a RequestContext based on the config provided. This can be useful when interacting
with services outside the request-response cycle, for example in stand-alone scripts or in
Expand All @@ -39,4 +41,74 @@ in the API layer by the AuthGuard, and creates the RequestContext which is then
to all resolvers & controllers.


</div>
## CreateDefaultRequestContextConfig

<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="28" packageName="@vendure/core" since="3.6.5" />

Optional overrides passed to `RequestContextService.createDefaultContext`. This is
effectively an all-optional version of the [RequestContext](/reference/typescript-api/request/request-context#requestcontext) constructor parameters.

```ts title="Signature"
type CreateDefaultRequestContextConfig = Partial<ConstructorParameters<typeof RequestContext>[0]>
```
## CreateRequestContextConfig

<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="40" packageName="@vendure/core" since="1.5.0" />

Configuration accepted by `RequestContextService.create`. Used to construct a
RequestContext outside of the normal request-response cycle, e.g. in stand-alone scripts
or worker jobs.

```ts title="Signature"
interface CreateRequestContextConfig {
req?: Request;
apiType: ApiType;
channelOrToken?: Channel | string;
languageCode?: LanguageCode;
currencyCode?: CurrencyCode;
user?: User;
activeOrderId?: ID;
}
```

<div className="members-wrapper">

### req

<MemberInfo kind="property" type={`Request`} />


### apiType

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/request/api-type#apitype'>ApiType</a>`} />


### channelOrToken

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/channel#channel'>Channel</a> | string`} />


### languageCode

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>`} />


### currencyCode

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/currency-code#currencycode'>CurrencyCode</a>`} />


### user

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/user#user'>User</a>`} />


### activeOrderId

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/id#id'>ID</a>`} />




</div>
22 changes: 20 additions & 2 deletions packages/core/e2e/asset.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { DeletionResult, LogicalOperator, SortOrder } from '@vendure/common/lib/generated-types';
import { omit } from '@vendure/common/lib/omit';
import { pick } from '@vendure/common/lib/pick';
import { mergeConfig } from '@vendure/core';
import { AssetService, mergeConfig } from '@vendure/core';
import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
import fs from 'fs-extra';
import path from 'node:path';
import { Readable } from 'node:stream';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
Expand All @@ -25,7 +26,7 @@ describe('Asset resolver', () => {
const { server, adminClient } = createTestEnvironment(
mergeConfig(testConfig(), {
assetOptions: {
permittedFileTypes: ['image/*', '.pdf', '.zip'],
permittedFileTypes: ['image/*', '.pdf', '.zip', '.md'],
},
}),
);
Expand Down Expand Up @@ -358,6 +359,23 @@ describe('Asset resolver', () => {
fs.rmSync(filename);
}
});

// https://github.com/vendurehq/vendure/issues/4651
it('create an asset from a file stream without RequestContext', async () => {
const assetService = server.app.get(AssetService);

const stream = new Readable({ encoding: 'utf-8' });
stream.push('test file content');
stream.push(null);

const result = await assetService.createFromFileStream(stream, 'test-file.md');

expect(result).toBeDefined();
expect('id' in result).toBe(true);
if ('id' in result) {
expect(result.name).toBe('test-file.md');
}
});
});

describe('filter by tags', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,44 @@ import { User } from '../../../entity/user/user.entity';
import { ChannelService } from '../../services/channel.service';
import { getUserChannelsPermissions } from '../utils/get-user-channels-permissions';

/**
* @description
* Optional overrides passed to {@link RequestContextService.createDefaultContext}. This is
* effectively an all-optional version of the {@link RequestContext} constructor parameters.
*
* @docsCategory request
* @docsPage RequestContextService
* @since 3.6.5
*/
export type CreateDefaultRequestContextConfig = Partial<ConstructorParameters<typeof RequestContext>[0]>;

/**
* @description
* Configuration accepted by {@link RequestContextService.create}. Used to construct a
* RequestContext outside of the normal request-response cycle, e.g. in stand-alone scripts
* or worker jobs.
*
* @docsCategory request
* @docsPage RequestContextService
* @since 1.5.0
*/
export interface CreateRequestContextConfig {
req?: Request;
apiType: ApiType;
channelOrToken?: Channel | string;
languageCode?: LanguageCode;
currencyCode?: CurrencyCode;
user?: User;
activeOrderId?: ID;
}

/**
* @description
* Creates new {@link RequestContext} instances.
*
* @docsCategory request
* @docsPage RequestContextService
* @docsWeight 0
*/
@Injectable()
export class RequestContextService {
Expand All @@ -30,6 +63,28 @@ export class RequestContextService {
private configService: ConfigService,
) {}

/**
* @description
* Creates a RequestContext based on the default Channel. This is useful when a service method
* must be called outside the normal request-response cycle and needs a context that has a valid
* `languageCode` derived from the default Channel's configuration.
* @param config optional manual configuration overwriting the default values
*
* @since 3.6.5
*/
async createDefaultContext(config?: CreateDefaultRequestContextConfig): Promise<RequestContext> {
const channel = await this.channelService.getDefaultChannel();
return new RequestContext({
apiType: 'admin',
channel,
isAuthorized: true,
authorizedAsOwnerOnly: false,
languageCode: channel.defaultLanguageCode ?? this.configService.defaultLanguageCode,
currencyCode: channel.defaultCurrencyCode,
...config,
});
}

/**
* @description
* Creates a RequestContext based on the config provided. This can be useful when interacting
Expand All @@ -38,15 +93,7 @@ export class RequestContextService {
*
* @since 1.5.0
*/
async create(config: {
req?: Request;
apiType: ApiType;
channelOrToken?: Channel | string;
languageCode?: LanguageCode;
currencyCode?: CurrencyCode;
user?: User;
activeOrderId?: ID;
}): Promise<RequestContext> {
async create(config: CreateRequestContextConfig): Promise<RequestContext> {
const { req, apiType, channelOrToken, languageCode, currencyCode, user, activeOrderId } = config;
let channel: Channel;
if (channelOrToken instanceof Channel) {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/service/services/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { AssetChannelEvent } from '../../event-bus/events/asset-channel-event';
import { AssetEvent } from '../../event-bus/events/asset-event';
import { CustomFieldRelationService } from '../helpers/custom-field-relation/custom-field-relation.service';
import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
import { RequestContextService } from '../helpers/request-context/request-context.service';
import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
import { TranslatorService } from '../helpers/translator/translator.service';
import { patchEntity } from '../helpers/utils/patch-entity';
Expand Down Expand Up @@ -106,6 +107,7 @@ export class AssetService {
private customFieldRelationService: CustomFieldRelationService,
private readonly translatableSaver: TranslatableSaver,
private readonly translator: TranslatorService,
private readonly requestContextService: RequestContextService,
) {
this.permittedMimeTypes = this.configService.assetOptions.permittedFileTypes
.map(val => (/\.[\w]+/.test(val) ? mime.lookup(val) || undefined : val))
Expand Down Expand Up @@ -505,7 +507,7 @@ export class AssetService {
? maybeFilePathOrCtx
: maybeCtx instanceof RequestContext
? maybeCtx
: RequestContext.empty();
: await this.requestContextService.createDefaultContext();
const result = await this.createAssetInternal(ctx, stream, filename, mimetype);
if (isGraphQlErrorResult(result)) {
return result;
Expand Down
Loading