84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
import { z } from "zod";
|
|
import {
|
|
BusinessDataBusinessListingsSearchLiveRequestInfo,
|
|
BusinessDataGoogleQuestionsAndAnswersLiveRequestInfo,
|
|
type BusinessDataBusinessListingsSearchLiveItem,
|
|
} from "dataforseo-client";
|
|
import { businessDataApi } from "@/server/lib/dataforseo/core";
|
|
import {
|
|
assertOk,
|
|
buildTaskBilling,
|
|
type DataforseoApiResponse,
|
|
} from "@/server/lib/dataforseo/envelope";
|
|
|
|
type BusinessListingItem = BusinessDataBusinessListingsSearchLiveItem;
|
|
|
|
export async function fetchBusinessListingsSearch(input: {
|
|
categories?: string[];
|
|
title?: string;
|
|
locationCoordinate: string;
|
|
orderBy?: string[];
|
|
limit: number;
|
|
}): Promise<DataforseoApiResponse<BusinessListingItem[]>> {
|
|
const response = await businessDataApi().businessListingsSearchLive([
|
|
new BusinessDataBusinessListingsSearchLiveRequestInfo({
|
|
categories: input.categories,
|
|
title: input.title,
|
|
location_coordinate: input.locationCoordinate,
|
|
order_by: input.orderBy,
|
|
limit: input.limit,
|
|
}),
|
|
]);
|
|
const task = assertOk(response);
|
|
return {
|
|
data: task.result?.[0]?.items ?? [],
|
|
billing: buildTaskBilling(task),
|
|
};
|
|
}
|
|
|
|
// Q&A results carry both answered (`items`) and unanswered
|
|
// (`items_without_answers`) rows; the SDK types this result as `any`, so we
|
|
// validate a generic record shape and flatten both.
|
|
const questionsResultSchema = z
|
|
.object({
|
|
items: z.array(z.record(z.string(), z.unknown())).nullable().optional(),
|
|
items_without_answers: z
|
|
.array(z.record(z.string(), z.unknown()))
|
|
.nullable()
|
|
.optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
function combinedQuestionItems(results: unknown): Record<string, unknown>[] {
|
|
const list = Array.isArray(results) ? results : [];
|
|
return list.flatMap((result) => {
|
|
const parsed = questionsResultSchema.safeParse(result ?? {});
|
|
if (!parsed.success) return [];
|
|
return [
|
|
...(parsed.data.items ?? []),
|
|
...(parsed.data.items_without_answers ?? []),
|
|
];
|
|
});
|
|
}
|
|
|
|
export async function fetchQuestionsAnswers(input: {
|
|
keyword: string;
|
|
locationCoordinate: string;
|
|
languageCode: string;
|
|
depth: number;
|
|
}): Promise<DataforseoApiResponse<Record<string, unknown>[]>> {
|
|
const response = await businessDataApi().googleQuestionsAndAnswersLive([
|
|
new BusinessDataGoogleQuestionsAndAnswersLiveRequestInfo({
|
|
keyword: input.keyword,
|
|
location_coordinate: input.locationCoordinate,
|
|
language_code: input.languageCode,
|
|
depth: input.depth,
|
|
}),
|
|
]);
|
|
const task = assertOk(response);
|
|
return {
|
|
data: combinedQuestionItems(task.result),
|
|
billing: buildTaskBilling(task),
|
|
};
|
|
}
|