Skip to content

Commit

Permalink
feat: increments commerce client (#2647)
Browse files Browse the repository at this point in the history
## What's the purpose of this pull request?

To add the Reviews & Ratings API integration through commerce client

## How it works?

Adds 3 new calls to the client:
- client.commerce.rating (retrieves rating information for a specific
product)
- client.commerce.reviews.list (retrieves all reviews for a specific
product)
- client.commerce.reviews.create (creates a new review for a specific
product)

## How to test it?

Creates a `.ts` file on the root folder of the project and adds the
following code:
```typescript
import { getContextFactory, Options } from "./packages/api/src/platforms/vtex";

const apiOptions = {
  platform: 'vtex',
  account: 'storeframework',
  locale: 'en-US',
  environment: 'vtexcommercestable',
  channel: '{"salesChannel":"1"}',
  showSponsored: false,
} as Options

const apiCtx = getContextFactory(apiOptions)

const commerceApiClient = apiCtx({}).clients.commerce
```

After that you can use the `commerceApiClient` to call the new methods.

To run the file locally use the following command:
```bash
npx tsx 
```

## References

[JIRA Task: SFS-2092](https://vtex-dev.atlassian.net/browse/SFS-2092)
[Reviews & Ratings API
Doc](https://developers.vtex.com/docs/api-reference/reviews-and-ratings-api#overview)

## Checklist

**PR Description**

- [ ] Added Rating types
- [ ] Added Reviews types
- [ ] Incremented ProductSearchReviewResult
- [ ] Created adapatObject function on `utils`
- [ ] Created camelToSnakeCase function on `utils`
  • Loading branch information
Guilera authored and artursantiago committed Feb 7, 2025
1 parent 60fdaec commit c678ed4
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 0 deletions.
53 changes: 53 additions & 0 deletions packages/api/src/platforms/vtex/clients/commerce/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import type { MasterDataResponse } from './types/Newsletter'
import type { Address, AddressInput } from './types/Address'
import type { DeliveryMode, SelectedAddress } from './types/ShippingData'
import { getStoreCookie, getWithCookie } from '../../utils/cookies'
import type { ProductRating } from './types/ProductRating'
import type {
CreateProductReviewInput,
ProductReviewsInput,
ProductReviewsResult,
} from './types/ProductReview'
import { adaptObject } from '../../utils/adaptObject'
import { camelToSnakeCase } from '../../utils/camelToSnakeCase'

type ValueOf<T> = T extends Record<string, infer K> ? K : never

Expand All @@ -30,6 +38,8 @@ const BASE_INIT = {
},
}

const REVIEWS_AND_RATINGS_API_PATH = 'api/io/reviews-and-ratings/api'

export const VtexCommerce = (
{ account, environment, incrementAddress, subDomainPrefix }: Options,
ctx: Context
Expand Down Expand Up @@ -364,5 +374,48 @@ export const VtexCommerce = (
{ storeCookies }
)
},
rating: (productId: string): Promise<ProductRating> => {
return fetchAPI(
`${base}/${REVIEWS_AND_RATINGS_API_PATH}/rating/${productId}`,
undefined,
{ storeCookies }
)
},
reviews: {
create: (input: CreateProductReviewInput): Promise<string> => {
return fetchAPI(
`${base}/${REVIEWS_AND_RATINGS_API_PATH}/review`,
{
...BASE_INIT,
body: JSON.stringify(input),
method: 'POST',
},
{ storeCookies }
)
},
list: ({
orderBy,
orderWay,
...partialInput
}: ProductReviewsInput): Promise<ProductReviewsResult> => {
const formattedInput = adaptObject<string>(
{
orderBy: orderBy ? `${orderBy}:${orderWay ?? 'asc'}` : undefined,
...partialInput,
},
(_, value) => value !== undefined,
camelToSnakeCase,
String
)

const params = new URLSearchParams(formattedInput)

return fetchAPI(
`${base}/${REVIEWS_AND_RATINGS_API_PATH}/reviews?${params.toString()}`,
undefined,
{ storeCookies }
)
},
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ProductRating {
average: number
totalCount: number
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export interface ProductReview {
id: string
productId: string
rating: number
title: string
text: string
reviewerName: string
shopperId: string
reviewDateTime: string
searchDate: string
verifiedPurchaser: boolean
sku: string | null
approved: boolean
location: string | null
locale: string | null
pastReviews: string | null
}

export enum ProductReviewsInputOrderBy {
productId = 'ProductId',
shopperId = 'ShopperId',
approved = 'Approved',
reviewDateTime = 'ReviewDateTime',
searchDate = 'SearchDate',
rating = 'Rating',
locale = 'Locale',
}

export interface ProductReviewsInput {
searchTerm?: string
from?: number
to?: number
orderBy?: ProductReviewsInputOrderBy
orderWay?: 'asc' | 'desc'
status?: boolean
productId?: string
rating?: number
}

export interface ProductReviewsResult {
data: ProductReview[]
range: {
from: number
to: number
total: number
}
}

export interface CreateProductReviewInput {
productId: string
rating: number
title: string
text: string
reviewerName: string
approved: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export interface Product {
selectedProperties: Array<{ key: string; value: string }>
releaseDate: string
advertisement?: Advertisement
rating: {
average: number
totalCount: number
}
}

interface Image {
Expand Down
39 changes: 39 additions & 0 deletions packages/api/src/platforms/vtex/utils/adaptObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Transforms an object's keys and values based on provided formatters and a predicate filter.
*
* @template T - The type of the transformed values.
* @param obj - The object to transform.
* @param predicate - A predicate function that determines whether a key-value pair should be included in the output.
* @param keyFormatter - A function that formats the object keys. Defaults to returning the key as is.
* @param valueFormatter - A function that formats the object values. Defaults to returning the value as is.
* @returns A new object with transformed keys and values, including only the key-value pairs that satisfy the predicate.
*
* @example <caption>Select all keys that have a defined value and also makes all keys uppercase and all values as numbers</caption>
* ```ts
* const obj = { john: "25", will: "10", bob: undefined };
* const result = adaptObject<number>(
* obj,
* (key, value) => value !== undefined,
* key => key.toUpperCase(),
* Integer.parseInt
* );
* console.log(result); // { JOHN: 25, WILL: 10 }
* ```
*/
export function adaptObject<T>(
obj: Record<string, unknown>,
predicate: (key: string, value: unknown) => boolean,
keyFormatter: (key: string) => string = (key) => key,
valueFormatter: (value: unknown) => T = (value) => value as T
): Record<string, T> {
return Object.entries(obj).reduce(
(acc, [key, value]) => {
if (predicate(key, value)) {
acc[keyFormatter(key)] = valueFormatter(value)
}

return acc
},
{} as Record<string, T>
)
}
3 changes: 3 additions & 0 deletions packages/api/src/platforms/vtex/utils/camelToSnakeCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function camelToSnakeCase(str: string): string {
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
}

0 comments on commit c678ed4

Please sign in to comment.