Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion linx/actions/auction/addBid.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { logger } from "@deco/deco/o11y";
import { cleanResponse } from "../../../utils/http.ts";
import type { AppContext } from "../../mod.ts";
import { CartOperation } from "../../utils/types/basket.ts";

Expand Down Expand Up @@ -25,7 +27,24 @@ const action = async (
},
);

const data = await response.json();
const data = await cleanResponse<CartOperation>(response);

if (typeof data !== "object") {
logger.error(`Failed to parse response from linx as JSON: ${data}`);
return {
Errors: [],
IsValid: false,
RefreshBasket: false,
ResponseCallBack: {
Code: "0",
Parameters: [],
Value: "",
},
SuccessMessage: null,
Url: null,
Warnings: [],
};
}

return data;
};
Expand Down
12 changes: 9 additions & 3 deletions linx/loaders/auction/ListBids.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { logger } from "@deco/deco/o11y";
import type { AppContext } from "../../../linx/mod.ts";
import { nullOnNotFound } from "../../../utils/http.ts";
import { Bids } from "../../utils/types/ListBidsJSON.ts";
import { cleanResponse, nullOnNotFound } from "../../../utils/http.ts";
import { Bids, WebPage } from "../../utils/types/ListBidsJSON.ts";

/**
* @title Linx Integration
Expand All @@ -26,7 +27,12 @@ const bidsloader = async (
return null;
}

const auction = await response.json();
const auction = await cleanResponse<WebPage>(response);

if (typeof auction !== "object") {
logger.error(`Failed to parse response from linx as JSON: ${auction}`);
return null;
}

return auction.Bids;
};
Expand Down
38 changes: 25 additions & 13 deletions linx/loaders/auction/apiList.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Auction } from "../../utils/types/auctionAPI.ts";
import type { AppContext } from "../../../linx/mod.ts";
import { cleanResponse } from "../../../utils/http.ts";
import { logger } from "@deco/deco/o11y";

/**
* @title Linx Integration
Expand All @@ -12,22 +14,32 @@ const loader = async (
): Promise<Auction[] | null> => {
const { layer } = ctx;

const responsePromise = await layer
["POST /v1/Catalog/API.svc/web/SearchProductAuctions"](
{},
// @ts-ignore body is required
{ body: {} },
);
try {
const responsePromise = await layer
["POST /v1/Catalog/API.svc/web/SearchProductAuctions"](
{},
// @ts-ignore body is required
{ body: {} },
);

return await responsePromise.json();
};
const response = await cleanResponse<Auction[]>(responsePromise);

if (typeof response !== "object") {
logger.error(`Failed to parse response from linx as JSON: ${response}`);
return null;
}

export const cache = "stale-while-revalidate";
return response;
} catch (error) {
console.error("Linx auction API list error", error);
throw error;
}
};

export const cacheKey = (_props: unknown, req: Request, _ctx: AppContext) => {
const url = new URL(req.url);
url.pathname = "/v1/Catalog/API.svc/web/SearchProductAuctions";
return url.href;
export const cache = {
maxAge: 60, // 1 min
};

export const cacheKey = () => "/v1/Catalog/API.svc/web/SearchProductAuctions";

export default loader;
15 changes: 11 additions & 4 deletions linx/loaders/auction/list.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { logger } from "@deco/deco/o11y";
import type { AppContext } from "../../../linx/mod.ts";
import { nullOnNotFound } from "../../../utils/http.ts";
import { cleanResponse, nullOnNotFound } from "../../../utils/http.ts";
import { isAuctionModel } from "../../utils/paths.ts";
import { toAuction, toFilters } from "../../utils/transform.ts";
import { AuctionListingPage } from "../../utils/types/auction.ts";
import { WebPage } from "../../utils/types/gridProductsJSON.ts";

/**
* @title Linx Integration
Expand All @@ -24,14 +26,19 @@ const loader = async (
return null;
}

const auctions = await response.json();
const auctions = await cleanResponse<WebPage>(response);

if (typeof auctions !== "object") {
logger.error(`Failed to parse response from linx as JSON: ${auctions}`);
return null;
}

if (!auctions || !isAuctionModel(auctions)) {
throw new Error("/leilao.json returned another model than Auction");
}

const products = auctions.Model.ProductAuctions.map((auction) =>
toAuction(auction, { cdn })
const products = auctions.Model.ProductAuctions.map((auction, index) =>
toAuction(auction, { cdn }, auctions.Model.Grid.Products[index])
);
const facets = toFilters(auctions.Model.Grid.Facets, url);
const pageCount = auctions.Model.Grid.PageCount;
Expand Down
12 changes: 11 additions & 1 deletion linx/loaders/path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { logger } from "@deco/deco/o11y";
import { STALE } from "../../utils/fetch.ts";
import { cleanResponse } from "../../utils/http.ts";
import type { AppContext } from "../mod.ts";
import type { API } from "../utils/client.ts";
import { redirect } from "@deco/deco";
Expand Down Expand Up @@ -58,6 +60,14 @@ async function loader(
const redirectUrl = new URL(redirectUrlRaw);
throw redirect(redirectUrl);
}
return response.json();

const data = await cleanResponse<API["GET /*splat"]["response"]>(response);

if (typeof data !== "object") {
logger.error(`Failed to parse response from linx as JSON: ${data}`);
return null;
}

return data;
}
export default loader;
19 changes: 17 additions & 2 deletions linx/loaders/product/listingPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import {
toProduct,
} from "../../utils/transform.ts";

export interface Props {
/**
* @ignore
* @maximum 2
*/
retryCount?: number;
}

const MAX_ATTEMPTS = 1;

const getPageInfo = ({ page, nbPages, recordPerPage, records, url }: {
page: number;
nbPages: number;
Expand Down Expand Up @@ -45,7 +55,7 @@ const getPageInfo = ({ page, nbPages, recordPerPage, records, url }: {
* @description Product Listing Page loader
*/
const loader = async (
_props: unknown,
{ retryCount = 0 }: Props,
req: Request,
ctx: AppContext,
): Promise<ProductListingPage | null> => {
Expand All @@ -60,7 +70,12 @@ const loader = async (
!forProducts ||
!isGridProductsModel(forProducts)
) {
throw new Error("Expected GridProducts model");
if (retryCount <= MAX_ATTEMPTS) {
return ctx.invoke("linx/loaders/product/listingPage.ts", {
retryCount: retryCount + 1,
});
}
return null;
}

const {
Expand Down
5 changes: 4 additions & 1 deletion linx/utils/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { DEFAULT_IMAGE } from "../../commerce/utils/constants.ts";
import { CartResponse } from "./types/basketJSON.ts";
import {
MediaGroup,
Product as LinxProductGroupGridProductsJSON,
} from "./types/gridProductsJSON.ts";
import { NavigationInfo as ProductNavigation } from "./types/shared.ts";
Expand Down Expand Up @@ -458,10 +459,12 @@ export const toCart = (
export const toAuction = (
auction: ProductAuction,
{ cdn }: { cdn: string },
): ProductAuction => {
product: LinxProductGroupGridProductsJSON,
): ProductAuction & { MediaGroups: MediaGroup[] } => {
return ({
...auction,
ProductImage: new URL(auction.ProductImage, cdn).href,
MediaGroups: product.MediaGroups,
});
};

Expand Down
48 changes: 48 additions & 0 deletions utils/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,51 @@ export const nullOnNotFound = (error: any) => {
}
throw error;
};

/**
* Clean function to process HTTP responses based on Content-Type
* @param {Response} response - The fetch response object
* @returns {Promise<object|string|Blob>} - Returns the response body converted to the appropriate format
*/
export async function cleanResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
throw new Error(`HTTP Error! Status: ${response.status}`);
}

const contentType = response.headers.get("Content-Type") || "";

// deno-lint-ignore no-explicit-any
const contentHandlers: Record<string, () => Promise<any>> = {
"application/json": () => response.json(),
"text/": () => response.text(),
"image/": () => response.blob(),
"audio/": () => response.blob(),
"video/": () => response.blob(),
"application/octet-stream": () => response.blob(),
};

try {
// Find the appropriate handler based on content-type
const handlerKey = Object.keys(contentHandlers).find((type) =>
contentType.includes(type)
);

if (handlerKey) {
return await contentHandlers[handlerKey]();
}

// Fallback - try JSON first, then text
try {
return await response.json();
} catch {
return await response.text() as unknown as T;
}
} catch (error) {
console.error("Error processing response:", error);
throw new Error(
`Failed to process response: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
}
Loading