Files
winninghunter/src/app/api/tiktok-shop/import/route.ts
T

45 lines
2.9 KiB
TypeScript

import { NextResponse } from "next/server";
import { z } from "zod";
import { currentUser } from "@/lib/auth/current-user";
import { importTikTokProducts, runTikTokShopActor } from "@/lib/apify-tiktok";
import { planFromUser } from "@/lib/plans";
import { checkAndConsumeQuota, refundQuota } from "@/lib/quota";
import { hizSiniriAsimi } from "@/lib/rate-limit";
const schema = z.object({ query: z.string().trim().min(2).max(100), region: z.enum(["US", "GB", "SG", "MY", "PH", "TH", "VN", "ID"]).default("US"), maxResults: z.coerce.number().int().refine((v) => [10, 25, 50].includes(v)) });
export async function POST(request: Request) {
const limited = hizSiniriAsimi(request, "arama");
if (limited) return limited;
const user = await currentUser();
if (!user) return NextResponse.json({ error: "UNAUTHORIZED" }, { status: 401 });
const parsed = schema.safeParse(await request.json().catch(() => null));
if (!parsed.success) return NextResponse.json({ error: "INVALID_INPUT" }, { status: 400 });
const plan = planFromUser(user as never);
const isAdmin = user.role === "ADMIN";
if (!isAdmin && (!plan || plan.code === "FREE")) return NextResponse.json({ error: "UPGRADE_REQUIRED" }, { status: 403 });
const max = isAdmin || plan?.code === "PREMIUM" ? 50 : plan?.code === "STANDARD" ? 25 : 10;
if (parsed.data.maxResults > max) return NextResponse.json({ error: "PLAN_LIMIT_EXCEEDED", maxResults: max }, { status: 403 });
let daily = false;
let credits = false;
if (!isAdmin) {
const dailyQuota = await checkAndConsumeQuota({ userId: user.id, plan, metric: "tiktok_search_daily" });
if (!dailyQuota.allowed) return NextResponse.json({ error: "DAILY_QUOTA_EXCEEDED", usage: dailyQuota }, { status: 403 });
daily = true;
const apiQuota = await checkAndConsumeQuota({ userId: user.id, plan, metric: "api_credits_monthly", amount: parsed.data.maxResults });
if (!apiQuota.allowed) { await refundQuota({ userId: user.id, metric: "tiktok_search_daily" }); return NextResponse.json({ error: "API_CREDITS_EXCEEDED", usage: apiQuota }, { status: 403 }); }
credits = true;
}
try {
const rows = await runTikTokShopActor(parsed.data);
const result = await importTikTokProducts(rows, parsed.data.region);
if (credits && rows.length < parsed.data.maxResults) await refundQuota({ userId: user.id, metric: "api_credits_monthly", amount: parsed.data.maxResults - rows.length });
return NextResponse.json({ ok: true, ...result, received: rows.length });
} catch (error) {
if (daily) await refundQuota({ userId: user.id, metric: "tiktok_search_daily" });
if (credits) await refundQuota({ userId: user.id, metric: "api_credits_monthly", amount: parsed.data.maxResults });
const code = error instanceof Error && /^APIFY_[A-Z0-9_]+$/.test(error.message) ? error.message : "APIFY_TIKTOK_FAILED";
return NextResponse.json({ error: code }, { status: code === "APIFY_NOT_CONFIGURED" ? 503 : 502 });
}
}