diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index b2eaa04d..129db6d6 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -9,6 +9,7 @@ const TABS = [ { href: "/admin/scrape/geo", label: "🗺 NSPD geo (rosreestr2coord)" }, { href: "/admin/scrape/cadastre", label: "🏗 Cadastre bulk harvest" }, { href: "/admin/scrape/objective", label: "ETL Objective" }, + { href: "/admin/scrape/cian-cookies", label: "Cian Cookies" }, { href: "/admin/leads", label: "Лиды PRINZIP" }, ]; diff --git a/frontend/src/app/admin/scrape/cian-cookies/page.tsx b/frontend/src/app/admin/scrape/cian-cookies/page.tsx new file mode 100644 index 00000000..6a0c6500 --- /dev/null +++ b/frontend/src/app/admin/scrape/cian-cookies/page.tsx @@ -0,0 +1,462 @@ +"use client"; + +import { useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; + +import { apiFetch } from "@/lib/api"; +import { cardStyle, inputStyle, labelStyle } from "@/lib/adminStyles"; + +interface UploadResponse { + ok?: boolean; + userId?: number; + cookieCount?: number; + detail?: string; +} + +interface TestAuthResponse { + authenticated: boolean; + userId?: number; + reason?: string; +} + +interface CookieEditorEntry { + name: string; + value: string | number | boolean; + [key: string]: unknown; +} + +function convertCookiesFormat(input: string): Record { + const parsed: unknown = JSON.parse(input.trim()); + if (Array.isArray(parsed)) { + // Cookie-Editor array format: [{name, value, domain, ...}, ...] + return (parsed as CookieEditorEntry[]).reduce>( + (acc, c) => { + if ( + typeof c === "object" && + c !== null && + "name" in c && + "value" in c + ) { + acc[String(c.name)] = String(c.value); + } + return acc; + }, + {}, + ); + } + if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) { + // Already a dict: {"name1": "val1", "name2": "val2"} + return Object.fromEntries( + Object.entries(parsed as Record).map(([k, v]) => [ + k, + String(v), + ]), + ); + } + throw new Error("Expected JSON array or plain object"); +} + +function parsePreview(raw: string): { + cookies: Record | null; + error: string | null; +} { + if (!raw.trim()) return { cookies: null, error: null }; + try { + const cookies = convertCookiesFormat(raw); + return { cookies, error: null }; + } catch (e) { + return { cookies: null, error: e instanceof Error ? e.message : String(e) }; + } +} + +export default function CianCookiesPage() { + const [token, setToken] = useState(() => + typeof window === "undefined" + ? "" + : (localStorage.getItem("admin_token") ?? ""), + ); + const [rawInput, setRawInput] = useState(""); + const [showInstructions, setShowInstructions] = useState(false); + + const saveToken = (v: string) => { + setToken(v); + if (typeof window !== "undefined") { + localStorage.setItem("admin_token", v); + } + }; + + const preview = parsePreview(rawInput); + const previewKeys = preview.cookies ? Object.keys(preview.cookies) : []; + + const uploadMutation = useMutation({ + mutationFn: () => { + if (!preview.cookies) { + throw new Error("Invalid JSON — fix the input first"); + } + return apiFetch( + "/api/v1/admin/scrape/cian/upload-cookies", + { + method: "POST", + headers: { "X-Admin-Token": token }, + body: JSON.stringify(preview.cookies), + }, + ); + }, + }); + + const testAuthQuery = useQuery({ + queryKey: ["cian-test-auth", token], + queryFn: () => + apiFetch("/api/v1/admin/scrape/cian/test-auth", { + headers: { "X-Admin-Token": token }, + }), + enabled: false, // only on explicit button click + retry: false, + }); + + const canUpload = + !!token && + !!preview.cookies && + previewKeys.length > 0 && + !uploadMutation.isPending; + + return ( +
+

+ Cian Valuation Calculator — Upload Cookies +

+

+ Позволяет обновить сессию Циан для скрапера оценщика без перезапуска + backend. Backend endpoints: POST{" "} + + /api/v1/admin/scrape/cian/upload-cookies + {" "} + · GET{" "} + + /api/v1/admin/scrape/cian/test-auth + +

+ + {/* Instructions collapsible */} +
+ + {showInstructions && ( +
    +
  1. + Открой{" "} + + + cian.ru + + {" "} + и войди под нужным аккаунтом. +
  2. +
  3. + Установи расширение Cookie-Editor{" "} + (Chrome/Firefox) или открой DevTools → Application → + Cookies → www.cian.ru. +
  4. +
  5. + Cookie-Editor: нажми Export → Export as JSON → + скопируй массив{" "} + + [{"{"}name, value, ...{"}"}] + + . +
  6. +
  7. + DevTools (ручной способ): собери объект{" "} + + {"{"} + {'"'}name1{'"'}: {'"'}val1{'"'}, ...{"}"} + + . +
  8. +
  9. Вставь JSON в поле ниже — формат определится автоматически.
  10. +
  11. + Убедись что preview показывает нужные ключи, затем нажми Upload. +
  12. +
+ )} +
+ + {/* Admin token input */} +
+ + + {/* Textarea */} +