From 02c2ce6cd3ba53bbc98335ce4759f99133f20fd2 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sat, 23 May 2026 18:16:19 +0300 Subject: [PATCH] fix(tradein-ui): move Cian cookies upload page to tradein-mvp frontend (routing fix) - Move page from main frontend to tradein-mvp/frontend/src/app/scrapers/cian-cookies/ so /trade-in/api/* Caddy route maps to tradein backend (where /admin/scrape/cian/upload-cookies endpoint lives) - Remove X-Admin-Token header (tradein admin endpoints don't require it; auth = Caddy basic_auth) and remove localStorage token state - Clear textarea on successful upload (security hygiene per review) - Add Cian Cookies tab to tradein-mvp Topbar nav - Revert tab entry in main frontend admin layout --- frontend/src/app/admin/layout.tsx | 1 - .../app/admin/scrape/cian-cookies/page.tsx | 462 ------------------ .../src/app/scrapers/cian-cookies/page.tsx | 351 +++++++++++++ .../src/components/trade-in/Topbar.tsx | 14 +- 4 files changed, 364 insertions(+), 464 deletions(-) delete mode 100644 frontend/src/app/admin/scrape/cian-cookies/page.tsx create mode 100644 tradein-mvp/frontend/src/app/scrapers/cian-cookies/page.tsx diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 129db6d6..b2eaa04d 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -9,7 +9,6 @@ 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 deleted file mode 100644 index 6a0c6500..00000000 --- a/frontend/src/app/admin/scrape/cian-cookies/page.tsx +++ /dev/null @@ -1,462 +0,0 @@ -"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 */} -