diff options
| author | Saumit <justsaumit@protonmail.com> | 2025-09-27 02:14:26 +0530 |
|---|---|---|
| committer | Saumit <justsaumit@protonmail.com> | 2025-09-27 02:14:26 +0530 |
| commit | 82e03978b89938219958032efb1448cc76baa181 (patch) | |
| tree | 626f3e54d52ecd49be0ed3bee30abacc0453d081 /src/frontend/gateways | |
Initial snapshot - OpenTelemetry demo 2.1.3 -f
Diffstat (limited to 'src/frontend/gateways')
| -rw-r--r-- | src/frontend/gateways/Api.gateway.ts | 119 | ||||
| -rw-r--r-- | src/frontend/gateways/Session.gateway.ts | 33 | ||||
| -rw-r--r-- | src/frontend/gateways/http/Shipping.gateway.ts | 60 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/Ad.gateway.ts | 19 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/Cart.gateway.ts | 29 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/Checkout.gateway.ts | 19 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/Currency.gateway.ts | 24 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/ProductCatalog.gateway.ts | 24 | ||||
| -rw-r--r-- | src/frontend/gateways/rpc/Recommendations.gateway.ts | 21 |
9 files changed, 348 insertions, 0 deletions
diff --git a/src/frontend/gateways/Api.gateway.ts b/src/frontend/gateways/Api.gateway.ts new file mode 100644 index 0000000..eb9438c --- /dev/null +++ b/src/frontend/gateways/Api.gateway.ts @@ -0,0 +1,119 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { Ad, Address, Cart, CartItem, Money, PlaceOrderRequest, Product } from '../protos/demo'; +import { IProductCart, IProductCartItem, IProductCheckout } from '../types/Cart'; +import request from '../utils/Request'; +import { AttributeNames } from '../utils/enums/AttributeNames'; +import SessionGateway from './Session.gateway'; +import { context, propagation } from "@opentelemetry/api"; + +const { userId } = SessionGateway.getSession(); + +const basePath = '/api'; + +const Apis = () => ({ + getCart(currencyCode: string) { + return request<IProductCart>({ + url: `${basePath}/cart`, + queryParams: { sessionId: userId, currencyCode }, + }); + }, + addCartItem({ currencyCode, ...item }: CartItem & { currencyCode: string }) { + return request<Cart>({ + url: `${basePath}/cart`, + body: { item, userId }, + queryParams: { currencyCode }, + method: 'POST', + }); + }, + emptyCart() { + return request<undefined>({ + url: `${basePath}/cart`, + method: 'DELETE', + body: { userId }, + }); + }, + + getSupportedCurrencyList() { + return request<string[]>({ + url: `${basePath}/currency`, + }); + }, + + getShippingCost(itemList: IProductCartItem[], currencyCode: string, address: Address) { + return request<Money>({ + url: `${basePath}/shipping`, + queryParams: { + itemList: JSON.stringify(itemList.map(({ productId, quantity }) => ({ productId, quantity }))), + currencyCode, + address: JSON.stringify(address), + }, + }); + }, + + placeOrder({ currencyCode, ...order }: PlaceOrderRequest & { currencyCode: string }) { + return request<IProductCheckout>({ + url: `${basePath}/checkout`, + method: 'POST', + queryParams: { currencyCode }, + body: order, + }); + }, + + listProducts(currencyCode: string) { + return request<Product[]>({ + url: `${basePath}/products`, + queryParams: { currencyCode }, + }); + }, + getProduct(productId: string, currencyCode: string) { + return request<Product>({ + url: `${basePath}/products/${productId}`, + queryParams: { currencyCode }, + }); + }, + listRecommendations(productIds: string[], currencyCode: string) { + return request<Product[]>({ + url: `${basePath}/recommendations`, + queryParams: { + productIds, + sessionId: userId, + currencyCode, + }, + }); + }, + listAds(contextKeys: string[]) { + return request<Ad[]>({ + url: `${basePath}/data`, + queryParams: { + contextKeys, + }, + }); + }, +}); + +/** + * Extends all the API calls to set baggage automatically. + */ +const ApiGateway = new Proxy(Apis(), { + get(target, prop, receiver) { + const originalFunction = Reflect.get(target, prop, receiver); + + if (typeof originalFunction !== 'function') { + return originalFunction; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (...args: any[]) { + const baggage = propagation.getActiveBaggage() || propagation.createBaggage(); + const newBaggage = baggage.setEntry(AttributeNames.SESSION_ID, { value: userId }); + const newContext = propagation.setBaggage(context.active(), newBaggage); + return context.with(newContext, () => { + return Reflect.apply(originalFunction, undefined, args); + }); + }; + }, +}); + +export default ApiGateway; diff --git a/src/frontend/gateways/Session.gateway.ts b/src/frontend/gateways/Session.gateway.ts new file mode 100644 index 0000000..d5791b5 --- /dev/null +++ b/src/frontend/gateways/Session.gateway.ts @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { v4 } from 'uuid'; + +interface ISession { + userId: string; + currencyCode: string; +} + +const sessionKey = 'session'; +const defaultSession = { + userId: v4(), + currencyCode: 'USD', +}; + +const SessionGateway = () => ({ + getSession(): ISession { + if (typeof window === 'undefined') return defaultSession; + const sessionString = localStorage.getItem(sessionKey); + + if (!sessionString) localStorage.setItem(sessionKey, JSON.stringify(defaultSession)); + + return JSON.parse(sessionString || JSON.stringify(defaultSession)) as ISession; + }, + setSessionValue<K extends keyof ISession>(key: K, value: ISession[K]) { + const session = this.getSession(); + + localStorage.setItem(sessionKey, JSON.stringify({ ...session, [key]: value })); + }, +}); + +export default SessionGateway(); diff --git a/src/frontend/gateways/http/Shipping.gateway.ts b/src/frontend/gateways/http/Shipping.gateway.ts new file mode 100644 index 0000000..fad3264 --- /dev/null +++ b/src/frontend/gateways/http/Shipping.gateway.ts @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { Address, CartItem, GetQuoteResponse } from '../../protos/demo'; + +const { SHIPPING_ADDR = '' } = process.env; + +// Transform address from camelCase to snake_case for HTTP API +const transformAddress = (address: Address) => ({ + street_address: address.streetAddress, + city: address.city, + state: address.state, + country: address.country, + zip_code: address.zipCode, +}); + +// Transform cart items from camelCase to snake_case for HTTP API +const transformCartItems = (items: CartItem[]) => + items.map(item => ({ + product_id: item.productId, + quantity: item.quantity, + })); + +const ShippingGateway = () => ({ + async getShippingCost(itemList: CartItem[], address: Address): Promise<GetQuoteResponse> { + const requestBody = { + items: transformCartItems(itemList), + address: transformAddress(address), + }; + + const response = await fetch(`${SHIPPING_ADDR}/get-quote`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error: ${response.status} ${response.statusText} - ${errorText}`); + } + + const data = await response.json(); + + const costUsd = data.cost_usd ? { + currencyCode: data.cost_usd.currency_code, + units: data.cost_usd.units, + nanos: data.cost_usd.nanos, + } : undefined; + + const transformedResponse: GetQuoteResponse = { + costUsd, + }; + + return transformedResponse; + }, +}); + +export default ShippingGateway(); diff --git a/src/frontend/gateways/rpc/Ad.gateway.ts b/src/frontend/gateways/rpc/Ad.gateway.ts new file mode 100644 index 0000000..1fd7244 --- /dev/null +++ b/src/frontend/gateways/rpc/Ad.gateway.ts @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { AdResponse, AdServiceClient } from '../../protos/demo'; + +const { AD_ADDR = '' } = process.env; + +const client = new AdServiceClient(AD_ADDR, ChannelCredentials.createInsecure()); + +const AdGateway = () => ({ + listAds(contextKeys: string[]) { + return new Promise<AdResponse>((resolve, reject) => + client.getAds({ contextKeys: contextKeys }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, +}); + +export default AdGateway(); diff --git a/src/frontend/gateways/rpc/Cart.gateway.ts b/src/frontend/gateways/rpc/Cart.gateway.ts new file mode 100644 index 0000000..25dfc87 --- /dev/null +++ b/src/frontend/gateways/rpc/Cart.gateway.ts @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { Cart, CartItem, CartServiceClient, Empty } from '../../protos/demo'; + +const { CART_ADDR = '' } = process.env; + +const client = new CartServiceClient(CART_ADDR, ChannelCredentials.createInsecure()); + +const CartGateway = () => ({ + getCart(userId: string) { + return new Promise<Cart>((resolve, reject) => + client.getCart({ userId }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, + addItem(userId: string, item: CartItem) { + return new Promise<Empty>((resolve, reject) => + client.addItem({ userId, item }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, + emptyCart(userId: string) { + return new Promise<Empty>((resolve, reject) => + client.emptyCart({ userId }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, +}); + +export default CartGateway(); diff --git a/src/frontend/gateways/rpc/Checkout.gateway.ts b/src/frontend/gateways/rpc/Checkout.gateway.ts new file mode 100644 index 0000000..3d54f9e --- /dev/null +++ b/src/frontend/gateways/rpc/Checkout.gateway.ts @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { CheckoutServiceClient, PlaceOrderRequest, PlaceOrderResponse } from '../../protos/demo'; + +const { CHECKOUT_ADDR = '' } = process.env; + +const client = new CheckoutServiceClient(CHECKOUT_ADDR, ChannelCredentials.createInsecure()); + +const CheckoutGateway = () => ({ + placeOrder(order: PlaceOrderRequest) { + return new Promise<PlaceOrderResponse>((resolve, reject) => + client.placeOrder(order, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, +}); + +export default CheckoutGateway(); diff --git a/src/frontend/gateways/rpc/Currency.gateway.ts b/src/frontend/gateways/rpc/Currency.gateway.ts new file mode 100644 index 0000000..ac25719 --- /dev/null +++ b/src/frontend/gateways/rpc/Currency.gateway.ts @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { GetSupportedCurrenciesResponse, CurrencyServiceClient, Money } from '../../protos/demo'; + +const { CURRENCY_ADDR = '' } = process.env; + +const client = new CurrencyServiceClient(CURRENCY_ADDR, ChannelCredentials.createInsecure()); + +const CurrencyGateway = () => ({ + convert(from: Money, toCode: string) { + return new Promise<Money>((resolve, reject) => + client.convert({ from, toCode }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, + getSupportedCurrencies() { + return new Promise<GetSupportedCurrenciesResponse>((resolve, reject) => + client.getSupportedCurrencies({}, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, +}); + +export default CurrencyGateway(); diff --git a/src/frontend/gateways/rpc/ProductCatalog.gateway.ts b/src/frontend/gateways/rpc/ProductCatalog.gateway.ts new file mode 100644 index 0000000..18d8305 --- /dev/null +++ b/src/frontend/gateways/rpc/ProductCatalog.gateway.ts @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { ListProductsResponse, Product, ProductCatalogServiceClient } from '../../protos/demo'; + +const { PRODUCT_CATALOG_ADDR = '' } = process.env; + +const client = new ProductCatalogServiceClient(PRODUCT_CATALOG_ADDR, ChannelCredentials.createInsecure()); + +const ProductCatalogGateway = () => ({ + listProducts() { + return new Promise<ListProductsResponse>((resolve, reject) => + client.listProducts({}, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, + getProduct(id: string) { + return new Promise<Product>((resolve, reject) => + client.getProduct({ id }, (error, response) => (error ? reject(error) : resolve(response))) + ); + }, +}); + +export default ProductCatalogGateway(); diff --git a/src/frontend/gateways/rpc/Recommendations.gateway.ts b/src/frontend/gateways/rpc/Recommendations.gateway.ts new file mode 100644 index 0000000..c58a470 --- /dev/null +++ b/src/frontend/gateways/rpc/Recommendations.gateway.ts @@ -0,0 +1,21 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +import { ChannelCredentials } from '@grpc/grpc-js'; +import { ListRecommendationsResponse, RecommendationServiceClient } from '../../protos/demo'; + +const { RECOMMENDATION_ADDR = '' } = process.env; + +const client = new RecommendationServiceClient(RECOMMENDATION_ADDR, ChannelCredentials.createInsecure()); + +const RecommendationsGateway = () => ({ + listRecommendations(userId: string, productIds: string[]) { + return new Promise<ListRecommendationsResponse>((resolve, reject) => + client.listRecommendations({ userId, productIds }, (error, response) => + error ? reject(error) : resolve(response) + ) + ); + }, +}); + +export default RecommendationsGateway(); |
