import { BadRequestError } from "@lib/errors.ts"; import { resources } from "@lib/resources.ts"; import { ResourceStatus, SearchResult } from "@lib/types.ts"; import { getTypeSenseClient } from "@lib/typesense.ts"; import { extractHashTags } from "@lib/string.ts"; type ResourceType = keyof typeof resources; type SearchParams = { q: string; type?: ResourceType; tags?: string[]; status?: ResourceStatus; author?: string; query_by?: string; }; export function parseResourceUrl(_url: string): SearchParams | undefined { try { const url = new URL(_url); let query = url.searchParams.get("q"); if (!query) { return undefined; } query = decodeURIComponent(query); const hashTags = extractHashTags(query); for (const tag of hashTags) { query = query.replace("#" + tag, ""); } return { q: query, type: url.searchParams.get("type") as ResourceType || undefined, tags: hashTags, status: url.searchParams.get("status") as ResourceStatus || undefined, query_by: url.searchParams.get("query_by") || undefined, }; } catch (_err) { return undefined; } } export async function searchResource( { q, query_by = "name,description,author,tags", tags = [], type, status }: SearchParams, ): Promise { const typesenseClient = await getTypeSenseClient(); if (!typesenseClient) { throw new Error("Query not available"); } const filter_by: string[] = []; if (type) { filter_by.push(`type:=${type}`); } if (status) { filter_by.push(`status:=${status}`); } if (tags?.length) { filter_by.push(`tags:[${tags.map((t) => `\`${t}\``).join(",")}]`); for (const tag of tags) { q = q.replaceAll(`#${tag}`, ""); } } return await typesenseClient.collections("resources") .documents().search({ q, query_by, facet_by: "rating,author,tags", max_facet_values: 10, filter_by: filter_by.join(" && "), per_page: 50, }); }