Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions apps/api/src/routes/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from "zod";
import { supabase, dbConfig } from "../db/client";
import logger from "../utils/logger";
import { escapeIlike } from "../utils/db";
import { escapePostgrest } from "../utils/db";

const router = Router();

Expand Down Expand Up @@ -111,7 +112,7 @@ async function resolveToGeneric(input: string): Promise<{ input: string; generic
.from("medicines")
.select("brand_name, generic_name")
.or(
`id.eq.${escaped},brand_name.ilike.%${escaped}%,generic_name.ilike.%${escaped}%`
`id.eq.${escaped},brand_name.ilike.%${escapePostgrest(escaped)}%,generic_name.ilike.%${escapePostgrest(escaped)}%`
)
.limit(1)
.maybeSingle();
Expand Down Expand Up @@ -256,7 +257,7 @@ router.post("/check", async (req: Request, res: Response) => {
.from("drug_interactions")
.select("*")
.or(
`and(drug_a_id.eq.${a},drug_b_id.eq.${b}),and(drug_a_id.eq.${b},drug_b_id.eq.${a})`
`and(drug_a_id.eq.${escapePostgrest(a)},drug_b_id.eq.${escapePostgrest(b)}),and(drug_a_id.eq.${escapePostgrest(b)},drug_b_id.eq.${escapePostgrest(a)})`
)
.maybeSingle();

Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/routes/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { supabase } from "../db/client";
import { getMlServiceUrl, MISSING_ML_SERVICE_URL_MESSAGE } from "../config/mlService";
import { validateUploadSize } from "../middleware/uploadSizeValidator";
import { uploadRateLimiter } from "../middleware/uploadRateLimit";

import { escapePostgrest } from "../utils/db";
import { escapeIlike } from "../utils/db";

const router = Router();
Expand Down Expand Up @@ -507,7 +507,7 @@ router.post("/extract", uploadRateLimiter, validateUploadSize, (req: Request, re
"composition, mrp, jan_aushadhi_price"
)
.or(
`brand_name.ilike.%${escapeIlike(matchedName)}%,generic_name.ilike.%${escapeIlike(matchedName)}%`
`brand_name.ilike.%${escapePostgrest(matchedName!)}%,generic_name.ilike.%${escapePostgrest(matchedName!)}%`
)
.limit(1)
.maybeSingle();
Expand Down Expand Up @@ -701,7 +701,7 @@ router.post("/verify-brand", async (req: Request, res: Response) => {
"brand_name, generic_name, manufacturer, batch_number, expiry_date, cdsco_approval_status, is_counterfeit_alert"
)
.or(
`brand_name.ilike.%${escapeIlike(brandName)}%,generic_name.ilike.%${escapeIlike(brandName)}%`
`brand_name.ilike.%${escapePostgrest(brandName)}%,generic_name.ilike.%${escapePostgrest(brandName)}%`
)
.limit(1)
.maybeSingle();
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/services/medicineRag.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from "zod";
import { anonSupabase } from "../db/supabase";
import logger from "../utils/logger";
import { escapeIlike } from "../utils/db";
import { escapeIlike, escapePostgrest } from "../utils/db";

// ── Constants ────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -277,7 +277,7 @@ export async function retrieveRelevantMedicines(
"id, brand_name, generic_name, manufacturer, composition, strength, dosage_form, schedule, mrp, jan_aushadhi_price"
)
.or(
`generic_name.ilike.${pattern},brand_name.ilike.${pattern},composition.ilike.${pattern}`
`generic_name.ilike.${escapePostgrest(pattern)},brand_name.ilike.${escapePostgrest(pattern)},composition.ilike.${escapePostgrest(pattern)}`
)
.limit(limit);

Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/utils/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@
export function escapeIlike(word: string): string {
return word.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
}
/**
* Escapes a value for safe use in PostgREST .or() filters.
* Wraps in double quotes to prevent comma injection.
*/
export function escapePostgrest(val: string): string {
return `"${val
.replace(/\\/g, "\\\\")
.replace(/%/g, "\\%")
.replace(/_/g, "\\_")
.replace(/"/g, '\\"')}"`;
}
5 changes: 4 additions & 1 deletion apps/web/app/[locale]/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { supabase } from "@/lib/supabase";
import { useTranslations } from "next-intl";
import { fuzzyMatchBrand } from "@/lib/api";
import SearchSuggestions, { HistoryItem } from "@/components/SearchSuggestions";
import { escapePostgrest } from "@/lib/supabase/utils";

/** Maximum number of suggestions shown at once */
const MAX_SUGGESTIONS = 8;
Expand Down Expand Up @@ -161,7 +162,9 @@ export default function SearchBar({ dark = false, onSearchChange }: SearchBarPro
const response = await supabase
.from("medicines")
.select("brand_name, batch_number")
.or(`brand_name.ilike.%${trimmed}%,batch_number.ilike.%${trimmed}%`)
.or(
`brand_name.ilike.%${escapePostgrest(trimmed)}%,batch_number.ilike.%${escapePostgrest(trimmed)}%`
)
.abortSignal(controller.signal)
.limit(MAX_SUGGESTIONS);

Expand Down
12 changes: 12 additions & 0 deletions apps/web/lib/supabase/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Escapes a value for safe use in PostgREST .or() and .ilike() filters.
* Wraps in double quotes to prevent comma injection and escapes
* PostgreSQL ILIKE wildcard characters (% and _).
*/
export function escapePostgrest(val: string): string {
return `"${val
.replace(/\\/g, "\\\\")
.replace(/%/g, "\\%")
.replace(/_/g, "\\_")
.replace(/"/g, '\\"')}"`;
}
Loading