[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/checkout.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable max-params */
import type {
  InstanceOptions,
  IOContext,
  IOResponse,
  RequestConfig,
} from '@vtex/api'
import { JanusClient } from '@vtex/api'
import type { AxiosError } from 'axios'

import { checkoutCookieFormat, statusToError } from '../utils'

export class Checkout extends JanusClient {
  private get routes() {
    const base = '/api/checkout/pub'

    return {
      addItem: (orderFormId: string, queryString: string) =>
        `${base}/orderForm/${orderFormId}/items${queryString}`,
      cancelOrder: (orderFormId: string) =>
        `${base}/orders/${orderFormId}/user-cancel-request`,
      orderFormCustomData: (
        orderFormId: string,
        appId: string,
        field: string
      ) => `${base}/orderForm/${orderFormId}/customData/${appId}/${field}`,
      updateItems: (orderFormId: string) =>
        `${base}/orderForm/${orderFormId}/items/update`,
      profile: (orderFormId: string) =>
        `${base}/orderForm/${orderFormId}/profile`,
      attachmentsData: (orderFormId: string, field: string) =>
        `${base}/orderForm/${orderFormId}/attachments/${field}`,
      attachmentItem: (
        orderFormId: string,
        field: string,
        index: string | number
      ) =>
        `${base}/orderForm/${orderFormId}/items/${index}/attachments/${field}`,
      assemblyOptions: (
        orderFormId: string,
        itemId: string | number,
        assemblyOptionsId: string
      ) =>
        `${base}/orderForm/${orderFormId}/items/${itemId}/assemblyOptions/${assemblyOptionsId}`,
      checkin: (orderFormId: string) =>
        `${base}/orderForm/${orderFormId}/checkIn`,
      orderForm: (orderFormId?: string) =>
        `${base}/orderForm/${orderFormId ?? ''}`,
      orders: `${base}/orders`,
      simulation: (queryString: string) =>
        `${base}/orderForms/simulation${queryString}`,
      changeToAnonymousUser: (orderFormId: string) =>
        `/checkout/changeToAnonymousUser/${orderFormId}`,
    }
  }

  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...options?.headers,
        ...(ctx.storeUserAuthToken
          ? { VtexIdclientAutCookie: ctx.storeUserAuthToken }
          : { VtexIdclientAutCookie: ctx.authToken }),
        'x-vtex-user-agent': ctx.userAgent,
      },
    })
  }

  public addItem = (orderFormId: string, items: any) =>
    this.post<OrderForm>(
      this.routes.addItem(orderFormId, this.getChannelQueryString()),
      { orderItems: items },
      { metric: 'checkout-addItem' }
    )

  public cancelOrder = (orderFormId: string, reason: string) =>
    this.post(
      this.routes.cancelOrder(orderFormId),
      { reason },
      { metric: 'checkout-cancelOrder' }
    )

  public setOrderFormCustomData = (
    orderFormId: string,
    appId: string,
    field: string,
    value: any
  ) =>
    this.put(
      this.routes.orderFormCustomData(orderFormId, appId, field),
      { value },
      { metric: 'checkout-setOrderFormCustomData' }
    )

  public updateItems = (orderFormId: string, orderItems: any) =>
    this.post(
      this.routes.updateItems(orderFormId),
      { orderItems },
      { metric: 'checkout-updateItems' }
    )

  public updateOrderFormIgnoreProfile = (
    orderFormId: string,
    ignoreProfileData: boolean
  ) =>
    this.patch(
      this.routes.profile(orderFormId),
      { ignoreProfileData },
      { metric: 'checkout-updateOrderFormIgnoreProfile' }
    )

  public updateOrderFormPayment = (orderFormId: string, payments: any) =>
    this.post(
      this.routes.attachmentsData(orderFormId, 'paymentData'),
      { payments },
      { metric: 'checkout-updateOrderFormPayment' }
    )

  public updateOrderFormProfile = (orderFormId: string, fields: any) =>
    this.post(
      this.routes.attachmentsData(orderFormId, 'clientProfileData'),
      fields,
      { metric: 'checkout-updateOrderFormProfile' }
    )

  public updateOrderFormShipping = (orderFormId: string, shipping: any) =>
    this.post(
      this.routes.attachmentsData(orderFormId, 'shippingData'),
      shipping,
      { metric: 'checkout-updateOrderFormShipping' }
    )

  public updateOrderFormMarketingData = (
    orderFormId: string,
    marketingData: any
  ) =>
    this.post(
      this.routes.attachmentsData(orderFormId, 'marketingData'),
      marketingData,
      { metric: 'checkout-updateOrderFormMarketingData' }
    )

  public async clearCart(orderFormId: string) {
    return this.post(
      `${this.routes.orderForm(orderFormId)}/items/removeAll`,
      {}
    )
  }

  public updateSalesChannel = async (
    orderFormId: string,
    salesChannel: any
  ) => {
    const orderForm = await this.get(this.routes.orderForm(orderFormId))

    const { items, salesChannel: sc }: any = orderForm

    if (String(salesChannel) !== String(sc) && items?.length) {
      await this.clearCart(orderFormId)

      await this.post(
        `${this.routes.orderForm(orderFormId)}/items?sc=${salesChannel}`,
        {
          orderItems: items.map((item: any) => ({
            id: item.id,
            index: item.index,
            price: item.price,
            quantity: item.quantity,
            seller: item.seller,
          })),
        }
      )

      // Check attachments
      const attachmentPromise: any[] = []

      items.forEach((item: { attachments: any[] }, index: string | number) => {
        if (item.attachments?.length) {
          item.attachments.forEach(
            (attachment: { name: string; content: any }) => {
              attachmentPromise.push(
                this.post(
                  this.routes.attachmentItem(
                    orderFormId,
                    attachment.name,
                    index
                  ),
                  {
                    content: attachment.content,
                  }
                )
              )
            }
          )
        }
      })
      if (attachmentPromise.length) {
        await Promise.all(attachmentPromise)
      }
    }

    return orderForm
  }

  public updateOrderFormClientPreferencesData = (
    orderFormId: string,
    clientPreferencesData: OrderFormClientPreferencesData
  ) => {
    // The API default value of `optinNewsLetter` is `null`, but it doesn't accept a POST with its value as `null`
    const filteredClientPreferencesData =
      clientPreferencesData.optinNewsLetter === null
        ? { locale: clientPreferencesData.locale }
        : clientPreferencesData

    return this.post(
      this.routes.attachmentsData(orderFormId, 'clientPreferencesData'),
      filteredClientPreferencesData,
      { metric: 'checkout-updateOrderFormClientPreferencesData' }
    )
  }

  public addAssemblyOptions = async (
    orderFormId: string,
    itemId: string | number,
    assemblyOptionsId: string,
    body: any
  ) =>
    this.post<OrderForm>(
      this.routes.assemblyOptions(orderFormId, itemId, assemblyOptionsId),
      body,
      { metric: 'checkout-addAssemblyOptions' }
    )

  public removeAssemblyOptions = async (
    orderFormId: string,
    itemId: string | number,
    assemblyOptionsId: string,
    body: any
  ) =>
    this.delete<OrderForm>(
      this.routes.assemblyOptions(orderFormId, itemId, assemblyOptionsId),
      { metric: 'checkout-removeAssemblyOptions', data: body }
    )

  public updateOrderFormCheckin = (orderFormId: string, checkinPayload: any) =>
    this.post(this.routes.checkin(orderFormId), checkinPayload, {
      metric: 'checkout-updateOrderFormCheckin',
    })

  public orderForm = (orderFormId?: string) => {
    return this.get<OrderForm>(this.routes.orderForm(orderFormId), {
      metric: 'checkout-orderForm',
    })
  }

  public orderFormRaw = () => {
    return this.postRaw<OrderForm>(
      this.routes.orderForm(),
      { expectedOrderFormSections: ['items'] },
      { metric: 'checkout-orderForm' }
    )
  }

  public newOrderForm = (orderFormId?: string) => {
    return this.http
      .postRaw<OrderForm>(this.routes.orderForm(orderFormId), undefined, {
        metric: 'checkout-newOrderForm',
      })
      .catch(statusToError) as Promise<IOResponse<OrderForm>>
  }

  public changeToAnonymousUser = (orderFormId: string) => {
    return this.get(this.routes.changeToAnonymousUser(orderFormId), {
      metric: 'checkout-change-to-anonymous',
    }).catch((err) => {
      // This endpoint is expected to return a redirect to
      // the user, so we can ignore the error if it is a 3xx
      if (!err.response || /^3..$/.test((err as AxiosError).code ?? '')) {
        throw err
      }
    })
  }

  public orders = () =>
    this.get(this.routes.orders, { metric: 'checkout-orders' })

  public simulation = (simulation: SimulationPayload) =>
    this.post<SimulationOrderForm>(
      this.routes.simulation(this.getChannelQueryString()),
      simulation,
      {
        metric: 'checkout-simulation',
      }
    )

  public getRegionId = (
    country: string,
    postalCode: string | null,
    salesChannel: string,
    geoCoordinates: [number, number] | null
  ): Promise<RegionsResponse[]> => {
    const baseUrl = '/api/checkout/pub/regions'
    const params = new URLSearchParams({
      country,
      sc: salesChannel,
    })

    if (postalCode) {
      params.append('postalCode', postalCode)
    }

    if (geoCoordinates) {
      params.append('geoCoordinates', geoCoordinates.join(';'))
    }

    const url = `${baseUrl}?${params.toString()}`

    return this.get(url)
  }

  protected get = <T>(url: string, config: RequestConfig = {}) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http.get<T>(url, config).catch(statusToError) as Promise<T>
  }

  protected post = <T>(url: string, data?: any, config: RequestConfig = {}) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http
      .post<T>(url, data, config)
      .catch(statusToError) as Promise<T>
  }

  protected postRaw = async <T>(
    url: string,
    data?: any,
    config: RequestConfig = {}
  ) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http
      .postRaw<T>(url, data, config)
      .catch(statusToError) as Promise<IOResponse<T>>
  }

  protected delete = <T>(url: string, config: RequestConfig = {}) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http.delete<T>(url, config).catch(statusToError) as Promise<
      IOResponse<T>
    >
  }

  protected patch = <T>(
    url: string,
    data?: any,
    config: RequestConfig = {}
  ) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http
      .patch<T>(url, data, config)
      .catch(statusToError) as Promise<T>
  }

  protected put = <T>(url: string, data?: any, config: RequestConfig = {}) => {
    config.headers = {
      ...config.headers,
      ...this.getCommonHeaders(),
    }

    return this.http
      .put<T>(url, data, config)
      .catch(statusToError) as Promise<T>
  }

  private getCommonHeaders = () => {
    const { orderFormId, segmentToken, sessionToken } = this
      .context as CustomIOContext

    const checkoutCookie = orderFormId ? checkoutCookieFormat(orderFormId) : ''
    const segmentTokenCookie = segmentToken
      ? `vtex_segment=${segmentToken};`
      : ''

    const sessionTokenCookie = sessionToken
      ? `vtex_session=${sessionToken};`
      : ''

    return {
      Cookie: `${checkoutCookie}${segmentTokenCookie}${sessionTokenCookie}`,
    }
  }

  private getChannelQueryString = () => {
    const { segment } = this.context as CustomIOContext
    const channel = segment?.channel
    const queryString = channel ? `?sc=${channel}` : ''

    return queryString
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/vtexId.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

interface AuthenticatedUser {
  user: string
}

export default class VtexId extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, { ...options })
  }

  public async getAuthenticatedUser(
    authToken: string
  ): Promise<AuthenticatedUser> {
    return this.http.get('/api/vtexid/pub/authenticated/user/', {
      params: { authToken },
      metric: 'authenticated-user-get',
    })
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/masterDataExtended.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

/*
  The default Master Data client was causing higher response times compared
  to calling Master Data directly from the app using this custom client.
*/

export class MasterDataExtended extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        VtexIdClientAutCookie: context.authToken,
      },
    })
  }

  public getDocumentById = async (
    dataEntity: string,
    id: string,
    fields: string[]
  ) =>
    this.http.get(
      `/api/dataentities/${dataEntity}/documents/${id}?_fields=${fields.join(
        ','
      )}`
    )
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/salesChannel.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export interface SalesChannelResponse {
  Id: string
  Name: string
  CurrencyCode: string
  CurrencySymbol: string
  IsActive: boolean
  CultureInfo: string
}

export class SalesChannel extends JanusClient {
  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        VtexIdClientAutCookie: context.authToken,
      },
    })
  }

  public getSalesChannel = async () => ({
    data: await this.http.get<SalesChannelResponse>(
      '/api/catalog_system/pvt/saleschannel/list'
    ),
  })
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/FullSessions.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export default class FullSessions extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
    })
  }

  public async getSessions({ headers }: any): Promise<any> {
    return this.http.get('/api/sessions?items=*', {
      headers,
    })
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/index.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { IOContext } from '@vtex/api'
import { IOClients } from '@vtex/api'

import { LMClient } from '../utils/LicenseManager'
import { ProfileSystemClient } from '../utils/ProfileSystem'
import { Checkout } from './checkout'
import FullSessions from './FullSessions'
import IdentityClient from './IdentityClient'
import { OrganizationsGraphQLClient } from './Organizations'
import { SalesChannel } from './salesChannel'
import { Schema } from './schema'
import VtexId from './vtexId'
import { MasterDataExtended } from './masterDataExtended'

export const getTokenToHeader = (ctx: IOContext) => {
  const adminToken = ctx.authToken
  const userToken = ctx.storeUserAuthToken
  const { sessionToken, account } = ctx

  let allCookies = `VtexIdclientAutCookie=${adminToken}`

  if (userToken) {
    allCookies += `; VtexIdclientAutCookie_${account}=${userToken}`
  }

  return {
    'x-vtex-credential': ctx.authToken,
    VtexIdclientAutCookie: adminToken,
    cookie: allCookies,
    ...(sessionToken && {
      'x-vtex-session': sessionToken,
    }),
  }
}

// Extend the default IOClients implementation with our own custom clients.
export class Clients extends IOClients {
  public get lm() {
    return this.getOrSet('lm', LMClient)
  }

  public get profileSystem() {
    return this.getOrSet('profileSystem', ProfileSystemClient)
  }

  public get checkout() {
    return this.getOrSet('checkout', Checkout)
  }

  public get organizations() {
    return this.getOrSet('organizations', OrganizationsGraphQLClient)
  }

  public get schema() {
    return this.getOrSet('schema', Schema)
  }

  public get vtexId() {
    return this.getOrSet('vtexId', VtexId)
  }

  public get identity() {
    return this.getOrSet('identity', IdentityClient)
  }

  public get salesChannel() {
    return this.getOrSet('salesChannel', SalesChannel)
  }

  public get fullSessions() {
    return this.getOrSet('fullSessions', FullSessions)
  }

  public get masterDataExtended() {
    return this.getOrSet('masterDataExtended', MasterDataExtended)
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/IdentityClient.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export default class IdentityClient extends JanusClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(ctx, {
      ...options,
      headers: {
        ...options?.headers,
      },
    })
  }

  public async validateToken({ token }: { token: string }): Promise<any> {
    return this.http.post('/api/vtexid/credential/validate', { token })
  }

  public async getToken({
    appkey,
    apptoken,
  }: {
    appkey: string
    apptoken: string
  }): Promise<any> {
    return this.http.post('/api/vtexid/apptoken/login', { appkey, apptoken })
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/clients/metrics.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import axios from 'axios'

const ANALYTICS_URL = 'https://rc.vtex.com/api/analytics/schemaless-events'

export const B2B_METRIC_NAME = 'b2b-suite-buyerorg-data'

export interface Metric {
  readonly account: string
  readonly kind: string
  readonly description: string
  readonly name: typeof B2B_METRIC_NAME
}

export const sendMetric = async (metric: Metric) => {
  await axios.post(ANALYTICS_URL, metric)
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/typings/Checkout.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
interface OrderFormMarketingData {
  utmCampaign?: string
  utmMedium?: string
  utmSource?: string
  utmiCampaign?: string
  utmiPart?: string
  utmipage?: string
  marketingTags?: string | string[]
}

interface CheckoutAddress {
  addressType: string
  receiverName: string | null
  addressId: string
  postalCode: string
  city: string
  state: string
  country: string
  street: string
  number: string
  neighborhood: string
  complement: string
  reference: string | null
  geoCoordinates: [number, number]
}

interface CheckoutAssemblyItem {
  id: string
  inputValues: Record<string, string>
}

interface CheckoutAttachmentOffering {
  name: string
  required: boolean
  schema: Record<string, unknown>
}

interface OrderFormItem {
  id: string
  name: string
  detailUrl: string
  imageUrl: string
  productRefId: string
  skuName: string
  quantity: number
  uniqueId: string
  productId: string
  refId: string
  ean: string
  priceValidUntil: string
  price: number
  tax: number
  listPrice: number
  sellingPrice: number
  rewardValue: number
  isGift: boolean
  parentItemIndex: number | null
  parentAssemblyBinding: string | null
  productCategoryIds: string
  priceTags: string[]
  measurementUnit: string
  additionalInfo: {
    brandName: string
    brandId: string
    offeringInfo: any | null
    offeringType: any | null
    offeringTypeId: any | null
  }
  productCategories: Record<string, string>
  seller: string
  sellerChain: string[]
  availability: string
  unitMultiplier: number
  assemblies: CheckoutAssemblyItem[]
  attachmentOfferings: CheckoutAttachmentOffering[]
  priceDefinition: {
    calculatedSellingPrice: number
    sellingPrices: SellingPrice[]
    total: number
  } | null
}

interface SellingPrice {
  quantity: number
  value: number
}

interface InstallmentOption {
  paymentSystem: string
  paymentName: string
  paymentGroupName: string
  value: number
  bin: string | null
  installments: SimulationInstallment[]
}

interface SimulationInstallment {
  value: number
  interestRate: number
  total: number
  count: number
}

interface PaymentData {
  installmentOptions: InstallmentOption[]
  paymentSystems: Array<{
    id: string
    name: string
    groupName: string
    validator: {
      regex: string
      mask: string
      cardCodeRegex: string
      cardCodeMask: string
      weights: number[]
      useCvv: boolean
      useExpirationDate: boolean
      useCardHolderName: boolean
      useBillingAddress: boolean
    }
    stringId: string
    template: string
    requiresDocument: boolean
    isCustom: boolean
    description: string | null
    requiresAuthentication: boolean
    dueDate: string
    availablePayments: any | null
  }>
  payments: any[]
  giftCards: any[]
  giftCardMessages: any[]
  availableAccounts: any[]
  availableTokens: any[]
}

interface OrderForm {
  orderFormId: string
  salesChannel: string
  loggedIn: boolean
  isCheckedIn: boolean
  storeId: string | null
  checkedInPickupPointId: string | null
  allowManualPrice: boolean
  canEditData: boolean
  userProfileId: string | null
  userType: string | null
  ignoreProfileData: boolean
  value: number
  messages: any[]
  items: OrderFormItem[]
  selectableGifts: any[]
  totalizers: Array<{ id: string; name: string; value: number }>
  shippingData: {
    address: CheckoutAddress
    logisticsInfo: LogisticsInfo[]
    selectedAddresses: CheckoutAddress[]
    availableAddresses: CheckoutAddress[]
    pickupPoints: Array<{
      friendlyName: string
      address: CheckoutAddress
      additionalInfo: string
      id: string
      businessHours: Array<{
        DayOfWeek: number
        OpeningTime: string
        ClosingTime: string
      }>
    }>
  }
  clientProfileData: any | null
  paymentData: PaymentData
  marketingData: OrderFormMarketingData | null
  sellers: Array<{
    id: string
    name: string
    logo: string
  }>
  clientPreferencesData: OrderFormClientPreferencesData
  commercialConditionData: any | null
  storePreferencesData: {
    countryCode: string
    saveUserData: boolean
    timeZone: string
    currencyCode: string
    currencyLocale: number
    currencySymbol: string
    currencyFormatInfo: {
      currencyDecimalDigits: number
      currencyDecimalSeparator: string
      currencyGroupSeparator: string
      currencyGroupSize: number
      startsWithCurrencySymbol: boolean
    }
  }
  giftRegistryData: any | null
  openTextField: any | null
  invoiceData: any | null
  customData: any | null
  itemMetadata: {
    items: MetadataItem[]
  }
  hooksData: any | null
  ratesAndBenefitsData: {
    rateAndBenefitsIdentifiers: any[]
    teaser: any[]
  }
  subscriptionData: any | null
  itemsOrdination: any | null
}

interface OrderFormClientPreferencesData {
  locale: string
  optinNewsLetter: boolean | null
}

interface OrderFormItemInput {
  id?: number
  index?: number
  quantity?: number
  seller?: string
  inputValues: Record<string, string>
  options?: AssemblyOptionInput[]
}

interface AssemblyOptionInput {
  id: string
  quantity: number
  assemblyId: string
  seller: string
  inputValues: Record<string, string>
  options?: AssemblyOptionInput[]
}

interface PayloadItem {
  id: string
  quantity: number
  seller: string
  parentItemIndex?: number | null
  parentAssemblyBinding?: string | null
}

interface SimulationPayload {
  country?: string
  items: PayloadItem[]
  postalCode?: string
  isCheckedIn?: boolean
  priceTables?: string[]
  marketingData?: Record<string, string>
  geoCoordinates?: [string | number, string | number]
  shippingData?: any
}

interface SLAItem {
  id: string
  deliveryChannel: string
  name: string
  deliveryIds: Array<{
    courierId: string
    warehouseId: string
    dockId: string
    courierName: string
    quantity: number
  }>
  shippingEstimate: string
  shippingEstimateDate: string | null
  lockTTL: string | null
  availableDeliveryWindows: any[]
  deliveryWindow: string | null
  price: number
  listPrice: number
  tax: number
  pickupStoreInfo: {
    isPickupStore: boolean
    friendlyName: string | null
    address: CheckoutAddress | null
    additionalInfo: any | null
    dockId: string | null
  }
  pickupPointId: string | null
  pickupDistance: number
  polygonName: string | null
}

interface LogisticsInfo {
  itemIndex: number
  selectedSla: string | null
  selectedDeliveryChannel: string | null
  addressId: string
  slas: SLAItem[]
  shipsTo: string[]
  itemId: string
  deliveryChannels: Array<{ id: string }>
}

interface SimulationOrderForm extends OrderForm {
  logisticsInfo?: LogisticsInfo[]
}

interface SLA {
  id: string
  deliveryChannel: string
  name: string
  deliveryIds: Array<{
    courierId: string
    warehouseId: string
    dockId: string
    courierName: string
    quantity: number
  }>
  shippingEstimate: string
  shippingEstimateDate: string | null
  lockTTL: string | null
  availableDeliveryWindows: any[]
  deliveryWindow: string | null
  price: number
  listPrice: number
  tax: number
  pickupStoreInfo: {
    isPickupStore: boolean
    friendlyName: string | null
    address: CheckoutAddress | null
    additionalInfo: any | null
    dockId: string | null
  }
  pickupPointId: string | null
  pickupDistance: number
  polygonName: string | null
}

interface ItemWithSimulationInput {
  itemId: string
  sellers: Array<{
    sellerId: string
  }>
}

interface RatesAndBenefitsData {
  rateAndBenefitsIdentifiers: Array<{
    id: string
    name: string
    featured: boolean
    description: string
  }>
  teaser: Array<{
    featured: boolean
    id: string
    name: string
    conditions: {
      parameters: Array<{
        name: string
        value: string
      }>
      minimumQuantity: number
    }
    effects: {
      parameters: Array<{
        name: string
        value: string
      }>
    }
    teaserType: string
  }>
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/typings/ItemMetadata.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/**
 * Assembly options related types
 */

interface CompositionItem {
  id: string
  minQuantity: number
  maxQuantity: number
  initialQuantity: number
  priceTable: string
  seller: string
}

interface Composition {
  minQuantity: number
  maxQuantity: number
  items: CompositionItem[]
}

interface AssemblyOption {
  id: string
  name: string
  composition: Composition | null
}

interface CatalogMetadataItem {
  Name: string
  NameComplete: string
  MainImage: string
  BrandName: string
  CategoryId: number
  ProductId: number
  id: string
  seller: string
  assemblyOptions: AssemblyOption[]
}

interface MetadataItem {
  id: string
  name: string
  imageUrl: string
  detailUrl: string
  seller: string
  assemblyOptions: AssemblyOption[]
  skuName: string
  productId: string
  refId: string
  ean: string | null
}
interface AddedItem {
  choiceType: string
  compositionItem: CompositionItem
  extraQuantity: number
  item: OrderFormItem
  normalizedQuantity: number
}

interface RemovedItem {
  initialQuantity: number
  name: string
  removedQuantity: number
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/typings/custom.d.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
export interface Order {
  orderId: string
  clientProfileData: ClientProfileData | null
  creationDate: string
  clientName: string
  items: Item[] | null
  comments: Comment[] | null
  totalValue: number
  status: string
  statusDescription: string | null
  sequence: string | null
  salesChannel: string
  affiliateId: string | null
  origin: string | null
  workflowInErrorState: boolean | null
  workflowInRetry: boolean | null
  ShippingEstimatedDate: string | null
  ShippingEstimatedDateMax: string
  ShippingEstimatedDateMin: string
  orderIsComplete: boolean | null
  authorizedDate: string | null
  callCenterOperatorName: string | null
  totalItems: number | null
  currencyCode: string | null
  hostname: string
  checkedInPickupPointId: string
  shippingData: ShippingData | null
  paymentData: PaymentData | null
  storePreferencesData: StorePreferencesData | null
  fulfillment?: Fulfillment
}

export interface StorePreferencesData {
  countryCode: string
  currencyCode: string
}

export interface PaymentData {
  transactions: Transaction[]
}

export interface Transaction {
  isActive: boolean
  payments: Payment[]
}

export interface Payment {
  id: string | null
  paymentSystem: string
  paymentSystemName: string
  value: number
  installments: number | null
  referenceValue: number | null
}

export interface ClientProfileData {
  corporateDocument: string | null
  corporateName: string | null
  corporatePhone: string | null
  customerClass: string | null
  document: string | null
  documentType: string | null
  email: string
  firstName: string | null
  id: string
  isCorporate: boolean
  lastName: string | null
  phone: string | null
  stateInscription: string | null
  tradeName: string | null
  userProfileId: string | null
}

export type ProductCategory = {
  id: string
  name: string
}

export type ItemAdditionalInfo = {
  categories: ProductCategory[]
}

export interface Item {
  seller: string
  quantity: number
  description: string | null
  ean: string | null
  refId: string | null
  id: string | null
  productId: string | null
  sellingPrice: number
  price: number
  imageUrl: string | null
  skuName: string | null
  name: string
  additionalInfo: ItemAdditionalInfo
}

export interface Comment {
  createdBy: CommentCreatedBy | null
  creationDate: string
  description: string
  domain: string
  id: string
  lastUpdate: string | null
  owner: string | null
  target: CommentTarget | null
}

export interface CommentCreatedBy {
  id: string
  name: string
  email: string | null
  key: string | null
}

export interface CommentTarget {
  id: string
  type: string | null
  url: string | null
}

export interface ShippingData {
  id: string | null
  logisticsInfo: LogisticsInfoItem[]
  selectedAddresses: Address[]
}

export interface LogisticsInfoItem {
  itemIndex: number
  selectedSla: string
  lockTTL: string | null
  price: number
  listPrice: number
  sellingPrice: number
  deliveryCompany: string | null
  shippingEstimate: string
  shippingEstimateDate: string | null
  deliveryChannel: string
  addressId: string
  pickupStoreInfo: PickupStoreInfo
  pickupPointId: string | null
  slas: SLA[]
}

export interface PickupStoreInfo {
  additionalInfo: string | null
  address: Address | null
  dockId: string | null
  friendlyName: string | null
  isPickupStore: boolean
}

export interface Address {
  addressId: string
  addressType: string
  receiverName: string | null
  street: string
  number: string
  complement: string | null
  neighborhood: string
  postalCode: string
  city: string
  state: string
  country: string
  reference: string | null
  geoCoordinates: number[] | null
}

export interface SLA {
  id: string | null
  deliveryChannel: string | null
  name: string | null
  deliveryIds: DeliveryId[]
  shippingEstimate: string | null
  shippingEstimateDate: string | null
  lockTTL: string | null
  price: number | null
  listPrice: number | null
  tax: number | null
  pickupStoreInfo: PickupStoreInfo | null
  pickupPointId: string | null
  pickupDistance: number | null
  polygonName: string | null
}

export interface DeliveryId {
  courierId: string | null
  warehouseId: string | null
  dockId: string | null
  courierName: string | null
  quantity: number | null
}
export interface GetOrganizationByEmailBase {
  id: string
  organizationStatus: string
  orgId: string
  costId: string
  costCenterName: string | null
}

export interface GetOrganizationsByEmailResponse {
  data: {
    getOrganizationsByEmail: GetOrganizationByEmailBase[]
  }
}
export interface GetOrganizationsPaginatedByEmailResponse {
  data: GetOrganizationByEmailBase[]
  pagination: {
    page: number
    pageSize: number
    total: number
  }
}
export interface GetCostCenterType {
  getCostCenterById: {
    name: string | null
    organization: string | null
    paymentTerms: {
      id: string | null
      name: string | null
    } | null
    address: Address
    phoneNumber: string | null
    businessDocument: string | null
    stateRegistration: string | null
    sellers: {
      id: string | null
      name: string | null
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/cookie.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { parse } from 'cookie'
import { keys } from 'ramda'

const isUserLoggedIn = (ctx: Context) => {
  const {
    vtex: { account },
  } = ctx

  return !!ctx.cookies.get(`VtexIdclientAutCookie_${account}`)
}

const parseCookie = (cookie: string) => {
  const parsed = parse(cookie)
  const [cookieName] = keys(parsed)
  const cookieValue = parsed[cookieName]

  const extraOptions = {
    domain: parsed.domain,
    expires: parsed.expires ? new Date(parsed.expires) : undefined,
    path: parsed.path,
  }

  return {
    name: cookieName,
    options: extraOptions,
    value: cookieValue,
  }
}

/** Checkout cookie methods */
const CHECKOUT_COOKIE = 'checkout.vtex.com'

const checkoutCookieFormat = (orderFormId: string) =>
  `${CHECKOUT_COOKIE}=__ofid=${orderFormId};`

const getOrderFormIdFromCookie = (cookies: Context['cookies']) => {
  const cookie = cookies.get(CHECKOUT_COOKIE)

  return cookie && cookie.split('=')[1]
}

export {
  isUserLoggedIn,
  CHECKOUT_COOKIE,
  checkoutCookieFormat,
  getOrderFormIdFromCookie,
  parseCookie,
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/constants.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
export const CUSTOMER_SCHEMA_NAME = 'CL'
export const CUSTOMER_REQUIRED_FIELDS = [
  'email',
  'id',
  'accountId',
  'accountName',
  'dataEntityId',
]
export const ROLES_VBASE_ID = 'allRolesVbId'

// License Manager constants
export const B2B_ORGANIZATIONS_PRODUCT_ID = 97
export const B2B_LM_PRODUCT_CODE = B2B_ORGANIZATIONS_PRODUCT_ID
export const BUYER_ORGANIZATION_VIEW_ROLE = 'buyer_organization_view'
export const BUYER_ORGANIZATION_EDIT_ROLE = 'buyer_organization_edit'

// License Manager roles object
export const LICENSE_MANAGER_ROLES = {
  B2B_ORGANIZATIONS_VIEW: BUYER_ORGANIZATION_VIEW_ROLE,
  B2B_ORGANIZATIONS_EDIT: BUYER_ORGANIZATION_EDIT_ROLE,
}

// Role constants for GraphQL directives
export const B2B_ORGANIZATIONS_EDIT_ROLE_PARAM = 'B2B_ORGANIZATIONS_EDIT'

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/GraphQLError.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
export default class GraphQLError extends Error {
  public extensions: any

  constructor(message: string, details?: any) {
    super(message)
    this.extensions = {
      message,
      ...details,
      exception: {
        ...details?.exception,
        // vtex-node-api checks err?.extensions?.exception?.level before logging
        level: details?.logLevel || 'error',
      },
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/LicenseManager.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

import { statusToError, toUUID } from './index'
import {
  B2B_ORGANIZATIONS_PRODUCT_ID,
  BUYER_ORGANIZATION_VIEW_ROLE,
  BUYER_ORGANIZATION_EDIT_ROLE,
} from './constants'

export class LMClient extends ExternalClient {
  constructor(ctx: IOContext, options?: InstanceOptions) {
    super(`http://${ctx.account}.vtexcommercestable.com.br/`, ctx, {
      ...options,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        VtexIdclientAutCookie: ctx.authToken,
      },
    })
  }

  public getUserIdByEmail = async (email: string) => {
    return this.get(this.routes.userByEmail(email))
      .then((res: any) => {
        return res?.UserId ? toUUID(res.UserId) : null
      })
      .catch(() => {
        return null
      })
  }

  public saveUser = async (name: string, email: string) => {
    const data = {
      email,
      name,
      roles: [
        {
          id: 957,
        },
      ],
    }

    // Check if the user exists
    const checkUser: any = await this.get(this.routes.userByEmail(email))
      .then((res: any) => {
        return res
      })
      .catch(() => {
        return {}
      })

    if (!checkUser?.UserId) {
      // Create with role
      return this.post(this.routes.createUser(), data)
        .then(() => true)
        .catch(() => false)
    }

    // Update with role
    return this.put(this.routes.updateUser(checkUser.UserId), [957]).then(
      () => {
        return { userId: checkUser.UserId }
      }
    )
  }

  public deleteUser = async (userId: string) => {
    const user = await this.get(this.routes.userById(userId))
      .then(() => {
        return true
      })
      .catch(() => {
        return false
      })

    return user ? this.delete(this.routes.deleteUser(userId, '957'), {}) : {}
  }

  public getUserAdminPermissions = async (account: string, userId: string) => {
    return this.get(this.routes.getUserAdminPermissions(account, userId)).then(
      (res: any) => {
        return res
      }
    )
  }

  public checkUserSpecificRole = async (
    account: string,
    userEmail: string,
    productCode: number,
    resourceCode: string
  ) => {
    return this.get(
      `/api/license-manager/pvt/accounts/${encodeURI(
        account
      )}/products/${productCode}/logins/${encodeURI(
        userEmail
      )}/resources/${encodeURI(resourceCode)}/granted`
    ).then((res: any) => {
      return res
    })
  }

  public hasBuyerOrganizationViewRole = async (
    account: string,
    userEmail: string
  ) => {
    try {
      const hasRole = await this.checkUserSpecificRole(
        account,
        userEmail,
        B2B_ORGANIZATIONS_PRODUCT_ID,
        BUYER_ORGANIZATION_VIEW_ROLE
      )

      return hasRole
    } catch (error) {
      return false
    }
  }

  public hasBuyerOrganizationEditRole = async (
    account: string,
    userEmail: string
  ) => {
    try {
      const hasRole = await this.checkUserSpecificRole(
        account,
        userEmail,
        B2B_ORGANIZATIONS_PRODUCT_ID,
        BUYER_ORGANIZATION_EDIT_ROLE
      )

      return hasRole
    } catch (error) {
      return false
    }
  }

  protected get = <T>(url: string) => {
    return this.http.get<T>(url).catch(statusToError)
  }

  protected post = <T>(url: string, data: any) => {
    return this.http.post<T>(url, data).catch(statusToError)
  }

  protected put = <T>(url: string, data: any) => {
    return this.http.put<T>(url, data).catch(statusToError)
  }

  protected delete = <T>(url: string, data: any) => {
    return this.http.delete<T>(url, data).catch(statusToError)
  }

  private get routes() {
    return {
      addCallcenter: (userId: string) =>
        `api/license-manager/users/${userId}/roles`,
      createRole: () => `api/license-manager/site/pvt/roles`,
      createUser: () => `api/license-manager/site/pvt/logins`,
      deleteUser: (userId: string, roleId: string) =>
        `api/license-manager/users/${userId}/roles/${roleId}`,
      getRoles: () => `api/license-manager/site/pvt/roles/list/paged`,
      updateUser: (userId: string) =>
        `api/license-manager/users/${userId}/roles`,
      userByEmail: (email: string) =>
        `api/license-manager/pvt/users/${encodeURIComponent(email)}`,
      userById: (id: string) => `api/license-manager/pvt/users/${id}`,
      getUserAdminPermissions: (account: string, userId: string) =>
        `/api/license-manager/pvt/accounts/${account}/logins/${userId}/granted`,
    }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/ProfileSystem.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { InstanceOptions, IOContext, RequestConfig } from '@vtex/api'
import { JanusClient } from '@vtex/api'

export class ProfileSystemClient extends JanusClient {
  private baseUrl = '/api/profile-system/pvt/profiles'

  constructor(context: IOContext, options?: InstanceOptions) {
    super(context, {
      ...options,
      headers: {
        ...options?.headers,
        VtexIdClientAutCookie: context.authToken,
      },
      timeout: 5000,
    })
  }

  public getProfileInfo = (userId: string, customFields?: string) =>
    this.get(`${this.baseUrl}/${userId}/personalData`, {
      metric: 'profile-system-getProfileInfo',
      params: {
        extraFields: customFields,
      },
    })

  public async createRegisterOnProfileSystem(email: string, name: string) {
    const value = await this.http.post<{ profileId: string }>(
      `${this.baseUrl}/${email}/PersonalData?extraFields=_all`,
      {
        firstName: name,
      }
    )

    return value.profileId
  }

  protected get = <T>(url: string, config?: RequestConfig) =>
    this.http.get<T>(url, config)
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/index.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as crypto from 'crypto'

import { AuthenticationError, ForbiddenError, UserInputError } from '@vtex/api'
import type { AxiosError } from 'axios'
import slugify from 'slugify'

import schemas from '../mdSchema'
import type { ErrorResponse } from '../resolvers/Routes/utils'
import roleNames from '../roleNames'

export * from './cookie'

export const toHash = (obj: any) => {
  return crypto.createHash('md5').update(JSON.stringify(obj)).digest('hex')
}

export function toUUID(uuid: string) {
  return uuid.indexOf('-') === -1
    ? `${uuid.substring(0, 8)}-${uuid.substring(8, 12)}-${uuid.substring(
        12,
        16
      )}-${uuid.substring(16, 20)}-${uuid.substring(20, uuid.length)}`
    : uuid
}

export function Slugify(str: any) {
  return slugify(str, { lower: true, remove: /[*+~.()'"!:@]/g })
}

export const currentSchema = (entity: string) => {
  return schemas.find((item: any) => {
    return item.name === entity
  })
}

export const currentRoleNames = (locale: any) => {
  return roleNames[locale] ?? roleNames['en-US']
}

export function statusToError(error: ErrorResponse) {
  if (!error.response) {
    throw error
  }

  const { response } = error as AxiosError
  const { status } = response!

  if (status === 401) {
    throw new AuthenticationError(error)
  }

  if (status === 403) {
    throw new ForbiddenError(error)
  }

  if (status === 400) {
    throw new UserInputError(error)
  }

  throw error
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/index.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-explicit-any */

import { deleteRole, saveRole } from './Mutations/Roles'
import { sessionWatcher } from './Mutations/Settings'
import {
  addCostCenterToUser,
  addOrganizationToUser,
  addUser,
  deleteUser,
  ignoreB2BSessionData,
  impersonateUser,
  saveUser,
  setActiveUserByOrganization,
  setCurrentOrganization,
  updateUser,
  setCurrentPriceTable,
} from './Mutations/Users'
import { getFeaturesByModule, listFeatures } from './Queries/Features'
import { getRole, hasUsers, listRoles } from './Queries/Roles'
import { getAppSettings, getSessionWatcher } from './Queries/Settings'
import {
  checkCustomerSchema,
  checkImpersonation,
  checkUserPermission,
  getActiveUserByEmail,
  getAllUsersByEmail,
  getB2BUserById,
  getOrganizationsByEmail,
  getOrganizationsPaginatedByEmail,
  getUser,
  getUserByEmail,
  listAllUsers,
  listUsers,
  listUsersPaginated,
} from './Queries/Users'
import { Routes } from './Routes'

export const resolvers = {
  Mutation: {
    addCostCenterToUser,
    addOrganizationToUser,
    addUser,
    deleteRole,
    deleteUser,
    ignoreB2BSessionData,
    impersonateUser,
    saveRole,
    saveUser,
    sessionWatcher,
    setActiveUserByOrganization,
    setCurrentOrganization,
    updateUser,
    setCurrentPriceTable,
  },
  Query: {
    checkCustomerSchema,
    checkImpersonation,
    checkUserPermission,
    getActiveUserByEmail,
    getAppSettings,
    getB2BUser: getB2BUserById,
    getFeaturesByModule,
    getOrganizationsByEmail,
    getOrganizationsPaginatedByEmail,
    getRole,
    getSessionWatcher,
    getUser,
    getUserByEmail,
    getUsersByEmail: getAllUsersByEmail,
    hasUsers,
    listAllUsers,
    listFeatures,
    listRoles,
    listUsers,
    listUsersPaginated,
  },
  Routes,
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/utils/metrics/changeTeam.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import type { Metric } from '../../clients/metrics'
import { B2B_METRIC_NAME, sendMetric } from '../../clients/metrics'

type ChangeTeamFieldsMetric = {
  date: string
  user_id: string
  user_role: string
  user_email: string
  new_org_id: string
  new_cost_center_id: string
}

export class ChangeTeamMetric implements Metric {
  public readonly description: string
  public readonly kind: string
  public readonly account: string
  public readonly fields: ChangeTeamFieldsMetric
  public readonly name = B2B_METRIC_NAME

  constructor(account: string, fields: ChangeTeamFieldsMetric) {
    this.account = account
    this.fields = fields
    this.kind = 'change-team-graphql-event'
    this.description = 'User change team/organization - Graphql'
  }
}

export type ChangeTeamParams = {
  account: string
  userId: string
  userRole: string
  userEmail: string
  orgId: string
  costCenterId: string
}

const buildMetric = (metricParams: ChangeTeamParams): ChangeTeamMetric => {
  return new ChangeTeamMetric(metricParams.account, {
    date: new Date().toISOString(),
    user_id: metricParams.userId,
    user_role: metricParams.userRole,
    user_email: metricParams.userEmail,
    new_org_id: metricParams.orgId,
    new_cost_center_id: metricParams.costCenterId,
  })
}

export const sendChangeTeamMetric = async (metricParams: ChangeTeamParams) => {
  try {
    const metric = buildMetric(metricParams)

    await sendMetric(metric)
  } catch (error) {
    console.warn('Unable to log metrics', error)
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Mutations/Profiles.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currentSchema } from '../../utils'
import { getProfileByRole } from '../Queries/Profiles'

const config: any = currentSchema('b2b_profiles')

export const saveProfile = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  try {
    const { id, roleId, features } = params
    const check: any = getProfileByRole(_, { roleId }, ctx)

    if (!check.length) {
      const ret = await masterdata.createOrUpdateEntireDocument({
        dataEntity: config.name,
        fields: { roleId, features },
        id,
        schema: config.version,
      })

      return { status: 'success', message: '', id: ret.DocumentId }
    }

    return {
      message: `There's a profile already associated to this Role`,
      status: 'error',
    }
  } catch (error) {
    logger.error({
      error,
      message: 'Profiles.saveProfile-error',
    })

    return { status: 'error', message: error }
  }
}

export const deleteProfile = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  try {
    await masterdata.deleteDocument({ dataEntity: config.name, id: params.id })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'Profiles.deleteProfile-error',
    })

    return { status: 'error', message: error }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Mutations/Settings.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { getAppId } from '../Queries/Settings'

export const sessionWatcher = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { vbase },
    vtex: { logger },
  } = ctx

  const app: string = getAppId()

  const settings: any = await vbase.getJSON('b2b_settings', app).catch(() => {
    return {}
  })

  const { active } = params

  settings.sessionWatcher = { active }

  return vbase
    .saveJSON('b2b_settings', app, settings)
    .then(() => true)
    .catch((error) => {
      logger.error({
        error,
        message: 'sessionWatcher.saveSessionWatcherError',
      })

      return false
    })
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Mutations/Roles.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currentRoleNames, Slugify, toHash } from '../../utils'
import { ROLES_VBASE_ID } from '../../utils/constants'
import { groupByRole } from '../Queries/Features'
import { searchRoles } from '../Queries/Roles'

export const saveRole = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { vbase },
    vtex: { logger },
  } = ctx

  try {
    const { id, name, features } = params
    const locked = params.locked ?? false
    const data: any = { name, features, locked }

    if (params.slug) {
      data.slug = params.slug
    } else if (!id && !params.slug) {
      data.slug = Slugify(name)
    }

    const roles = await searchRoles(null, ctx)
    const role = roles.find((item) => item.slug === data.slug)

    await vbase.saveJSON('b2b_roles', ROLES_VBASE_ID, [
      ...roles.filter((item) => item.slug !== data.slug),
      {
        features,
        id: role?.id ? role.id : data.id ?? data.slug,
        locked,
        name,
        slug: data.slug,
      },
    ])

    return { status: 'success', message: '', id: data.slug }
  } catch (error) {
    logger.error({
      error,
      message: 'Roles.saveRole-error',
    })

    return { status: 'error', message: error }
  }
}

const onlyModules = (obj: any) => {
  return obj.map(({ module }: any) => {
    return { module }
  })
}

export const syncRoles = async (ctx: Context) => {
  const newRoles: any = []
  // Get role names based on the location
  const roleNames = currentRoleNames(ctx.vtex.tenant?.locale)
  // List all features grouped by Role
  const groups = await groupByRole(ctx)

  // List all roles from VB
  const roles: any = await searchRoles(null, ctx)

  groups?.forEach((role: any) => {
    const [slug] = Object.getOwnPropertyNames(role)

    let currRole: any = {}
    const roleIndex = roles.findIndex((o: any) => o.slug === slug)

    if (roleIndex === -1) {
      currRole = {
        features: role[slug],
        locked: true,
        name: roleNames[slug],
        slug,
      }
    } else if (
      toHash(onlyModules(role[slug])) !==
      toHash(onlyModules(roles[roleIndex].features))
    ) {
      // Compare features
      const currModules: any = roles[roleIndex].features

      const newModules = role[slug].filter((m: any) => {
        return (
          roles[roleIndex].features.findIndex(
            (i: any) => i.module === m.module
          ) === -1
        )
      })

      newModules.forEach((m: any) => {
        currModules.push(m)
      })

      currRole = {
        ...roles[roleIndex],
        features: currModules,
      }
    }

    if (currRole.name) {
      newRoles.push(currRole)
    }
  })

  const oldRoles = roles.filter((old: any) => {
    return newRoles.findIndex((n: any) => n.slug === old.slug) === -1
  })

  const mergedRoles = oldRoles.concat(newRoles)

  const promise: any = []

  mergedRoles.forEach((role: any) => {
    promise.push(saveRole(role.id ?? null, role, ctx))
  })

  return Promise.all(promise).then(() => mergedRoles)
}

export const deleteRole = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { vbase },
    vtex: { logger },
  } = ctx

  try {
    const roles = await searchRoles(null, ctx)

    await vbase.saveJSON(
      'b2b_roles',
      ROLES_VBASE_ID,
      roles.filter((item) => item.id !== params.id)
    )

    return { status: 'success', message: '', id: params.id }
  } catch (error) {
    logger.error({
      error,
      message: 'Roles.deleteRole-error',
    })

    return { status: 'error', message: error }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Mutations/Users.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currentSchema } from '../../utils'
import { CUSTOMER_SCHEMA_NAME } from '../../utils/constants'
import type { ChangeTeamParams } from '../../utils/metrics/changeTeam'
import { sendChangeTeamMetric } from '../../utils/metrics/changeTeam'
import {
  getAllUsersByEmail,
  getOrganizationsByEmail,
  getUserByEmailOrgIdAndCostId,
} from '../Queries/Users'

const config: any = currentSchema('b2b_users')

const MAX_RETRY = 5
const RETRY_BACKOFF_FACTOR_MS = 100
const MAX_BACKOFF_MS = 1000

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const setChangeSession = async (
  sessionParameters: any,
  countRetry = 0
): Promise<void> => {
  const {
    context: {
      clients: { session },
      vtex: { logger },
    },
    publicKey,
    value,
    sessionCookie,
  } = sessionParameters

  try {
    await session.updateSession(publicKey, value, [], sessionCookie)
  } catch (error) {
    logger.error({
      error,
      message: 'setChangeSession.error',
      attempt: countRetry,
    })

    if (countRetry < MAX_RETRY) {
      countRetry++
      const backoff = Math.min(
        2 ** (countRetry - 1) * RETRY_BACKOFF_FACTOR_MS,
        MAX_BACKOFF_MS
      )

      await delay(backoff)

      return setChangeSession(sessionParameters, countRetry)
    }
  }
}

const addUserToMasterdata = async ({
  masterdata,
  params: { name, email },
}: {
  masterdata: any
  params: { name: string; email: string }
}) => {
  const names = name.split(' ')
  const [firstName] = names

  names.shift()
  const lastName = names.length > 0 ? names.join(' ') : firstName // if it gets the lastName empty, it'll repeat the firstName to avoid errors on the checkout
  const { DocumentId } = await masterdata
    .createDocument({
      dataEntity: CUSTOMER_SCHEMA_NAME,
      fields: {
        email,
        firstName,
        lastName,
      },
    })
    .then((response: { DocumentId: string }) => {
      return response
    })
    .catch((error: any) => {
      if (error.response?.data?.Message === 'duplicated entry') {
        return masterdata
          .searchDocuments({
            dataEntity: CUSTOMER_SCHEMA_NAME,
            fields: ['id'],
            pagination: {
              page: 1,
              pageSize: 1,
            },
            where: `email=${email}`,
          })
          .then((res: [{ id: string }]) => {
            return { DocumentId: res[0].id }
          })
      }

      throw error
    })

  return DocumentId
}

export const getUser = async ({
  masterdata,
  params: { email, id, userId },
}: any) => {
  const where = id || userId ? `id=${id || userId}` : `email=${email}`

  return masterdata
    .searchDocuments({
      dataEntity: config.name,
      fields: [
        'id',
        'email',
        'name',
        'orgId',
        'clId',
        'costId',
        'canImpersonate',
        'roleId',
        'userId',
        'active',
        'selectedPriceTable',
      ],
      pagination: {
        page: 1,
        pageSize: 1,
      },
      schema: config.version,
      where,
    })
    .then((res: any) => {
      return res.length > 0 ? res[0] : null
    })
    .catch(() => null)
}

const updateUserFields = async ({ masterdata, fields, id }: any) => {
  const { DocumentId } = await masterdata
    .createOrUpdateEntireDocument({
      dataEntity: config.name,
      fields,
      id,
      schema: config.version,
    })
    .then((response: { DocumentId: string }) => {
      return response
    })
    .catch((error: any) => {
      if (error.response.status < 400) {
        return {
          DocumentId: id,
        }
      }

      throw error
    })

  return DocumentId
}

const addSelectedPriceTableToB2bUser = async ({
  masterdata,
  fields,
  id,
}: any) => {
  const { DocumentId } = await masterdata
    .createOrUpdatePartialDocument({
      dataEntity: config.name,
      fields,
      id,
      schema: config.version,
    })
    .then((response: { DocumentId: string }) => {
      return response
    })
    .catch((error: any) => {
      if (error.response.status < 400) {
        return {
          DocumentId: id,
        }
      }

      throw error
    })

  return DocumentId
}

const createPermission = async ({ masterdata, params }: any) => {
  const {
    roleId,
    canImpersonate,
    name,
    email,
    userId,
    clId,
    orgId,
    costId,
    id,
  } = params

  await masterdata
    .createOrUpdateEntireDocument({
      dataEntity: config.name,
      fields: {
        canImpersonate,
        clId,
        costId,
        email,
        name,
        orgId,
        roleId,
        userId,
      },
      id,
      schema: config.version,
    })
    .then((response: { DocumentId: string }) => {
      return response
    })
    .catch((error: any) => {
      if (error.response.status < 400) {
        return {
          DocumentId: id,
        }
      }

      throw error
    })
}

export const addUser = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata, lm },
    vtex: { logger },
  } = ctx

  try {
    const costCenter = await ctx.clients.organizations.getCostCenterById(
      params.costId
    )

    // before adding an user to a cost center we check if the cost
    // center exists and if it has a valid name, otherwise both
    // login and UI might break.
    if (
      !costCenter?.data?.getCostCenterById?.name ||
      params.orgId !== costCenter?.data?.getCostCenterById?.organization
    ) {
      throw new Error(`Invalid cost center`)
    }

    const cId = await addUserToMasterdata({ masterdata, params })

    const organizations = await getOrganizationsByEmail(
      _,
      { email: params.email },
      ctx
    )

    if (
      organizations &&
      Array.isArray(organizations) &&
      organizations?.find(
        (org: any) => org.orgId === params.orgId && org.costId === params.costId
      )
    ) {
      return {
        message: `Email already exists in the organization and cost center`,
        status: 'duplicated-organization',
      }
    }

    await createPermission({
      lm,
      masterdata,
      params: {
        ...params,
        clId: cId,
      },
    })

    return { status: 'success', message: '', id: cId }
  } catch (error) {
    logger.error({
      error,
      message: 'addUser.error',
    })

    return { status: 'error', message: error }
  }
}

export const updateUser = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata, lm },
    vtex: { logger },
  } = ctx

  try {
    // check if new user already exists in CL and create profile if not
    if (!params.clId) {
      params.clId = await addUserToMasterdata({ masterdata, params })
    }

    await createPermission({
      lm,
      masterdata,
      params,
    })

    return { status: 'success', message: '', id: params.clId }
  } catch (error) {
    logger.error({
      error,
      message: 'updateUser.error',
    })

    return { status: 'error', message: error }
  }
}

export const deleteUserProfile = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { ids } = params

  try {
    const users: any = []

    ids.forEach((id: string) => {
      users.push(
        masterdata.createOrUpdatePartialDocument({
          dataEntity: config.name,
          fields: { roleId: '' },
          id,
          schema: config.version,
        })
      )
    })

    const result = await Promise.all(users)
      .then((response: any) => {
        return response
      })
      .catch((error: any) => {
        if (error.response.status >= 400) {
          throw error
        }

        logger.error({
          error,
          message: 'deleteUserProfile.error',
        })

        return []
      })

    return {
      id: result
        .map((item: { DocumentId: string }) => item.DocumentId)
        .join(','),
      message: '',
      status: result.length > 0 ? 'success' : 'error',
    }
  } catch (error) {
    logger.error({
      error,
      message: 'deleteUserProfile.error',
    })

    return { status: 'error', message: error }
  }
}

export const deleteUser = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { id } = params

  try {
    await masterdata.deleteDocument({
      dataEntity: config.name,
      id,
    })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'deleteUser.error',
    })

    return { status: 'error', message: error }
  }
}

export const impersonateUser = async (_: any, params: any, ctx: Context) => {
  const {
    vtex: { logger, sessionToken },
  } = ctx

  const { userId } = params

  try {
    await setChangeSession({
      context: ctx,
      publicKey: 'impersonate',
      value: userId,
      sessionCookie: sessionToken,
    })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'impersonateUser.error',
    })

    return { status: 'error', message: error }
  }
}

/**
 *
 * Persist the user in the database
 *
 * @deprecated
 *
 * @param _
 * @param params
 * @param ctx
 */
export const saveUser = async (_: any, params: any, ctx: Context) => {
  return updateUser(_, params, ctx)
}

export const addOrganizationToUser = async (
  _: any,
  params: any,
  ctx: Context
) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { userId, orgId, roleId, costId } = params

  const user = await getUser({ masterdata, params: { userId } })

  if (!user) {
    throw new Error('User not found')
  }

  try {
    const {
      canImpersonate,
      clId,
      email,
      name,
      roleId: roleIdUser,
      userId: id,
    } = user

    return await addUser(
      _,
      {
        active: false,
        canImpersonate,
        clId,
        costId,
        email,
        name,
        orgId,
        roleId: roleId || roleIdUser,
        userId: id,
      },
      ctx
    )
  } catch (error) {
    logger.error({
      error,
      message: 'addOrganizationToUser.error',
    })

    return { status: 'error', message: error }
  }
}

export const addCostCenterToUser = async (
  _: any,
  params: any,
  ctx: Context
) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { userId, costId } = params

  const user = await getUser({ masterdata, params: { userId } })

  if (!user) {
    throw new Error('User not found')
  }

  try {
    const {
      roleId,
      canImpersonate,
      name,
      email,
      userId: id,
      clId,
      orgId,
    } = user

    return await addUser(
      _,
      {
        active: false,
        canImpersonate,
        clId,
        costId,
        email,
        name,
        orgId,
        roleId,
        userId: id,
      },
      ctx
    )
  } catch (error) {
    logger.error({
      error,
      message: 'addCostCenterToUser.error',
    })

    return { status: 'error', message: error }
  }
}

export const setActiveUserByOrganization = async (
  _: any,
  params: any,
  ctx: Context
) => {
  const {
    clients: { masterdata, session },
    vtex: { logger, adminUserAuthToken, sessionToken },
  } = ctx

  let userId = null

  if (adminUserAuthToken) {
    userId = params.userId
  } else {
    const sessionData = await session
      .getSession(sessionToken as string, ['*'])
      .then((currentSession: any) => {
        return currentSession.sessionData
      })
      .catch((error: any) => {
        logger.error({
          error,
          message: 'orders-getSession-error',
        })

        return null
      })

    const currentUserEmail =
      sessionData?.namespaces?.profile?.email?.value ?? params.email

    const userByEmail = (await getUserByEmailOrgIdAndCostId(
      masterdata,
      {
        email: currentUserEmail,
        costId: params.costId,
        orgId: params.orgId,
      },
      ctx
    )) as any

    userId = userByEmail
      ? userByEmail.id
      : sessionData?.namespaces?.['storefront-permissions']?.userId?.value
  }

  const user = await getUser({ masterdata, params: { userId } })

  if (!user) {
    throw new Error('User not found')
  }

  await updateUserFields({
    fields: { ...user, active: true },
    id: userId,
    masterdata,
  })

  const users = await getAllUsersByEmail(_, { email: user.email }, ctx)

  try {
    const promises = users.map(async (userSecondary: any) => {
      if (userSecondary.id !== user.id) {
        await updateUserFields({
          fields: {
            ...userSecondary,
            active: false,
          },
          id: userSecondary.id,
          masterdata,
        })
      }
    })

    await Promise.all(promises)
  } catch (error) {
    logger.error({
      error,
      message: 'setActiveUserById.error',
    })
  }
}

export const setCurrentOrganization = async (
  _: any,
  params: any,
  ctx: Context
) => {
  const {
    vtex: { logger },
    cookies,
    request,
  } = ctx

  const sessionCookie =
    cookies.get('vtex_session') ?? request.header?.sessiontoken

  const { sessionData } = ctx.vtex as any

  const { orgId, costId } = params

  const {
    email: { value: email },
  } = sessionData.namespaces.profile

  const user = await getUserByEmailOrgIdAndCostId(
    _,
    { email, orgId, costId },
    ctx
  )

  if (!user) {
    const error =
      'This organization/cost center is not allowed to this current user'

    logger.error({
      error,
      message: 'updateCurrentOrganization.error',
    })

    return { status: 'error', message: error }
  }

  try {
    await setActiveUserByOrganization(
      _,
      {
        orgId: user.orgId,
        costId: user.costId,
        userId: user.id,
      },
      ctx
    )

    const metricParams: ChangeTeamParams = {
      account: sessionData?.namespaces?.account?.accountName.value,
      userId: user.id,
      userEmail: email,
      orgId,
      costCenterId: costId,
      userRole: user.roleId,
    }

    sendChangeTeamMetric(metricParams)

    await setChangeSession({
      context: ctx,
      publicKey: 'b2bCurrentCostCenter',
      value: costId,
      sessionCookie,
    })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'updateCurrentOrganization.error',
    })

    return { status: 'error', message: error }
  }
}

export const setCurrentPriceTable = async (
  _: any,
  params: { priceTable: string | null },
  ctx: Context
) => {
  const {
    vtex: { logger },
    clients: { masterdata },
  } = ctx

  const { sessionData } = ctx.vtex as any

  try {
    let { priceTable } = params

    // allow setting user priceTable back to null
    if (priceTable === undefined) priceTable = null

    // Get current user's organization
    const {
      'storefront-permissions': {
        organization: { value: orgId },
        userId: { value: userId },
      },
    } = sessionData.namespaces

    if (!orgId || !userId) {
      const error = 'User not properly authenticated with organization context'

      logger.error({
        error,
        message: 'setCurrentPriceTable.error.noOrgContext',
      })

      return { status: 'error', message: error }
    }

    // Get organization data to validate price table
    const organization = await ctx.clients.masterDataExtended.getDocumentById(
      'organizations',
      orgId,
      ['priceTables']
    )

    if (
      priceTable != null &&
      !organization?.priceTables?.includes(priceTable)
    ) {
      const error = 'Price table not allowed for this organization'

      logger.error({
        error,
        message: 'setCurrentPriceTable.error.invalidPriceTable',
        priceTable,
        orgId,
      })

      return { status: 'error', message: error }
    }

    // Update user's selected price table
    await addSelectedPriceTableToB2bUser({
      masterdata,
      fields: { selectedPriceTable: priceTable },
      id: userId,
    })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'setCurrentPriceTable.error',
    })

    return { status: 'error', message: error }
  }
}

export const ignoreB2BSessionData = async (
  _: void,
  { enabled }: { enabled: boolean },
  ctx: Context
) => {
  const {
    cookies,
    request,
    vtex: { logger },
  } = ctx

  const sessionCookie =
    cookies.get('vtex_session') ?? request.header?.sessiontoken

  try {
    await setChangeSession({
      context: ctx,
      publicKey: 'removeB2B',
      value: enabled,
      sessionCookie,
    })

    return { status: 'success', message: '' }
  } catch (error) {
    logger.error({
      error,
      message: 'removeB2BSessionData.error',
    })

    return { status: 'error', message: error }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Queries/Profiles.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currentSchema } from '../../utils'
import { listRoles } from './Roles'

const config: any = currentSchema('b2b_profiles')

export const getProfile = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  try {
    const { id } = params

    return await masterdata.getDocument({
      dataEntity: config.name,
      fields: ['id', 'roleId', 'features'],
      id,
    })
  } catch (error) {
    logger.error({
      error,
      message: 'Profiles.getProfile-error',
    })

    return { status: 'error', message: error }
  }
}

export const getProfileByRole = async (_: any, params: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  const { roleId } = params

  try {
    const [profile] = await masterdata.searchDocuments({
      dataEntity: config.name,
      fields: ['id', 'roleId', 'features'],
      pagination: { page: 1, pageSize: 50 },
      schema: config.version,
      where: `roleId=${roleId}`,
    })

    return profile
  } catch (error) {
    logger.error({
      error,
      message: 'Profiles.getProfileByRole-error',
    })

    return { status: 'error', message: error }
  }
}

export const listProfiles = async (_: any, __: any, ctx: Context) => {
  const {
    clients: { masterdata },
    vtex: { logger },
  } = ctx

  try {
    const roles: any = await listRoles(_, __, ctx)

    const profiles: any = await masterdata.searchDocuments({
      dataEntity: config.name,
      fields: ['id', 'roleId', 'features', 'scoped'],
      pagination: { page: 1, pageSize: 50 },
      schema: config.version,
    })

    return profiles.map((profile: any) => {
      return {
        ...profile,
        name: roles.find((role: any) => {
          return role.id === profile.roleId
        })?.name,
      }
    })
  } catch (error) {
    logger.error({
      error,
      message: 'Profiles.listProfiles-error',
    })

    return { status: 'error', message: error }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Queries/Settings.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
import schemas from '../../mdSchema'
import { toHash } from '../../utils'
import { syncRoles } from '../Mutations/Roles'
import type { ErrorResponse } from '../Routes/utils'

export const getAppId = (): string => {
  const app = process.env.VTEX_APP_ID
  const [appName] = String(app).split('@')

  return appName
}

export const getAppSettings = async (_: any, __: any, ctx: Context) => {
  const {
    clients: { masterdata, vbase },
    vtex: { logger },
  } = ctx

  const app: string = getAppId()

  const settings = (await vbase.getJSON('b2b_settings', app).catch(() => {
    return {}
  })) as {
    adminSetup: {
      schemaHash?: string | null
      roles?: string[] | boolean | null
    }
  }

  if (!settings.adminSetup) {
    settings.adminSetup = {}
  }

  const currHash = toHash(schemas)

  if (
    !settings.adminSetup?.schemaHash ||
    settings.adminSetup?.schemaHash !== currHash
  ) {
    const updates: Array<Promise<boolean>> = []

    schemas.forEach((schema) => {
      updates.push(
        masterdata
          .createOrUpdateSchema({
            dataEntity: schema.name,
            schemaBody: schema.body,
            schemaName: schema.version,
          })
          .then(() => true)
          .catch((error: ErrorResponse) => {
            if (error.response.status !== 304) {
              throw error
            }

            return true
          })
      )
    })

    await Promise.all(updates)
      .then(() => {
        settings.adminSetup.schemaHash = currHash
      })
      .catch((error) => {
        if (error.response.status !== 304) {
          logger.error({
            error,
            message: 'getAppSettings-error',
          })

          throw new Error(error)
        }
      })

    await vbase.saveJSON('b2b_settings', app, settings)
  }

  const roles: any = await syncRoles(ctx).catch(() => [])

  settings.adminSetup.roles = !!roles.length

  return settings
}

export const getSessionWatcher = async (_: any, __: any, ctx: Context) => {
  const {
    clients: { vbase },
    vtex: { logger },
  } = ctx

  const app: string = getAppId()

  const settings: any = await vbase.getJSON('b2b_settings', app).catch(() => {
    return {}
  })

  try {
    return settings?.sessionWatcher?.active ?? true
  } catch (error) {
    logger.error({
      error,
      message: 'getSessionWatcher.getSessionWatcherError',
    })

    return { status: 'error', message: error }
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Queries/Features.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
const sanitizeFeatures = (settings: any) => {
  const ret: any = []

  settings.forEach((app: any) => {
    const [module] = app.declarer.split('@')

    if (app[module]?.features) {
      const { name, features } = app[module]

      ret.push({
        features,
        module,
        name,
      })
    }
  })

  return ret
}

export const getFeaturesByModule = async (
  _: any,
  params: any,
  ctx: Context
) => {
  const { module } = params

  if (ctx.vtex?.settings?.dependenciesSettings) {
    const settingsFiles: any = sanitizeFeatures(
      ctx.vtex.settings.dependenciesSettings
    )

    return settingsFiles.find((app: any) => {
      return app.module === module
    })
  }

  return null
}

export const listFeatures = async (_: any, __: any, ctx: Context) => {
  let result = []

  if (ctx.vtex?.settings?.dependenciesSettings) {
    const settingsFiles: any = sanitizeFeatures(
      ctx.vtex.settings.dependenciesSettings
    )

    if (settingsFiles?.length) {
      result = settingsFiles
    } else if (Object.getOwnPropertyNames(settingsFiles).length) {
      result = [settingsFiles]
    }
  }

  return result
}

const extractRoles = (settings: any) => {
  const roles: any = {}

  const apps = settings.length ? settings : [settings]

  apps.forEach((app: any) => {
    app.features?.forEach((feature: any) => {
      feature.roles?.forEach((role: string) => {
        roles[role] = true
      })
    })
  })

  return Object.getOwnPropertyNames(roles)
}

const featuresByRole = (settings: any, role: string) => {
  const features: any = []
  const apps = settings.length ? settings : [settings]

  apps.forEach((app: any) => {
    app.features?.forEach((feature: any) => {
      feature.roles?.forEach((current: string) => {
        if (current === role) {
          const moduleIndex = features.findIndex((f: any) => {
            return f.module === app.module
          })

          if (moduleIndex === -1) {
            features.push({
              features: [feature.value],
              module: app.module,
            })
          } else {
            features[moduleIndex].features.push(feature.value)
          }
        }
      })
    })
  })

  return features
}

export const groupByRole = async (ctx: Context) => {
  const settings = await listFeatures(null, null, ctx)
  const roles = extractRoles(settings)
  const features: any = []

  roles.forEach((role: string) => {
    features.push({ [role]: featuresByRole(settings, role) })
  })

  return features
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of storefront-permissions/node/resolvers/Queries/Roles.ts from the storefront-permissions VTEX app.

[Assistant]
Here's the implementation:

```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currentRoleNames, currentSchema } from '../../utils'
import { ROLES_VBASE_ID } from '../../utils/constants'
import { getUserByRole } from './Users'

const sorting = (a: any, b: any) => (a.name > b.name ? 1 : -1)
const config: any = currentSchema('b2b_roles')

const getDefaultRoles = (locale: string) => {
  const roleNames = currentRoleNames(locale)
  const values = [] as Array<{
    features: string[]
    id: string
    locked: boolean
    name: string
    slug: string
  }>

  Object.keys(roleNames).forEach((slug) => {
    values.push({
      features: [],
      id: slug,
      locked: true,
      name: roleNames[slug],
      slug,
    })
  })

  return values.sort(sorting)
}

export const searchRoles = async (_: any, ctx: Context) => {
  const {
    clients: { vbase, masterdata },
  } = ctx

  try {
    const roles: any[] = await vbase.getJSON('b2b_roles', ROLES_VBASE_ID)

    roles.sort(sorting)

    return roles
  } catch (error) {
    if (error?.response?.status === 404) {
      const options: any = {
        dataEntity: config.name,
        fields: ['id', 'name', 'features', 'locked', 'slug'],
        pagination: { page: 1, pageSize: 50 },
        schema: config.version,
      }

      const roles = await masterdata.searchDocuments(options)

      return !roles || roles.length === 0
        ? getDefaultRoles(ctx.vtex.tenant?.locale ?? '')
        : roles
    }

    throw new Error(error)
  }
}

export const getRole = async (_: any, params: any, ctx: Context) => {
  const {
    vtex: { logger },
  } = ctx

  try {
    const { id, slug } = params

    const role: any = id
      ? (await searchRoles(null, ctx)).find((item: any) => item.id === id)
      : (await searchRoles(null, ctx)).find((item: any) => item.slug === slug)

    return role
  } catch (error) {
    logger.error({
      error,
      message: 'Roles.getRole',
    })

    return { status: 'error', message: error }
  }
}

export const hasUsers = async (_: any, params: any, ctx: Context) => {
  const role: any = await getRole(
    null,
    { id: params.id, slug: params.slug },
    ctx
  )

  if (role?.id) {
    const usersByRole: any = await getUserByRole(_, { id: role.id }, ctx)

    return usersByRole.length > 0
  }

  return false
}

export const listRoles = async (_: any, __: any, ctx: Context) => {
  const {
    vtex: { logger },
  } = ctx

  try {
    return await searchRoles(null, ctx)
  } catch (e) {
    logger.error({
      error: e,
      message: 'Roles.listRoles',
    })

    return { status: 'error', message: e }
  }
}

```