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
110 changes: 110 additions & 0 deletions smarthint/loaders/productList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Product } from "../../commerce/types.ts";
import { AppContext } from "../mod.ts";
import { getFilterParam, toProduct } from "../utils/transform.ts";
import { ComplexPageType, FilterProp } from "../utils/typings.ts";
import { getSessionCookie } from "../utils/getSession.ts";
import { getCategoriesParam, getProductParam } from "./recommendations.ts";

export interface Props {
/**
* @hide
*/
filter?: FilterProp[];
/**
* @hide
*/
categories?: string;
/**
* @hide
*/
products?: string[];
/**
* @description Your recommendations are divided by positions, defining which position of the recommendations according to your desire. All recommendations configured in the Admin Panel will be returned.
*/
position: string;
/**
* @description Type of page you are setting up.
*/
pagetype: ComplexPageType;
/**
* @default padrao
*/
channel?: string;
}

/**
* @title SmartHint Integration
* @description Product List from Recommendations (for ProductShelf)
*/
const loader = async (
props: Props,
req: Request,
ctx: AppContext,
): Promise<Product[] | null> => {
const { recs, shcode, publicUrl, categoryTree } = ctx;
const {
categories: categoriesParam,
filter = [],
position,
products: productsParam = [],
pagetype,
channel = "padrao",
} = props;

const url = new URL(req.url);

const { anonymous } = getSessionCookie(req.headers);

const pageIdentifier = new URL(url.pathname, publicUrl)?.href;

const filters = getFilterParam(url, filter);

const productsString = getProductParam(pagetype, productsParam);

const categories = getCategoriesParam({
categoriesParam,
categoryTree,
url,
});

const data = await recs["GET /recommendationByPage/withProducts"]({
shcode,
anonymous,
categories,
channel,
filter: filters,
pageIdentifier,
pagetype: pagetype.type,
position,
products: productsString,
}).then((r) => r.json());

const positionItem = data.find((item) =>
Number(item.SmartHintPosition) == Number(position)
);
Comment on lines +70 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard external response before parsing and .find().

This path assumes a successful JSON array response. A non-OK status, invalid JSON, or non-array payload will throw and break the loader.

🛡️ Suggested hardening
-  const data = await recs["GET /recommendationByPage/withProducts"]({
+  const response = await recs["GET /recommendationByPage/withProducts"]({
     shcode,
     anonymous,
     categories,
     channel,
     filter: filters,
     pageIdentifier,
     pagetype: pagetype.type,
     position,
     products: productsString,
-  }).then((r) => r.json());
+  });
+
+  if (!response.ok) return null;
+
+  let data: unknown;
+  try {
+    data = await response.json();
+  } catch {
+    return null;
+  }
+  if (!Array.isArray(data)) return null;
 
   const positionItem = data.find((item) =>
     Number(item.SmartHintPosition) == Number(position)
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = await recs["GET /recommendationByPage/withProducts"]({
shcode,
anonymous,
categories,
channel,
filter: filters,
pageIdentifier,
pagetype: pagetype.type,
position,
products: productsString,
}).then((r) => r.json());
const positionItem = data.find((item) =>
Number(item.SmartHintPosition) == Number(position)
);
const response = await recs["GET /recommendationByPage/withProducts"]({
shcode,
anonymous,
categories,
channel,
filter: filters,
pageIdentifier,
pagetype: pagetype.type,
position,
products: productsString,
});
if (!response.ok) return null;
let data: unknown;
try {
data = await response.json();
} catch {
return null;
}
if (!Array.isArray(data)) return null;
const positionItem = data.find((item) =>
Number(item.SmartHintPosition) == Number(position)
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@smarthint/loaders/productList.ts` around lines 70 - 84, The code calls
recs["GET /recommendationByPage/withProducts"] and immediately parses and uses
the response as an array (variable data) then runs .find to create positionItem;
to harden this, first capture the raw response, check response.ok and handle
non-OK (log/throw or return an empty result), then attempt to parse JSON inside
a try/catch to handle invalid JSON, and after parsing validate
Array.isArray(data) before calling .find (if not an array, set data = [] or
handle accordingly). Update the call site around recs["GET
/recommendationByPage/withProducts"], the parsing step that assigns data, and
the subsequent creation of positionItem to follow these guards.


if (!positionItem) return null;

const products: Product[] = [];

// Extract products from RecommendationsProducts
positionItem.RecommendationsProducts?.forEach((rec) => {
rec.Products?.forEach((p) => products.push(toProduct(p)));
});

// Extract products from RecommendationsPromotional
positionItem.RecommendationsPromotional?.forEach((rec) => {
rec.Products?.forEach((p) => products.push(toProduct(p)));
});

// Extract products from RecommendationsCombination combos
positionItem.RecommendationsCombination?.forEach((rec) => {
rec.combos?.forEach((combo) => {
combo.Products?.forEach((p) => products.push(toProduct(p)));
});
});

return products.length ? products : null;
};

export default loader;
2 changes: 1 addition & 1 deletion smarthint/loaders/recommendations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface Props {
channel?: string;
}

function getProductParam(pagetype: ComplexPageType, productsParam: string[]) {
export function getProductParam(pagetype: ComplexPageType, productsParam: string[]) {
if (productsParam.length) {
return productsParam.map((productId) => `productid:${productId}`).join("&");
}
Comment on lines +38 to 41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix formatting for getProductParam declaration to unblock CI.

deno fmt --check is failing on this declaration; reformat this signature exactly as deno fmt expects.

💡 Suggested patch
-export function getProductParam(pagetype: ComplexPageType, productsParam: string[]) {
+export function getProductParam(
+  pagetype: ComplexPageType,
+  productsParam: string[],
+) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getProductParam(pagetype: ComplexPageType, productsParam: string[]) {
if (productsParam.length) {
return productsParam.map((productId) => `productid:${productId}`).join("&");
}
export function getProductParam(
pagetype: ComplexPageType,
productsParam: string[],
) {
if (productsParam.length) {
return productsParam.map((productId) => `productid:${productId}`).join("&");
}
🧰 Tools
🪛 GitHub Actions: ci

[error] 38-41: deno fmt --check failed. Found 1 not formatted file in 2112 files. Formatting diff indicates function declaration 'getProductParam' needs reformatting (parameter list spanning multiple lines).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@smarthint/loaders/recommendations.ts` around lines 38 - 41, Reformat the
declaration of getProductParam to match deno fmt: run deno fmt and update the
function signature/spacing so it matches the formatter's output (ensure
parameter spacing and placement of the opening brace follow deno fmt conventions
for export function getProductParam(pagetype: ComplexPageType, productsParam:
string[])). After running deno fmt, commit the updated signature so CI passes.

Expand Down
2 changes: 2 additions & 0 deletions smarthint/manifest.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as $$$1 from "./loaders/autocomplete.ts";
import * as $$$2 from "./loaders/banners.ts";
import * as $$$0 from "./loaders/PLPBanners.ts";
import * as $$$3 from "./loaders/productListingPage.ts";
import * as $$$5 from "./loaders/productList.ts";
import * as $$$4 from "./loaders/recommendations.ts";
import * as $$$$$$0 from "./sections/Analytics/SmarthintTracking.tsx";

Expand All @@ -16,6 +17,7 @@ const manifest = {
"smarthint/loaders/autocomplete.ts": $$$1,
"smarthint/loaders/banners.ts": $$$2,
"smarthint/loaders/PLPBanners.ts": $$$0,
"smarthint/loaders/productList.ts": $$$5,
"smarthint/loaders/productListingPage.ts": $$$3,
"smarthint/loaders/recommendations.ts": $$$4,
},
Expand Down
Loading