Skip to content

docs: update handling custom occ endpoints recipe #7405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 25, 2025
Merged
Changes from all commits
Commits
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
169 changes: 90 additions & 79 deletions docs/content/cookbook/handling-custom-occ-endpoint.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
# Handling custom OCC endpoints

It is a common task to add support for a custom (non-standard) SAP OCC API endpoint not covered by the Alokai intergration.
This guide will show you how can do it using Alokai.
There are two ways how you can do it:

## Prerequisites
1. Generate new API client based on OpenAPI (swagger) specification.
2. Add support for a custom endpoint manually.

Before we start make sure that you are familiar with [Adding New API Methods](https://docs.alokai.com/storefront/integration-and-setup/storefront-extension#adding-new-api-methods) guide. With that guide, you would be able to
## Generating new API client

Generation of new API client allows you to add support for all custom endpoints at once. You just run a script and all the endpoints will be added to the integration.

How to do this is described in the [Generated API guide](/integrations/sapcc/features/generated-api).

This should be your default approach, because it is the most scalable and maintainable way.

## Adding support for a custom endpoint manually

If you don't want or cannot generate a new API client, you can add support for a custom endpoint manually.
It boils down to adding a new API method to the middleware that calls the custom endpoint. This guide shows how to do it efficiently.

When to use this approach?

- When for some reason you cannot generate a new API client, e.g. OpenAPI specification is not available.
- When you are iterating fast both on the API and the front-end and you don't want to regenerate the API client for each change.

### Prerequisites

Before we start make sure that you are familiar with [Adding New API Methods](/unified-data-layer/integration-and-setup/creating-new-api-methods) guide. With that guide, you would be able to
communicate with OCC API but it would require manual retrieval of context parameters (baseSiteId, userId, language,
and currency) and preparation of authorization headers. Read on to see how to streamline that process.

## Communicating with OCC API effectively
### Communicating with OCC API effectively

OCC endpoints have a given structure

Expand All @@ -35,74 +56,69 @@ Here's where to find the parameters:

- `baseUrl` - is configured in .env file as `SAPCC_API_URI`. The api client already knows it and prepends each URL with it.
- `baseSiteId` - is defined in the middleware configuration. That configuration is exposed to api method via context.
- `userId` - can be found in the request cookies under [`AUTH_USER_COOKIE_NAME`](https://docs.alokai.com/integrations/sapcc/api/sapcc-api/AUTH_USER_COOKIE_NAME).
- `language` - can be found in the request cookies under [`VSF_LOCALE_COOKIE`](https://docs.alokai.com/storefront/features/internationalization/internatialization-support).
- `currency` - can be found in the request cookies under [`VSF_CURRENCY_COOKIE`](https://docs.alokai.com/storefront/features/internationalization/currency-switching).
- authorization token - can be found in the request cookies under [`AUTH_USER_TOKEN_COOKIE_NAME`](https://docs.alokai.com/integrations/sapcc/api/sapcc-api/AUTH_USER_TOKEN_COOKIE_NAME)
- `userId` - can be found in the request cookies under [`AUTH_USER_COOKIE_NAME`](/integrations/sapcc/api/sapcc-api/AUTH_USER_COOKIE_NAME).
- `language` - can be found in the request cookies under [`VSF_LOCALE_COOKIE`](/storefront/features/internationalization/internatialization-support).
- `currency` - can be found in the request cookies under [`VSF_CURRENCY_COOKIE`](/storefront/features/internationalization/currency-switching).
- authorization token - can be found in the request cookies under [`AUTH_USER_TOKEN_COOKIE_NAME`](/integrations/sapcc/api/sapcc-api/AUTH_USER_TOKEN_COOKIE_NAME)

You don't have to parse the cookies yourself. Alokai provides helper methods for that. Here’s a code example of how to do it:

```typescript
import {
SapccIntegrationContext,
TokenModes,
createRequestOptions,
getUserIdFromRequest,
} from "@vsf-enterprise/sapcc-api";
import { BaseProps, BaseUserId } from "@vsf-enterprise/sapcc-types";
```typescript [apps/storefront-middleware/api/custom-methods/types.ts]
import { BaseProps, BaseUserId } from '@vsf-enterprise/sapcc-types';

export interface CustomEndpointProps extends BaseProps, BaseUserId {
export interface CustomMethodArgs extends BaseProps, BaseUserId {
customField: any;
}

export interface CustomResponse {
export interface CustomMethodResponse {
whatever: any;
}
```

```typescript [apps/storefront-middleware/api/custom-methods/custom.ts]
import { createRequestOptions, getUserIdFromRequest, TokenModes } from '@vsf-enterprise/sapcc-api';
import { type IntegrationContext } from '../../types';
import type { CustomMethodArgs, CustomMethodResponse } from './types';

const callCustomEndpoint = async (
context: SapccIntegrationContext,
props: CustomEndpointProps
): Promise<CustomResponse> => {
export async function exampleCustomMethod(
context: IntegrationContext,
args: CustomMethodArgs,
): Promise<CustomMethodResponse> {
const { config, req, client } = context;

const userId = getUserIdFromRequest({ req, props } as any); // retrieves userID from props or cookies
const userId = getUserIdFromRequest({ context, props: args }); // retrieves userID from props or cookies

const res = await client.get(
`/${config.api.baseSiteId}/users/${userId}/customEndpoint/${props.customField}`,
`/${config.api.baseSiteId}/users/${userId}/customEndpoint/${args.customField}`,
createRequestOptions({
// adds authorization headers and language & currency parameters
context,
props,
props: args,
tokenMode: TokenModes.CUSTOMERORAPPLICATION,
})
}),
);

return res.data;
};
}

```

Read more about the helper methods:

- [getUserIdFromRequest](https://docs.alokai.com/integrations/sapcc/api/sapcc-api/getUserIdFromRequest)
- [createRequestOptions](https://docs.alokai.com/integrations/sapcc/api/sapcc-api/createRequestOptions)
- [getUserIdFromRequest](/integrations/sapcc/api/sapcc-api/getUserIdFromRequest)
- [createRequestOptions](/integrations/sapcc/api/sapcc-api/createRequestOptions)

## Real life example
### Real life example

Here's an example implementation of the product interest feature. This feature is available in OCC API, but not in the middleware and SDK integration.
Here's an example implementation of the product interest feature.
Let's add support for it.

First, you need to add a new API method in the middleware. (For simplicity, this guide shows how to do it in one file, but we recommend splitting it into multiple files to maintain cleaner code.)
First, you need to add a new API method in the middleware.

```typescript [storefront-middleware/middleware.config.ts]
import {
SapccIntegrationContext,
TokenModes,
createRequestOptions,
getUserIdFromRequest,
} from "@vsf-enterprise/sapcc-api";
import { BaseProps, BaseUserId, Product } from "@vsf-enterprise/sapcc-types";
```typescript [apps/storefront-middleware/api/custom-methods/types.ts]
import { BaseProps, BaseUserId, Product } from '@vsf-enterprise/sapcc-types';

export interface GetProductInterestsProps extends BaseProps, BaseUserId {
export interface GetProductInterestsArgs extends BaseProps, BaseUserId {
productCode?: string;
}
export interface ProductInterestEntry {
Expand All @@ -118,66 +134,61 @@ export interface UserInterestsResponse {
results: Array<ProductInterestRelation>;
}

const getProductInterests = async (
context: SapccIntegrationContext,
props: GetProductInterestsProps
): Promise<UserInterestsResponse> => {
const { config, req, client } = context;
```

const userId = getUserIdFromRequest({ req, props } as any);
```typescript [apps/storefront-middleware/api/custom-methods/custom.ts]
import { createRequestOptions, getUserIdFromRequest, TokenModes } from '@vsf-enterprise/sapcc-api';
import { type IntegrationContext } from '../../types';
import type { GetProductInterestsArgs, UserInterestsResponse } from './types';

export async function getProductInterests(
context: IntegrationContext,
args: GetProductInterestsArgs,
): Promise<UserInterestsResponse> {
const { config, client } = context;

const userId = getUserIdFromRequest({ context, props: args });

const requestOptions = createRequestOptions({
context,
props,
props: args,
tokenMode: TokenModes.CUSTOMERORAPPLICATION,
});

const res = await client.get(
`/${config.api.baseSiteId}/users/${userId}/productinterests`,
{
...requestOptions,
params: {
...requestOptions.params,
productCode: props.productCode,
},
}
);
const res = await client.get(`/${config.api.baseSiteId}/users/${userId}/productinterests`, {
...requestOptions,
params: {
...requestOptions.params,
productCode: args.productCode,
},
});

return res.data;
};

const apiMethods = methods<typeof normalizers>();
const unifiedApiExtension = createUnifiedExtension<Context, Config>()({
normalizers,
apiMethods: {
...apiMethods,
getProductInterests,
},
config: {
/* ... */
},
});
}
```

```typescript [apps/storefront-middleware/api/custom-methods/index.ts]
export { getProductInterests } from './custom';
export * from './types';
```

Then, in your frontend application, you need to add a custom hook to retrieve the product interests on the front end.

```typescript [storefront-unified-nextjs/hooks/useProductInterests/useProductInterests.ts]
import { useQuery } from "@tanstack/react-query";
import { InferSdkArgs, useSdk } from "~/sdk";
import { useQuery } from '@tanstack/react-query';

export type GetProductInterestsArgs = InferSdkArgs<"getProductInterests">;
import { useSdk } from '@/sdk/alokai-context';

export function useProductInterests({ productCode }: GetProductInterestsArgs) {
export function useProductInterests({ productCode }: { productCode: string }) {
const sdk = useSdk();

return useQuery({
queryKey: ["product interests", productCode],
queryFn: () =>
sdk.unified.getProductInterests({
sdk.customExtension.getProductInterests({
productCode,
}),
refetchOnMount: false,
refetchOnWindowFocus: false,
queryKey: ['product interests', productCode],
});
}

```
Loading