From 9e53a7b4fc74b15ea564d2c3d88f84308cdc02f8 Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Sun, 17 May 2026 01:08:19 +0300 Subject: [PATCH] move next to web-front --- .npmrc | 1 + README.md | 4 +- deploy/docker/Dockerfile.web | 12 +- package-lock.json | 480 ++++++++----- package.json | 17 +- tasks/002-crawler.md | 650 ++++++++++++++++++ tsconfig.json | 14 +- vitest.config.ts | 4 +- next-env.d.ts => web-front/next-env.d.ts | 0 next.config.ts => web-front/next.config.ts | 0 .../postcss.config.mjs | 0 {public => web-front/public}/.gitkeep | 0 .../public}/dashcam-product.svg | 0 web-front/scripts/importCatalogExport.ts | 28 + .../app/(dev)/[devRoot]/showcase/layout.tsx | 0 .../app/(dev)/[devRoot]/showcase/page.tsx | 0 .../src}/app/api/videos/route.ts | 0 {src => web-front/src}/app/globals.css | 0 .../src}/app/health/live/route.ts | 0 .../src}/app/health/ready/route.ts | 0 {src => web-front/src}/app/layout.tsx | 0 {src => web-front/src}/app/page.tsx | 0 .../api/importCatalogExport.test.ts | 96 +++ .../api/importCatalogExport.ts | 134 ++++ .../dashcam-catalog/model/schema.test.ts | 69 ++ .../entities/dashcam-catalog/model/schema.ts | 77 +++ .../src}/entities/dashcam/model/data.ts | 0 .../src}/entities/dashcam/model/types.ts | 0 .../src}/entities/video/api/videoCache.ts | 0 .../entities/video/api/videoRepository.ts | 0 .../src}/entities/video/model/types.ts | 0 .../src}/entities/video/model/video.test.ts | 0 .../src}/entities/video/model/video.ts | 0 .../features/register-video/model/actions.ts | 0 .../register-video/ui/RegisterVideoForm.tsx | 0 {src => web-front/src}/shared/config/env.ts | 0 .../src}/shared/db/primitiveJsonDb.ts | 11 + {src => web-front/src}/shared/lib/cn.ts | 0 {src => web-front/src}/shared/lib/date.ts | 0 .../src}/shared/ui/atoms/Badge.tsx | 0 .../src}/shared/ui/atoms/Button.tsx | 0 .../src}/shared/ui/atoms/EvidenceChip.tsx | 0 .../src}/shared/ui/atoms/Icon.tsx | 0 .../src}/shared/ui/atoms/ScoreMeter.tsx | 0 .../src}/shared/ui/molecules/ProductCard.tsx | 0 .../src}/shared/ui/molecules/WinnerRail.tsx | 0 .../src}/shared/ui/organisms/PageHero.tsx | 0 .../ui/organisms/ProductComparisonTable.tsx | 0 .../shared/ui/templates/BestDashcamsPage.tsx | 0 .../widgets/cache-panel/ui/CachePanel.tsx | 0 .../video-dashboard/ui/VideoDashboard.tsx | 0 web-front/tsconfig.json | 20 + 52 files changed, 1442 insertions(+), 175 deletions(-) create mode 100644 tasks/002-crawler.md rename next-env.d.ts => web-front/next-env.d.ts (100%) rename next.config.ts => web-front/next.config.ts (100%) rename postcss.config.mjs => web-front/postcss.config.mjs (100%) rename {public => web-front/public}/.gitkeep (100%) rename {public => web-front/public}/dashcam-product.svg (100%) create mode 100644 web-front/scripts/importCatalogExport.ts rename {src => web-front/src}/app/(dev)/[devRoot]/showcase/layout.tsx (100%) rename {src => web-front/src}/app/(dev)/[devRoot]/showcase/page.tsx (100%) rename {src => web-front/src}/app/api/videos/route.ts (100%) rename {src => web-front/src}/app/globals.css (100%) rename {src => web-front/src}/app/health/live/route.ts (100%) rename {src => web-front/src}/app/health/ready/route.ts (100%) rename {src => web-front/src}/app/layout.tsx (100%) rename {src => web-front/src}/app/page.tsx (100%) create mode 100644 web-front/src/entities/dashcam-catalog/api/importCatalogExport.test.ts create mode 100644 web-front/src/entities/dashcam-catalog/api/importCatalogExport.ts create mode 100644 web-front/src/entities/dashcam-catalog/model/schema.test.ts create mode 100644 web-front/src/entities/dashcam-catalog/model/schema.ts rename {src => web-front/src}/entities/dashcam/model/data.ts (100%) rename {src => web-front/src}/entities/dashcam/model/types.ts (100%) rename {src => web-front/src}/entities/video/api/videoCache.ts (100%) rename {src => web-front/src}/entities/video/api/videoRepository.ts (100%) rename {src => web-front/src}/entities/video/model/types.ts (100%) rename {src => web-front/src}/entities/video/model/video.test.ts (100%) rename {src => web-front/src}/entities/video/model/video.ts (100%) rename {src => web-front/src}/features/register-video/model/actions.ts (100%) rename {src => web-front/src}/features/register-video/ui/RegisterVideoForm.tsx (100%) rename {src => web-front/src}/shared/config/env.ts (100%) rename {src => web-front/src}/shared/db/primitiveJsonDb.ts (87%) rename {src => web-front/src}/shared/lib/cn.ts (100%) rename {src => web-front/src}/shared/lib/date.ts (100%) rename {src => web-front/src}/shared/ui/atoms/Badge.tsx (100%) rename {src => web-front/src}/shared/ui/atoms/Button.tsx (100%) rename {src => web-front/src}/shared/ui/atoms/EvidenceChip.tsx (100%) rename {src => web-front/src}/shared/ui/atoms/Icon.tsx (100%) rename {src => web-front/src}/shared/ui/atoms/ScoreMeter.tsx (100%) rename {src => web-front/src}/shared/ui/molecules/ProductCard.tsx (100%) rename {src => web-front/src}/shared/ui/molecules/WinnerRail.tsx (100%) rename {src => web-front/src}/shared/ui/organisms/PageHero.tsx (100%) rename {src => web-front/src}/shared/ui/organisms/ProductComparisonTable.tsx (100%) rename {src => web-front/src}/shared/ui/templates/BestDashcamsPage.tsx (100%) rename {src => web-front/src}/widgets/cache-panel/ui/CachePanel.tsx (100%) rename {src => web-front/src}/widgets/video-dashboard/ui/VideoDashboard.tsx (100%) create mode 100644 web-front/tsconfig.json diff --git a/.npmrc b/.npmrc index 9f274d6..64ec295 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,4 @@ +registry=https://registry.npmjs.org/ audit=false fund=false update-notifier=false diff --git a/README.md b/README.md index 89aa842..8881eba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ## videoreg.ru -Small Next.js video registry using TypeScript, App Router, FSD-oriented folders, and a primitive file-backed JSON database. +Small video registry using TypeScript, a Next.js web frontend, App Router, FSD-oriented folders, and a primitive file-backed JSON database. + +The Next.js app lives under `web-front/`. Ingestion code lives under `ingestion/`. ### Local development diff --git a/deploy/docker/Dockerfile.web b/deploy/docker/Dockerfile.web index afddc0a..27287cd 100644 --- a/deploy/docker/Dockerfile.web +++ b/deploy/docker/Dockerfile.web @@ -10,9 +10,9 @@ RUN npm install -g npm@11.6.4 \ FROM deps AS build WORKDIR /app ENV NEXT_TELEMETRY_DISABLED=1 -COPY next-env.d.ts next.config.ts tsconfig.json vitest.config.ts ./ -COPY src src -COPY public public +COPY tsconfig.json vitest.config.ts ./ +COPY web-front web-front +COPY ingestion ingestion RUN npm run ci:quality FROM node:22-alpine AS runtime @@ -25,9 +25,9 @@ WORKDIR /app RUN addgroup -S nodeapp && adduser -S nodeapp -G nodeapp && mkdir -p /data && chown nodeapp:nodeapp /data -COPY --from=build /app/.next/standalone ./ -COPY --from=build /app/.next/static ./.next/static -COPY --from=build /app/public ./public +COPY --from=build /app/web-front/.next/standalone ./ +COPY --from=build /app/web-front/.next/static ./.next/static +COPY --from=build /app/web-front/public ./public USER nodeapp EXPOSE 3000 diff --git a/package-lock.json b/package-lock.json index 8219c1a..9ce9b23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,17 @@ "version": "0.1.0", "dependencies": { "next": "16.2.4", + "pg": "8.20.0", "react": "19.2.5", - "react-dom": "19.2.5" + "react-dom": "19.2.5", + "zod": "4.4.2" }, "devDependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@tailwindcss/postcss": "4.2.4", "@types/node": "24.10.1", + "@types/pg": "8.20.0", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "tailwindcss": "4.2.4", @@ -36,7 +39,7 @@ }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", - "resolved": "https://npm.yandex-team.ru/@alloc%2fquick-lru/-/quick-lru-5.2.0.tgz?rbtorrent=e0ab50933e60c2c6f57f6cb5fadcde27aa0a1d3e", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, "license": "MIT", @@ -49,7 +52,7 @@ }, "node_modules/@emnapi/core": { "version": "1.10.0", - "resolved": "https://npm.yandex-team.ru/@emnapi%2fcore/-/core-1.10.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", @@ -61,7 +64,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.10.0", - "resolved": "https://npm.yandex-team.ru/@emnapi%2fruntime/-/runtime-1.10.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "devOptional": true, "license": "MIT", @@ -72,7 +75,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/@emnapi%2fwasi-threads/-/wasi-threads-1.2.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", @@ -82,7 +85,7 @@ }, "node_modules/@img/colour": { "version": "1.1.0", - "resolved": "https://npm.yandex-team.ru/@img%2fcolour/-/colour-1.1.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "optional": true, @@ -92,7 +95,7 @@ }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" @@ -114,7 +117,7 @@ }, "node_modules/@img/sharp-darwin-x64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" @@ -136,7 +139,7 @@ }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" @@ -152,7 +155,7 @@ }, "node_modules/@img/sharp-libvips-darwin-x64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" @@ -168,7 +171,7 @@ }, "node_modules/@img/sharp-libvips-linux-arm": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" @@ -184,7 +187,7 @@ }, "node_modules/@img/sharp-libvips-linux-arm64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" @@ -200,7 +203,7 @@ }, "node_modules/@img/sharp-libvips-linux-ppc64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ "ppc64" @@ -216,7 +219,7 @@ }, "node_modules/@img/sharp-libvips-linux-riscv64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ "riscv64" @@ -232,7 +235,7 @@ }, "node_modules/@img/sharp-libvips-linux-s390x": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" @@ -248,7 +251,7 @@ }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" @@ -264,7 +267,7 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" @@ -280,7 +283,7 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.2.4", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" @@ -296,7 +299,7 @@ }, "node_modules/@img/sharp-linux-arm": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" @@ -318,7 +321,7 @@ }, "node_modules/@img/sharp-linux-arm64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" @@ -340,7 +343,7 @@ }, "node_modules/@img/sharp-linux-ppc64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ "ppc64" @@ -362,7 +365,7 @@ }, "node_modules/@img/sharp-linux-riscv64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ "riscv64" @@ -384,7 +387,7 @@ }, "node_modules/@img/sharp-linux-s390x": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" @@ -406,7 +409,7 @@ }, "node_modules/@img/sharp-linux-x64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" @@ -428,7 +431,7 @@ }, "node_modules/@img/sharp-linuxmusl-arm64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" @@ -450,7 +453,7 @@ }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" @@ -472,7 +475,7 @@ }, "node_modules/@img/sharp-wasm32": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-wasm32/-/sharp-wasm32-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" @@ -491,7 +494,7 @@ }, "node_modules/@img/sharp-win32-arm64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" @@ -510,7 +513,7 @@ }, "node_modules/@img/sharp-win32-ia32": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" @@ -529,7 +532,7 @@ }, "node_modules/@img/sharp-win32-x64": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/@img%2fsharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" @@ -548,7 +551,7 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://npm.yandex-team.ru/@jridgewell%2fgen-mapping/-/gen-mapping-0.3.13.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", @@ -559,7 +562,7 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://npm.yandex-team.ru/@jridgewell%2fremapping/-/remapping-2.3.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", @@ -570,7 +573,7 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://npm.yandex-team.ru/@jridgewell%2fresolve-uri/-/resolve-uri-3.1.2.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", @@ -580,14 +583,14 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://npm.yandex-team.ru/@jridgewell%2fsourcemap-codec/-/sourcemap-codec-1.5.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://npm.yandex-team.ru/@jridgewell%2ftrace-mapping/-/trace-mapping-0.3.31.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", @@ -598,7 +601,7 @@ }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", - "resolved": "https://npm.yandex-team.ru/@napi-rs%2fwasm-runtime/-/wasm-runtime-1.1.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", @@ -617,13 +620,13 @@ }, "node_modules/@next/env": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fenv/-/env-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" @@ -639,7 +642,7 @@ }, "node_modules/@next/swc-darwin-x64": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ "x64" @@ -655,7 +658,7 @@ }, "node_modules/@next/swc-linux-arm64-gnu": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ "arm64" @@ -671,7 +674,7 @@ }, "node_modules/@next/swc-linux-arm64-musl": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ "arm64" @@ -687,7 +690,7 @@ }, "node_modules/@next/swc-linux-x64-gnu": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ "x64" @@ -703,7 +706,7 @@ }, "node_modules/@next/swc-linux-x64-musl": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ "x64" @@ -719,7 +722,7 @@ }, "node_modules/@next/swc-win32-arm64-msvc": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" @@ -735,7 +738,7 @@ }, "node_modules/@next/swc-win32-x64-msvc": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/@next%2fswc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" @@ -751,7 +754,7 @@ }, "node_modules/@oxc-project/types": { "version": "0.127.0", - "resolved": "https://npm.yandex-team.ru/@oxc-project%2ftypes/-/types-0.127.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", "dev": true, "license": "MIT", @@ -761,7 +764,7 @@ }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", "cpu": [ "arm64" @@ -778,7 +781,7 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", "cpu": [ "arm64" @@ -795,7 +798,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", "cpu": [ "x64" @@ -812,7 +815,7 @@ }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", "cpu": [ "x64" @@ -829,7 +832,7 @@ }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", "cpu": [ "arm" @@ -846,7 +849,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", "cpu": [ "arm64" @@ -863,7 +866,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", "cpu": [ "arm64" @@ -880,7 +883,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", "cpu": [ "ppc64" @@ -897,7 +900,7 @@ }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", "cpu": [ "s390x" @@ -914,7 +917,7 @@ }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", "cpu": [ "x64" @@ -931,7 +934,7 @@ }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", "cpu": [ "x64" @@ -948,7 +951,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", "cpu": [ "arm64" @@ -965,7 +968,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", "cpu": [ "wasm32" @@ -984,7 +987,7 @@ }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", "cpu": [ "arm64" @@ -1001,7 +1004,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fbinding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", "cpu": [ "x64" @@ -1018,21 +1021,21 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/@rolldown%2fpluginutils/-/pluginutils-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://npm.yandex-team.ru/@standard-schema%2fspec/-/spec-1.1.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@swc/helpers": { "version": "0.5.15", - "resolved": "https://npm.yandex-team.ru/@swc%2fhelpers/-/helpers-0.5.15.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", "dependencies": { @@ -1041,7 +1044,7 @@ }, "node_modules/@tailwindcss/node": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2fnode/-/node-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", "dev": true, "license": "MIT", @@ -1057,7 +1060,7 @@ }, "node_modules/@tailwindcss/oxide": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide/-/oxide-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", "dev": true, "license": "MIT", @@ -1081,7 +1084,7 @@ }, "node_modules/@tailwindcss/oxide-android-arm64": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", "cpu": [ "arm64" @@ -1098,7 +1101,7 @@ }, "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", "cpu": [ "arm64" @@ -1115,7 +1118,7 @@ }, "node_modules/@tailwindcss/oxide-darwin-x64": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", "cpu": [ "x64" @@ -1132,7 +1135,7 @@ }, "node_modules/@tailwindcss/oxide-freebsd-x64": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", "cpu": [ "x64" @@ -1149,7 +1152,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", "cpu": [ "arm" @@ -1166,7 +1169,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", "cpu": [ "arm64" @@ -1183,7 +1186,7 @@ }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", "cpu": [ "arm64" @@ -1200,7 +1203,7 @@ }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", "cpu": [ "x64" @@ -1217,7 +1220,7 @@ }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", "cpu": [ "x64" @@ -1234,7 +1237,7 @@ }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", "bundleDependencies": [ "@napi-rs/wasm-runtime", @@ -1264,7 +1267,7 @@ }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", "cpu": [ "arm64" @@ -1281,7 +1284,7 @@ }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2foxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", "cpu": [ "x64" @@ -1298,7 +1301,7 @@ }, "node_modules/@tailwindcss/postcss": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/@tailwindcss%2fpostcss/-/postcss-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.4.tgz", "integrity": "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==", "dev": true, "license": "MIT", @@ -1312,7 +1315,7 @@ }, "node_modules/@tailwindcss/postcss/node_modules/postcss": { "version": "8.5.13", - "resolved": "https://npm.yandex-team.ru/postcss/-/postcss-8.5.13.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", "dev": true, "funding": [ @@ -1341,7 +1344,7 @@ }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", - "resolved": "https://npm.yandex-team.ru/@tybys%2fwasm-util/-/wasm-util-0.10.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", @@ -1352,7 +1355,7 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://npm.yandex-team.ru/@types%2fchai/-/chai-5.2.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", @@ -1363,21 +1366,21 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://npm.yandex-team.ru/@types%2fdeep-eql/-/deep-eql-4.0.2.tgz?rbtorrent=86b21a6951ed2f4f095bfc44ad099f52e96a540f", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://npm.yandex-team.ru/@types%2festree/-/estree-1.0.8.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "24.10.1", - "resolved": "https://npm.yandex-team.ru/@types%2fnode/-/node-24.10.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", @@ -1386,9 +1389,21 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", - "resolved": "https://npm.yandex-team.ru/@types%2freact/-/react-19.2.14.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", @@ -1399,7 +1414,7 @@ }, "node_modules/@types/react-dom": { "version": "19.2.3", - "resolved": "https://npm.yandex-team.ru/@types%2freact-dom/-/react-dom-19.2.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", @@ -1409,7 +1424,7 @@ }, "node_modules/@vitest/expect": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2fexpect/-/expect-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", @@ -1427,7 +1442,7 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2fmocker/-/mocker-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", @@ -1454,7 +1469,7 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2fpretty-format/-/pretty-format-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", @@ -1467,7 +1482,7 @@ }, "node_modules/@vitest/runner": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2frunner/-/runner-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", @@ -1481,7 +1496,7 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2fsnapshot/-/snapshot-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", @@ -1497,7 +1512,7 @@ }, "node_modules/@vitest/spy": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2fspy/-/spy-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", @@ -1507,7 +1522,7 @@ }, "node_modules/@vitest/utils": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/@vitest%2futils/-/utils-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", @@ -1522,7 +1537,7 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://npm.yandex-team.ru/assertion-error/-/assertion-error-2.0.1.tgz?rbtorrent=e44e2c582fce4c4572f92579fb25d52bc09ae144", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", @@ -1532,7 +1547,7 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.10.25", - "resolved": "https://npm.yandex-team.ru/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz", "integrity": "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==", "license": "Apache-2.0", "bin": { @@ -1544,7 +1559,7 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001791", - "resolved": "https://npm.yandex-team.ru/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "funding": [ { @@ -1564,7 +1579,7 @@ }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://npm.yandex-team.ru/chai/-/chai-6.2.2.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", @@ -1574,27 +1589,27 @@ }, "node_modules/client-only": { "version": "0.0.1", - "resolved": "https://npm.yandex-team.ru/client-only/-/client-only-0.0.1.tgz?rbtorrent=36b9ca4d2b7c37c1c85069c7059d07dd68f648e4", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/convert-source-map/-/convert-source-map-2.0.0.tgz?rbtorrent=8d247ba4886ecef994137a086a4d9817b81854db", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://npm.yandex-team.ru/csstype/-/csstype-3.2.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://npm.yandex-team.ru/detect-libc/-/detect-libc-2.1.2.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "devOptional": true, "license": "Apache-2.0", @@ -1604,7 +1619,7 @@ }, "node_modules/enhanced-resolve": { "version": "5.21.0", - "resolved": "https://npm.yandex-team.ru/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "dev": true, "license": "MIT", @@ -1618,14 +1633,14 @@ }, "node_modules/es-module-lexer": { "version": "2.1.0", - "resolved": "https://npm.yandex-team.ru/es-module-lexer/-/es-module-lexer-2.1.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://npm.yandex-team.ru/estree-walker/-/estree-walker-3.0.3.tgz?rbtorrent=5c4a77cc1271e3f8348b7ca0183ec2896986ca35", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", @@ -1635,7 +1650,7 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://npm.yandex-team.ru/expect-type/-/expect-type-1.3.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", @@ -1645,7 +1660,7 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://npm.yandex-team.ru/fdir/-/fdir-6.5.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", @@ -1663,7 +1678,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://npm.yandex-team.ru/fsevents/-/fsevents-2.3.3.tgz?rbtorrent=af48e653c1b69837ab1b663db0e3cccbadea9099", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, @@ -1678,25 +1693,24 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://npm.yandex-team.ru/graceful-fs/-/graceful-fs-4.2.11.tgz?rbtorrent=251aa13f9ef4151aff391872f25b06d079421837", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://npm.yandex-team.ru/jiti/-/jiti-2.6.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss/-/lightningcss-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", @@ -1726,7 +1740,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" @@ -1747,7 +1761,7 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" @@ -1768,7 +1782,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" @@ -1789,7 +1803,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" @@ -1810,7 +1824,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" @@ -1831,7 +1845,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" @@ -1852,7 +1866,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" @@ -1873,7 +1887,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" @@ -1894,7 +1908,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" @@ -1915,7 +1929,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" @@ -1936,7 +1950,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "resolved": "https://npm.yandex-team.ru/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" @@ -1957,7 +1971,7 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://npm.yandex-team.ru/magic-string/-/magic-string-0.30.21.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", @@ -1967,7 +1981,7 @@ }, "node_modules/nanoid": { "version": "3.3.12", - "resolved": "https://npm.yandex-team.ru/nanoid/-/nanoid-3.3.12.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { @@ -1985,7 +1999,7 @@ }, "node_modules/next": { "version": "16.2.4", - "resolved": "https://npm.yandex-team.ru/next/-/next-16.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", "license": "MIT", "dependencies": { @@ -2038,7 +2052,7 @@ }, "node_modules/obug": { "version": "2.1.1", - "resolved": "https://npm.yandex-team.ru/obug/-/obug-2.1.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ @@ -2049,20 +2063,110 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://npm.yandex-team.ru/pathe/-/pathe-2.0.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://npm.yandex-team.ru/picocolors/-/picocolors-1.1.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://npm.yandex-team.ru/picomatch/-/picomatch-4.0.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", @@ -2076,7 +2180,7 @@ }, "node_modules/postcss": { "version": "8.4.31", - "resolved": "https://npm.yandex-team.ru/postcss/-/postcss-8.4.31.tgz?rbtorrent=217e20884324e43b47ce44404002fccc49a6ab44", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { @@ -2102,9 +2206,48 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "19.2.5", - "resolved": "https://npm.yandex-team.ru/react/-/react-19.2.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "peer": true, @@ -2114,7 +2257,7 @@ }, "node_modules/react-dom": { "version": "19.2.5", - "resolved": "https://npm.yandex-team.ru/react-dom/-/react-dom-19.2.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "peer": true, @@ -2127,7 +2270,7 @@ }, "node_modules/rolldown": { "version": "1.0.0-rc.17", - "resolved": "https://npm.yandex-team.ru/rolldown/-/rolldown-1.0.0-rc.17.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", "dev": true, "license": "MIT", @@ -2161,13 +2304,13 @@ }, "node_modules/scheduler": { "version": "0.27.0", - "resolved": "https://npm.yandex-team.ru/scheduler/-/scheduler-0.27.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://npm.yandex-team.ru/semver/-/semver-7.7.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "optional": true, @@ -2180,7 +2323,7 @@ }, "node_modules/sharp": { "version": "0.34.5", - "resolved": "https://npm.yandex-team.ru/sharp/-/sharp-0.34.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", @@ -2225,37 +2368,46 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://npm.yandex-team.ru/siginfo/-/siginfo-2.0.0.tgz?rbtorrent=c81f36a5482caeab614c579277ba7d9410225594", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://npm.yandex-team.ru/source-map-js/-/source-map-js-1.2.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://npm.yandex-team.ru/stackback/-/stackback-0.0.2.tgz?rbtorrent=bdf62245e16001accd64dbf9710961fd2a4ec3d6", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha1-Gsig2Ug4SNFpXkGLbQMaPDzmjjs=", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.1.0", - "resolved": "https://npm.yandex-team.ru/std-env/-/std-env-4.1.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, "node_modules/styled-jsx": { "version": "5.1.6", - "resolved": "https://npm.yandex-team.ru/styled-jsx/-/styled-jsx-5.1.6.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { @@ -2278,14 +2430,14 @@ }, "node_modules/tailwindcss": { "version": "4.2.4", - "resolved": "https://npm.yandex-team.ru/tailwindcss/-/tailwindcss-4.2.4.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", - "resolved": "https://npm.yandex-team.ru/tapable/-/tapable-2.3.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", @@ -2299,14 +2451,14 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://npm.yandex-team.ru/tinybench/-/tinybench-2.9.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.1.2", - "resolved": "https://npm.yandex-team.ru/tinyexec/-/tinyexec-1.1.2.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", "dev": true, "license": "MIT", @@ -2316,7 +2468,7 @@ }, "node_modules/tinyglobby": { "version": "0.2.16", - "resolved": "https://npm.yandex-team.ru/tinyglobby/-/tinyglobby-0.2.16.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", @@ -2333,7 +2485,7 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://npm.yandex-team.ru/tinyrainbow/-/tinyrainbow-3.1.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", @@ -2343,13 +2495,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://npm.yandex-team.ru/tslib/-/tslib-2.8.1.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://npm.yandex-team.ru/typescript/-/typescript-5.9.3.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", @@ -2363,14 +2515,14 @@ }, "node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://npm.yandex-team.ru/undici-types/-/undici-types-7.16.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "8.0.10", - "resolved": "https://npm.yandex-team.ru/vite/-/vite-8.0.10.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", "dev": true, "license": "MIT", @@ -2449,7 +2601,7 @@ }, "node_modules/vite/node_modules/postcss": { "version": "8.5.13", - "resolved": "https://npm.yandex-team.ru/postcss/-/postcss-8.5.13.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", "dev": true, "funding": [ @@ -2478,7 +2630,7 @@ }, "node_modules/vitest": { "version": "4.1.5", - "resolved": "https://npm.yandex-team.ru/vitest/-/vitest-4.1.5.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", @@ -2568,7 +2720,7 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://npm.yandex-team.ru/why-is-node-running/-/why-is-node-running-2.3.0.tgz?rbtorrent=", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", @@ -2582,6 +2734,24 @@ "engines": { "node": ">=8" } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/zod": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz", + "integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index ac719fa..63c1e4e 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,25 @@ "version": "0.1.0", "type": "module", "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", + "dev": "next dev web-front", + "build": "next build web-front", + "start": "next start web-front", "typecheck": "tsc --noEmit", "test": "vitest run", + "catalog:import": "node --experimental-strip-types web-front/scripts/importCatalogExport.ts", + "ingestion:migrate": "node --experimental-strip-types ingestion/src/migrate.ts", + "ingestion:worker": "node --experimental-strip-types ingestion/src/worker.ts", + "ingestion:admin": "node --experimental-strip-types ingestion/src/adminServer.ts", + "ingestion:export": "node --experimental-strip-types ingestion/src/exportServer.ts", + "ingestion:export-catalog": "node --experimental-strip-types ingestion/src/exportCatalog.ts", "ci:quality": "npm run typecheck && npm test && npm run build" }, "dependencies": { "next": "16.2.4", + "pg": "8.20.0", "react": "19.2.5", - "react-dom": "19.2.5" + "react-dom": "19.2.5", + "zod": "4.4.2" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", @@ -31,6 +39,7 @@ "@emnapi/runtime": "1.10.0", "@tailwindcss/postcss": "4.2.4", "@types/node": "24.10.1", + "@types/pg": "8.20.0", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "tailwindcss": "4.2.4", diff --git a/tasks/002-crawler.md b/tasks/002-crawler.md new file mode 100644 index 0000000..b73e48b --- /dev/null +++ b/tasks/002-crawler.md @@ -0,0 +1,650 @@ +# Спека: Yandex Market Dashcam Catalog Ingestion + +## 1. Цель + +Построить поддерживаемый ingestion engine для каталога автомобильных видеорегистраторов. + +Целевая категория: + +`Автомобильные видеорегистраторы` + +Публичное приложение должно получать только approved canonical catalog: + +* название +* бренд +* модель +* нормализованные характеристики +* изображения +* source metadata +* дату первого обнаружения +* дату последнего обновления + +В MVP не парсим и не экспортируем: + +* цены +* продавцов +* наличие +* тексты отзывов +* авторов отзывов +* review content + +Общий aggregate rating можно сохранять внутри ingestion как source signal, но он не входит в публичный export и не участвует в `stableContentHash`. + +Главная мысль: не "краулер, который пролезет", а каталоговый ingestion engine, который можно поддерживать годами. + +--- + +## 2. Границы и запреты + +Не реализовывать: + +* обход rate limiter +* маскировку под реальных пользователей +* ротацию User-Agent / browser fingerprint +* прокси-фермы +* headless stealth +* обход captcha +* агрессивную параллельную загрузку +* регулярный browser-based crawl + +В MVP fetcher работает только через обычный HTTP fetch. + +Headless browser допустим только как ручной diagnostic tool для анализа структуры страницы. Он не является fallback в регулярном crawl path. + +--- + +## 3. Источники + +### Primary + +* публичные category pages Яндекс Маркета +* публичные product pages +* JSON-LD, если доступен в HTML +* embedded state, если доступен в HTML +* sitemap, но только в ограниченных границах + +### Preferred official fallback + +* Yandex Market Partner API, если доступен по условиям проекта +* фиды продавцов, если доступны легально +* фиды брендов +* официальные сайты производителей + +--- + +## 4. Deployment Model + +Ingestion живет отдельно от облачного runtime приложения. + +Код хранится в этом же репозитории: + +```text +ingestion/ + src/ + migrations/ + docker-compose.yml + Dockerfile +``` + +Стек ingestion деплоится отдельным `docker-compose.yml`. + +Минимальные сервисы: + +```text +postgres +ingestion-worker +ingestion-admin +export-server +``` + +Основное Next.js-приложение не подключается напрямую к ingestion DB. + +--- + +## 5. Source of Truth и Bridge + +Source of truth для каталога: PostgreSQL ingestion stack. + +Текущее примитивное хранилище приложения (`data/videoreg.json`) является только read-model cache. + +Bridge работает со стороны приложения: + +1. ingestion публикует versioned HTTPS export; +2. приложение вручную запускает operator import command; +3. import command скачивает manifest; +4. проверяет `schemaVersion`, `exportId`, `createdAt`, `sha256`, `itemCount`; +5. скачивает catalog artifact; +6. валидирует payload через shared Zod schema; +7. атомарно заменяет `data/videoreg.json`. + +В MVP import запускается вручную оператором, не по расписанию. + +--- + +## 6. Export Contract + +Export доступен по HTTPS со статичным токеном: + +```http +Authorization: Bearer +``` + +Токен передается через env import-команды. Токен не передается в query string. + +### Manifest + +```ts +type CatalogExportManifest = { + schemaVersion: 1; + exportId: string; + createdAt: string; + artifactUrl: string; + sha256: string; + itemCount: number; +}; +``` + +### Catalog Artifact + +```ts +type DashcamCatalogExport = { + schemaVersion: 1; + exportId: string; + createdAt: string; + products: ExportedDashcamProduct[]; +}; + +type ExportedDashcamProduct = { + id: string; + brand: string; + model: string; + title: string; + specs: Record; + images: ExportedProductImage[]; + sources: ExportedProductSource[]; + discoveredAt: string; + updatedAt: string; +}; + +type ExportedProductImage = { + url: string; + originalUrl: string; + source: "yandex_market"; + sha256?: string; + width?: number; + height?: number; +}; + +type ExportedProductSource = { + source: "yandex_market"; + sourceProductId?: string; + url: string; + stableContentHash: string; + fetchedAt: string; +}; +``` + +Runtime validation: shared Zod schema в репозитории. TypeScript-типы без runtime-валидации недостаточны. + +--- + +## 7. Image Policy + +Ingestion кеширует approved images и публикует versioned image artifact URLs. + +Export содержит: + +* URL versioned image artifact +* original source URL +* source metadata +* optional checksum/dimensions + +Приложение не должно hotlink-ить source images напрямую. + +Юридическое решение о хранении и показе изображений должно быть принято отдельно до production-публикации. + +--- + +## 8. Crawl Policy + +Нагрузка: + +* 1 запрос за 5-15 секунд +* максимум 100-300 страниц в сутки на источник +* jitter между запросами +* exponential backoff при 429/403/5xx +* остановка источника при captcha +* отдельный дневной budget на category pages, product pages, sitemap +* не скачивать повторно страницы, если stable content не изменился + +HTTP-only fetcher должен явно детектить: + +```text +rate_limited +blocked +captcha +not_found +parse_failed +needs_manual_review +``` + +При `captcha`, `403`, `429` источник ставится на паузу. Не пытаться обходить ограничение. + +--- + +## 9. Discovery Strategy + +Discovery работает sitemap-first, но не обходит широкий sitemap всего Маркета. + +Границы discovery задают: + +* ручные seed category URLs +* ручные seed product URL patterns +* product URLs, найденные из category HTTP discovery +* уже известные product URLs + +Sitemap используется для: + +* diff известных URL +* refresh кандидатных URL +* обнаружение URL рядом с уже известными границами + +Category pages используются вторично: + +* первая страница категории +* пагинация, если доступна без JS +* top listings +* новые product URLs, если они есть в HTML + +Если category HTML бедный без JS, discovery не должен переходить в regular headless crawl. Такие случаи уходят в `needs_manual_review` или official fallback. + +```ts +type DiscoveredProduct = { + source: "yandex_market"; + categoryUrl?: string; + productUrl: string; + sourceProductId?: string; + titlePreview?: string; + discoveredAt: string; +}; +``` + +--- + +## 10. Fetch Queue + +Очередь реализуется в PostgreSQL, без Redis/BullMQ в MVP. + +State machine: + +```text +pending + -> fetching + -> fetched + -> parsed + -> normalized + -> deduplicated + -> needs_review + -> approved + -> exported +``` + +Ошибочные состояния: + +```text +rate_limited +blocked +captcha +not_found +parse_failed +needs_manual_review +``` + +Для каждой задачи хранить: + +* source +* URL +* job type +* state +* attempts +* nextRunAt +* lastError +* createdAt +* updatedAt + +Backoff и daily budgets считаются в PostgreSQL. + +--- + +## 11. Parser + +Parser извлекает только разрешенные поля: + +* title +* brand +* model +* specs +* images +* sourceProductId +* aggregate rating как internal-only signal + +Приоритет источников внутри страницы: + +```text +JSON-LD > embedded state > semantic DOM > fallback DOM +``` + +Не извлекать: + +* prices +* sellers +* availability +* review texts +* review authors + +```ts +type ParsedMarketProduct = { + source: "yandex_market"; + sourceProductId?: string; + title: string; + brand?: string; + model?: string; + aggregateRating?: { + value: number; + scale?: number; + ratingCount?: number; + }; + images: string[]; + specs: Record; + sourceUrl: string; +}; +``` + +--- + +## 12. Snapshot и Checksum Model + +Сырой snapshot хранить всегда. Парсер должен уметь переобрабатывать старые страницы. + +Нельзя использовать hash всего HTML как сигнал "страница изменилась": динамическая верстка, цены, рекомендации, счетчики и timestamps будут постоянно шуметь. + +Для каждого snapshot хранить два хэша: + +```ts +type ProductSourceSnapshot = { + source: "yandex_market"; + url: string; + fetchedAt: string; + httpStatus: number; + rawSnapshotHash: string; + stableContentHash?: string; + rawHtmlPath?: string; + parsedJson: Record; +}; +``` + +### `rawSnapshotHash` + +Хэш точного raw snapshot для аудита и debug. + +### `stableContentHash` + +Source-specific semantic hash. Для Яндекс Маркета в MVP включать: + +* sourceProductId +* canonical product URL +* title +* brand +* model +* normalized specs +* image identifiers / original image URLs + +Исключать: + +* price +* sellers +* availability +* aggregate rating +* review count +* timestamps +* layout +* recommendations +* counters +* tracking data + +--- + +## 13. Normalization + +Нужно привести хаотичные характеристики к единому виду. + +Пример: + +```json +{ + "resolution_front": "3840x2160", + "channels": 2, + "sensor_front": "Sony STARVIS 2", + "gps": true, + "wifi": true, + "screen": true, + "parking_mode": true, + "memory_card_max_gb": 256, + "view_angle_degrees": 140 +} +``` + +Компоненты normalizer: + +* brand dictionary +* model cleaner +* spec mapper +* unit converter +* boolean detector +* alias dictionary + +--- + +## 14. Deduplication + +Дедупликация работает до модерации. Единица модерации: canonical product, уже сгруппированный из вариантов и source URLs. + +Сравнивать: + +* brand +* normalized model name +* aliases +* sourceProductId +* image perceptual hash +* specs similarity +* product URL history + +Статусы: + +```text +new_product +same_product +possible_duplicate +variant +oem_clone +``` + +`possible_duplicate`, `variant`, `oem_clone` не должны попадать в public export без ручного решения. + +--- + +## 15. Moderation Admin + +Админка живет внутри отдельного ingestion stack. + +Она работает напрямую с ingestion PostgreSQL, snapshots, очередями и canonical products. + +MVP-экраны: + +* новые canonical products +* bulk select +* "разрешить все" +* товары с `parse_failed` +* возможные дубли +* конфликтующие характеристики +* история изменений +* ручное объединение моделей +* ручное редактирование canonical specs + +Публикация в export разрешена только после ручной модерации. Bulk approve допустим, но утверждает canonical products, а не отдельные source URLs. + +--- + +## 16. Database + +Минимальная PostgreSQL-модель: + +```sql +products +product_variants +product_sources +source_snapshots +product_specs +product_images +source_ratings +crawl_jobs +crawl_errors +moderation_queue +export_runs +``` + +Принципы: + +* raw HTML/JSON snapshots и image artifacts хранить на mounted volume +* в PostgreSQL хранить пути, checksum, metadata и связи +* уникальность по `source + url` +* ingestion DB является source of truth +* `data/videoreg.json` приложения является replaceable read-model cache + +--- + +## 17. Sync Schedule + +### Частый легкий sync + +Каждые 6-12 часов: + +* первая страница категории +* sitemap diff в пределах известных границ +* новые product URLs +* изменение top listings, если доступно без JS + +### Глубокий sync + +Раз в 7-14 дней: + +* все известные карточки +* характеристики +* изображения +* source metadata + +### New product detection + +Каждый день: + +* category diff +* bounded sitemap diff +* known model aliases search +* official fallback feeds, если доступны + +--- + +## 18. MVP Phases + +### Phase 1 - Legal discovery + +* проверить robots.txt +* проверить sitemap +* проверить доступность API/фидов +* описать crawl budget +* описать allowed URL patterns +* зафиксировать image storage policy + +### Phase 2 - Shared contract + +* добавить shared Zod schema для manifest/catalog export +* добавить TypeScript-типы из схем +* описать `schemaVersion` +* добавить app import command +* заменить `data/videoreg.json` validated catalog payload + +### Phase 3 - Ingestion stack skeleton + +* создать `ingestion/` +* добавить `docker-compose.yml` +* поднять PostgreSQL +* добавить migrations +* добавить mounted volume для snapshots/images +* добавить healthcheck + +### Phase 4 - Queue and fetcher + +* реализовать PostgreSQL-backed queue +* реализовать HTTP-only fetcher +* добавить rate limits, retry, backoff +* stop on captcha / 403 / 429 +* сохранять raw snapshots +* считать `rawSnapshotHash` + +### Phase 5 - Parser + +* category parser +* bounded sitemap discovery +* product parser +* specs parser +* image parser +* aggregate rating parser as internal-only +* source-specific `stableContentHash` + +### Phase 6 - Normalizer + +* brand dictionary +* model cleaner +* spec mapper +* unit converter +* boolean detector + +### Phase 7 - Deduplication and moderation + +* exact match +* fuzzy match +* alias match +* canonical product grouping +* moderation queue +* bulk approve + +### Phase 8 - Export and import + +* publish versioned manifest +* publish versioned catalog artifact +* publish versioned image artifacts +* protect endpoints with Bearer token +* verify checksum in app import +* atomic replace of `data/videoreg.json` + +--- + +## 19. Definition of Done + +Система считается готовой, если: + +* ingestion stack запускается отдельно через `docker-compose.yml` +* PostgreSQL является source of truth +* приложение импортирует только approved canonical catalog +* `data/videoreg.json` заменяется только после manifest/checksum/schema validation +* crawler не создает заметной нагрузки на источник +* crawler останавливается при ограничениях +* crawler не использует regular headless/browser stealth +* raw snapshots хранятся всегда +* есть `rawSnapshotHash` и source-specific `stableContentHash` +* старые snapshots можно переобработать новым parser +* дубли попадают в moderation queue +* bulk approve работает по canonical products +* public export не содержит цены, продавцов, наличие и тексты отзывов +* public export не содержит aggregate rating в MVP +* approved images отдаются как versioned image artifacts diff --git a/tsconfig.json b/tsconfig.json index 752a139..0c3ba59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", @@ -24,18 +25,17 @@ ], "paths": { "@/*": [ - "./src/*" + "./web-front/src/*" ] } }, "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + "web-front/**/*.ts", + "web-front/**/*.tsx", + "ingestion/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "web-front/.next" ] } diff --git a/vitest.config.ts b/vitest.config.ts index 1b0d162..8d85605 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,11 +4,11 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'node', - include: ['src/**/*.test.ts'], + include: ['web-front/src/**/*.test.ts', 'ingestion/src/**/*.test.ts'], }, resolve: { alias: { - '@': path.resolve(__dirname, 'src'), + '@': path.resolve(__dirname, 'web-front/src'), }, }, }); diff --git a/next-env.d.ts b/web-front/next-env.d.ts similarity index 100% rename from next-env.d.ts rename to web-front/next-env.d.ts diff --git a/next.config.ts b/web-front/next.config.ts similarity index 100% rename from next.config.ts rename to web-front/next.config.ts diff --git a/postcss.config.mjs b/web-front/postcss.config.mjs similarity index 100% rename from postcss.config.mjs rename to web-front/postcss.config.mjs diff --git a/public/.gitkeep b/web-front/public/.gitkeep similarity index 100% rename from public/.gitkeep rename to web-front/public/.gitkeep diff --git a/public/dashcam-product.svg b/web-front/public/dashcam-product.svg similarity index 100% rename from public/dashcam-product.svg rename to web-front/public/dashcam-product.svg diff --git a/web-front/scripts/importCatalogExport.ts b/web-front/scripts/importCatalogExport.ts new file mode 100644 index 0000000..c7a8687 --- /dev/null +++ b/web-front/scripts/importCatalogExport.ts @@ -0,0 +1,28 @@ +import { importCatalogExport } from '../src/entities/dashcam-catalog/api/importCatalogExport.ts'; + +const manifestUrl = process.env.DASHCAM_CATALOG_MANIFEST_URL ?? process.argv[2]; +const token = process.env.DASHCAM_CATALOG_EXPORT_TOKEN; + +if (!manifestUrl) { + throw new Error('Set DASHCAM_CATALOG_MANIFEST_URL or pass manifest URL as the first argument'); +} + +if (!token) { + throw new Error('Set DASHCAM_CATALOG_EXPORT_TOKEN'); +} + +const result = await importCatalogExport({ manifestUrl, token }); + +console.log( + JSON.stringify( + { + status: 'imported', + exportId: result.exportId, + itemCount: result.itemCount, + sha256: result.sha256, + dbFilePath: result.dbFilePath, + }, + null, + 2, + ), +); diff --git a/src/app/(dev)/[devRoot]/showcase/layout.tsx b/web-front/src/app/(dev)/[devRoot]/showcase/layout.tsx similarity index 100% rename from src/app/(dev)/[devRoot]/showcase/layout.tsx rename to web-front/src/app/(dev)/[devRoot]/showcase/layout.tsx diff --git a/src/app/(dev)/[devRoot]/showcase/page.tsx b/web-front/src/app/(dev)/[devRoot]/showcase/page.tsx similarity index 100% rename from src/app/(dev)/[devRoot]/showcase/page.tsx rename to web-front/src/app/(dev)/[devRoot]/showcase/page.tsx diff --git a/src/app/api/videos/route.ts b/web-front/src/app/api/videos/route.ts similarity index 100% rename from src/app/api/videos/route.ts rename to web-front/src/app/api/videos/route.ts diff --git a/src/app/globals.css b/web-front/src/app/globals.css similarity index 100% rename from src/app/globals.css rename to web-front/src/app/globals.css diff --git a/src/app/health/live/route.ts b/web-front/src/app/health/live/route.ts similarity index 100% rename from src/app/health/live/route.ts rename to web-front/src/app/health/live/route.ts diff --git a/src/app/health/ready/route.ts b/web-front/src/app/health/ready/route.ts similarity index 100% rename from src/app/health/ready/route.ts rename to web-front/src/app/health/ready/route.ts diff --git a/src/app/layout.tsx b/web-front/src/app/layout.tsx similarity index 100% rename from src/app/layout.tsx rename to web-front/src/app/layout.tsx diff --git a/src/app/page.tsx b/web-front/src/app/page.tsx similarity index 100% rename from src/app/page.tsx rename to web-front/src/app/page.tsx diff --git a/web-front/src/entities/dashcam-catalog/api/importCatalogExport.test.ts b/web-front/src/entities/dashcam-catalog/api/importCatalogExport.test.ts new file mode 100644 index 0000000..5624ae6 --- /dev/null +++ b/web-front/src/entities/dashcam-catalog/api/importCatalogExport.test.ts @@ -0,0 +1,96 @@ +import { createHash } from 'node:crypto'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { importCatalogExport } from './importCatalogExport'; + +function jsonResponse(payload: unknown) { + return new Response(JSON.stringify(payload), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }); +} + +describe('importCatalogExport', () => { + it('validates manifest, checksum and atomically updates the primitive read model', async () => { + const tempDir = await mkdtemp(path.join(os.tmpdir(), 'videoreg-catalog-import-')); + const dbFilePath = path.join(tempDir, 'videoreg.json'); + + try { + const catalog = { + schemaVersion: 1, + exportId: 'export_1', + createdAt: '2026-05-16T21:00:00.000Z', + products: [ + { + id: 'ibox-f5', + brand: 'iBOX', + model: 'F5 WiFi', + title: 'Видеорегистратор для автомобиля iBOX F5 + WiFi с радар-детектором', + specs: { + channels: 1, + wifi: true, + }, + images: [], + sources: [ + { + source: 'yandex_market', + sourceProductId: '5197387201', + url: 'https://market.yandex.ru/card/ibox-f5-wifi-videoregistrator-s-signaturnym-radar-detektorom/5197387201', + stableContentHash: 'stable-hash', + fetchedAt: '2026-05-16T20:00:00.000Z', + }, + ], + discoveredAt: '2026-05-16T20:00:00.000Z', + updatedAt: '2026-05-16T20:00:00.000Z', + }, + ], + }; + const catalogBytes = Buffer.from(JSON.stringify(catalog)); + const digest = createHash('sha256').update(catalogBytes).digest('hex'); + const fetcher = async (input: string | URL) => { + const url = input.toString(); + + if (url === 'https://exports.example.test/manifest.json') { + return jsonResponse({ + schemaVersion: 1, + exportId: 'export_1', + createdAt: '2026-05-16T21:00:00.000Z', + artifactUrl: 'https://exports.example.test/catalog.json', + sha256: digest, + itemCount: 1, + }); + } + + if (url === 'https://exports.example.test/catalog.json') { + return jsonResponse(catalog); + } + + return new Response('not found', { status: 404 }); + }; + + const result = await importCatalogExport({ + manifestUrl: 'https://exports.example.test/manifest.json', + token: 'secret', + dbFilePath, + fetcher, + now: new Date('2026-05-16T22:00:00.000Z'), + }); + const stored = JSON.parse(await readFile(dbFilePath, 'utf8')) as Record; + + expect(result).toMatchObject({ + exportId: 'export_1', + itemCount: 1, + sha256: digest, + }); + expect(stored.dashcamCatalog).toEqual(catalog); + expect(stored.dashcamCatalogImportedAt).toBe('2026-05-16T22:00:00.000Z'); + expect(stored.videos).toEqual([]); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } + }); +}); diff --git a/web-front/src/entities/dashcam-catalog/api/importCatalogExport.ts b/web-front/src/entities/dashcam-catalog/api/importCatalogExport.ts new file mode 100644 index 0000000..d99ffe4 --- /dev/null +++ b/web-front/src/entities/dashcam-catalog/api/importCatalogExport.ts @@ -0,0 +1,134 @@ +import { createHash } from 'node:crypto'; +import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { + parseCatalogExportManifest, + parseDashcamCatalogExport, + type DashcamCatalogExport, +} from '../model/schema.ts'; + +type FetchLike = (input: string | URL, init?: RequestInit) => Promise; + +type PrimitiveDbCache = { + videos?: unknown[]; + dashcamCatalog?: DashcamCatalogExport; + dashcamCatalogImportedAt?: string; +}; + +export type ImportCatalogExportOptions = { + manifestUrl: string; + token: string; + dbFilePath?: string; + fetcher?: FetchLike; + now?: Date; +}; + +export type ImportCatalogExportResult = { + exportId: string; + itemCount: number; + sha256: string; + dbFilePath: string; +}; + +function getDefaultDbFilePath() { + if (process.env.VIDEOREG_DB_FILE) { + return process.env.VIDEOREG_DB_FILE; + } + + return path.join(process.cwd(), 'data', 'videoreg.json'); +} + +function sha256(buffer: Buffer) { + return createHash('sha256').update(buffer).digest('hex'); +} + +async function readExistingDb(filePath: string): Promise { + try { + const raw = await readFile(filePath, 'utf8'); + const parsed = JSON.parse(raw) as PrimitiveDbCache; + + return { + ...parsed, + videos: Array.isArray(parsed.videos) ? parsed.videos : [], + }; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return { videos: [] }; + } + + throw error; + } +} + +async function writeJsonAtomically(filePath: string, payload: PrimitiveDbCache) { + const temporaryPath = `${filePath}.${process.pid}.tmp`; + + await mkdir(path.dirname(filePath), { recursive: true }); + await writeFile(temporaryPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); + await rename(temporaryPath, filePath); +} + +async function fetchJsonBytes(fetcher: FetchLike, url: string, token: string) { + const response = await fetcher(url, { + headers: { + authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Catalog export request failed: ${response.status} ${response.statusText}`); + } + + return Buffer.from(await response.arrayBuffer()); +} + +function parseJson(buffer: Buffer, label: string) { + try { + return JSON.parse(buffer.toString('utf8')) as unknown; + } catch (error) { + throw new Error(`Invalid ${label} JSON: ${(error as Error).message}`); + } +} + +export async function importCatalogExport(options: ImportCatalogExportOptions): Promise { + const fetcher = options.fetcher ?? fetch; + const dbFilePath = options.dbFilePath ?? getDefaultDbFilePath(); + const importedAt = (options.now ?? new Date()).toISOString(); + + const manifestBytes = await fetchJsonBytes(fetcher, options.manifestUrl, options.token); + const manifest = parseCatalogExportManifest(parseJson(manifestBytes, 'manifest')); + const artifactBytes = await fetchJsonBytes(fetcher, manifest.artifactUrl, options.token); + const artifactHash = sha256(artifactBytes); + + if (artifactHash.toLowerCase() !== manifest.sha256.toLowerCase()) { + throw new Error(`Catalog artifact checksum mismatch: expected ${manifest.sha256}, got ${artifactHash}`); + } + + const catalog = parseDashcamCatalogExport(parseJson(artifactBytes, 'catalog artifact')); + + if (catalog.exportId !== manifest.exportId) { + throw new Error(`Catalog exportId mismatch: manifest=${manifest.exportId}, artifact=${catalog.exportId}`); + } + + if (catalog.createdAt !== manifest.createdAt) { + throw new Error(`Catalog createdAt mismatch: manifest=${manifest.createdAt}, artifact=${catalog.createdAt}`); + } + + if (catalog.products.length !== manifest.itemCount) { + throw new Error(`Catalog itemCount mismatch: manifest=${manifest.itemCount}, artifact=${catalog.products.length}`); + } + + const current = await readExistingDb(dbFilePath); + await writeJsonAtomically(dbFilePath, { + ...current, + dashcamCatalog: catalog, + dashcamCatalogImportedAt: importedAt, + }); + + return { + exportId: manifest.exportId, + itemCount: manifest.itemCount, + sha256: artifactHash, + dbFilePath, + }; +} diff --git a/web-front/src/entities/dashcam-catalog/model/schema.test.ts b/web-front/src/entities/dashcam-catalog/model/schema.test.ts new file mode 100644 index 0000000..72b7a14 --- /dev/null +++ b/web-front/src/entities/dashcam-catalog/model/schema.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; +import { DashcamCatalogExportSchema } from './schema'; + +const baseProduct = { + id: 'ibox-f5', + brand: 'iBOX', + model: 'F5 WiFi', + title: 'Видеорегистратор для автомобиля iBOX F5 + WiFi с радар-детектором', + specs: { + channels: 1, + wifi: true, + resolution_front: '2304x1296', + }, + images: [ + { + url: 'https://cdn.example.test/images/ibox-f5/orig.jpg', + originalUrl: 'https://avatars.mds.yandex.net/get-mpic/18787582/2a/orig', + source: 'yandex_market', + }, + ], + sources: [ + { + source: 'yandex_market', + sourceProductId: '5197387201', + url: 'https://market.yandex.ru/card/ibox-f5-wifi-videoregistrator-s-signaturnym-radar-detektorom/5197387201', + stableContentHash: 'abc123', + fetchedAt: '2026-05-16T20:00:00.000Z', + }, + ], + discoveredAt: '2026-05-16T20:00:00.000Z', + updatedAt: '2026-05-16T20:00:00.000Z', +}; + +describe('dashcam catalog export schema', () => { + it('accepts approved canonical catalog payload', () => { + expect( + DashcamCatalogExportSchema.parse({ + schemaVersion: 1, + exportId: 'export_1', + createdAt: '2026-05-16T21:00:00.000Z', + products: [baseProduct], + }), + ).toEqual({ + schemaVersion: 1, + exportId: 'export_1', + createdAt: '2026-05-16T21:00:00.000Z', + products: [baseProduct], + }); + }); + + it('rejects internal-only aggregate rating in public export', () => { + expect(() => + DashcamCatalogExportSchema.parse({ + schemaVersion: 1, + exportId: 'export_1', + createdAt: '2026-05-16T21:00:00.000Z', + products: [ + { + ...baseProduct, + aggregateRating: { + value: 4.8, + ratingCount: 17, + }, + }, + ], + }), + ).toThrow(); + }); +}); diff --git a/web-front/src/entities/dashcam-catalog/model/schema.ts b/web-front/src/entities/dashcam-catalog/model/schema.ts new file mode 100644 index 0000000..766665d --- /dev/null +++ b/web-front/src/entities/dashcam-catalog/model/schema.ts @@ -0,0 +1,77 @@ +import { z } from 'zod'; + +const isoDateTime = z.string().refine((value) => !Number.isNaN(Date.parse(value)), { + message: 'Expected an ISO-compatible date-time string', +}); + +const exportedSpecValueSchema = z.union([z.string(), z.number(), z.boolean()]); + +export const ExportedProductImageSchema = z + .object({ + url: z.string().url(), + originalUrl: z.string().url(), + source: z.literal('yandex_market'), + sha256: z.string().min(1).optional(), + width: z.number().int().positive().optional(), + height: z.number().int().positive().optional(), + }) + .strict(); + +export const ExportedProductSourceSchema = z + .object({ + source: z.literal('yandex_market'), + sourceProductId: z.string().min(1).optional(), + url: z.string().url(), + stableContentHash: z.string().min(1), + fetchedAt: isoDateTime, + }) + .strict(); + +export const ExportedDashcamProductSchema = z + .object({ + id: z.string().min(1), + brand: z.string().min(1), + model: z.string().min(1), + title: z.string().min(1), + specs: z.record(z.string().min(1), exportedSpecValueSchema), + images: z.array(ExportedProductImageSchema), + sources: z.array(ExportedProductSourceSchema).min(1), + discoveredAt: isoDateTime, + updatedAt: isoDateTime, + }) + .strict(); + +export const DashcamCatalogExportSchema = z + .object({ + schemaVersion: z.literal(1), + exportId: z.string().min(1), + createdAt: isoDateTime, + products: z.array(ExportedDashcamProductSchema), + }) + .strict(); + +export const CatalogExportManifestSchema = z + .object({ + schemaVersion: z.literal(1), + exportId: z.string().min(1), + createdAt: isoDateTime, + artifactUrl: z.string().url(), + sha256: z.string().regex(/^[a-f0-9]{64}$/i), + itemCount: z.number().int().nonnegative(), + }) + .strict(); + +export type CatalogExportManifest = z.infer; +export type DashcamCatalogExport = z.infer; +export type ExportedDashcamProduct = z.infer; +export type ExportedProductImage = z.infer; +export type ExportedProductSource = z.infer; +export type ExportedSpecValue = z.infer; + +export function parseCatalogExportManifest(input: unknown) { + return CatalogExportManifestSchema.parse(input); +} + +export function parseDashcamCatalogExport(input: unknown) { + return DashcamCatalogExportSchema.parse(input); +} diff --git a/src/entities/dashcam/model/data.ts b/web-front/src/entities/dashcam/model/data.ts similarity index 100% rename from src/entities/dashcam/model/data.ts rename to web-front/src/entities/dashcam/model/data.ts diff --git a/src/entities/dashcam/model/types.ts b/web-front/src/entities/dashcam/model/types.ts similarity index 100% rename from src/entities/dashcam/model/types.ts rename to web-front/src/entities/dashcam/model/types.ts diff --git a/src/entities/video/api/videoCache.ts b/web-front/src/entities/video/api/videoCache.ts similarity index 100% rename from src/entities/video/api/videoCache.ts rename to web-front/src/entities/video/api/videoCache.ts diff --git a/src/entities/video/api/videoRepository.ts b/web-front/src/entities/video/api/videoRepository.ts similarity index 100% rename from src/entities/video/api/videoRepository.ts rename to web-front/src/entities/video/api/videoRepository.ts diff --git a/src/entities/video/model/types.ts b/web-front/src/entities/video/model/types.ts similarity index 100% rename from src/entities/video/model/types.ts rename to web-front/src/entities/video/model/types.ts diff --git a/src/entities/video/model/video.test.ts b/web-front/src/entities/video/model/video.test.ts similarity index 100% rename from src/entities/video/model/video.test.ts rename to web-front/src/entities/video/model/video.test.ts diff --git a/src/entities/video/model/video.ts b/web-front/src/entities/video/model/video.ts similarity index 100% rename from src/entities/video/model/video.ts rename to web-front/src/entities/video/model/video.ts diff --git a/src/features/register-video/model/actions.ts b/web-front/src/features/register-video/model/actions.ts similarity index 100% rename from src/features/register-video/model/actions.ts rename to web-front/src/features/register-video/model/actions.ts diff --git a/src/features/register-video/ui/RegisterVideoForm.tsx b/web-front/src/features/register-video/ui/RegisterVideoForm.tsx similarity index 100% rename from src/features/register-video/ui/RegisterVideoForm.tsx rename to web-front/src/features/register-video/ui/RegisterVideoForm.tsx diff --git a/src/shared/config/env.ts b/web-front/src/shared/config/env.ts similarity index 100% rename from src/shared/config/env.ts rename to web-front/src/shared/config/env.ts diff --git a/src/shared/db/primitiveJsonDb.ts b/web-front/src/shared/db/primitiveJsonDb.ts similarity index 87% rename from src/shared/db/primitiveJsonDb.ts rename to web-front/src/shared/db/primitiveJsonDb.ts index 1db5662..b9bafcc 100644 --- a/src/shared/db/primitiveJsonDb.ts +++ b/web-front/src/shared/db/primitiveJsonDb.ts @@ -3,10 +3,13 @@ import 'server-only'; import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { DB_MEMORY_TTL_MS } from '@/shared/config/env'; +import { DashcamCatalogExportSchema, type DashcamCatalogExport } from '@/entities/dashcam-catalog/model/schema'; import type { Video } from '@/entities/video/model/types'; type PrimitiveDbSchema = { videos: Video[]; + dashcamCatalog?: DashcamCatalogExport; + dashcamCatalogImportedAt?: string; }; type MemorySnapshot = { @@ -64,8 +67,16 @@ async function ensureDbFile(filePath: string) { function parseSchema(raw: string): PrimitiveDbSchema { const parsed = JSON.parse(raw) as Partial; + const catalog = parsed.dashcamCatalog + ? DashcamCatalogExportSchema.safeParse(parsed.dashcamCatalog) + : undefined; + return { videos: Array.isArray(parsed.videos) ? parsed.videos : [], + ...(catalog?.success ? { dashcamCatalog: catalog.data } : {}), + ...(typeof parsed.dashcamCatalogImportedAt === 'string' + ? { dashcamCatalogImportedAt: parsed.dashcamCatalogImportedAt } + : {}), }; } diff --git a/src/shared/lib/cn.ts b/web-front/src/shared/lib/cn.ts similarity index 100% rename from src/shared/lib/cn.ts rename to web-front/src/shared/lib/cn.ts diff --git a/src/shared/lib/date.ts b/web-front/src/shared/lib/date.ts similarity index 100% rename from src/shared/lib/date.ts rename to web-front/src/shared/lib/date.ts diff --git a/src/shared/ui/atoms/Badge.tsx b/web-front/src/shared/ui/atoms/Badge.tsx similarity index 100% rename from src/shared/ui/atoms/Badge.tsx rename to web-front/src/shared/ui/atoms/Badge.tsx diff --git a/src/shared/ui/atoms/Button.tsx b/web-front/src/shared/ui/atoms/Button.tsx similarity index 100% rename from src/shared/ui/atoms/Button.tsx rename to web-front/src/shared/ui/atoms/Button.tsx diff --git a/src/shared/ui/atoms/EvidenceChip.tsx b/web-front/src/shared/ui/atoms/EvidenceChip.tsx similarity index 100% rename from src/shared/ui/atoms/EvidenceChip.tsx rename to web-front/src/shared/ui/atoms/EvidenceChip.tsx diff --git a/src/shared/ui/atoms/Icon.tsx b/web-front/src/shared/ui/atoms/Icon.tsx similarity index 100% rename from src/shared/ui/atoms/Icon.tsx rename to web-front/src/shared/ui/atoms/Icon.tsx diff --git a/src/shared/ui/atoms/ScoreMeter.tsx b/web-front/src/shared/ui/atoms/ScoreMeter.tsx similarity index 100% rename from src/shared/ui/atoms/ScoreMeter.tsx rename to web-front/src/shared/ui/atoms/ScoreMeter.tsx diff --git a/src/shared/ui/molecules/ProductCard.tsx b/web-front/src/shared/ui/molecules/ProductCard.tsx similarity index 100% rename from src/shared/ui/molecules/ProductCard.tsx rename to web-front/src/shared/ui/molecules/ProductCard.tsx diff --git a/src/shared/ui/molecules/WinnerRail.tsx b/web-front/src/shared/ui/molecules/WinnerRail.tsx similarity index 100% rename from src/shared/ui/molecules/WinnerRail.tsx rename to web-front/src/shared/ui/molecules/WinnerRail.tsx diff --git a/src/shared/ui/organisms/PageHero.tsx b/web-front/src/shared/ui/organisms/PageHero.tsx similarity index 100% rename from src/shared/ui/organisms/PageHero.tsx rename to web-front/src/shared/ui/organisms/PageHero.tsx diff --git a/src/shared/ui/organisms/ProductComparisonTable.tsx b/web-front/src/shared/ui/organisms/ProductComparisonTable.tsx similarity index 100% rename from src/shared/ui/organisms/ProductComparisonTable.tsx rename to web-front/src/shared/ui/organisms/ProductComparisonTable.tsx diff --git a/src/shared/ui/templates/BestDashcamsPage.tsx b/web-front/src/shared/ui/templates/BestDashcamsPage.tsx similarity index 100% rename from src/shared/ui/templates/BestDashcamsPage.tsx rename to web-front/src/shared/ui/templates/BestDashcamsPage.tsx diff --git a/src/widgets/cache-panel/ui/CachePanel.tsx b/web-front/src/widgets/cache-panel/ui/CachePanel.tsx similarity index 100% rename from src/widgets/cache-panel/ui/CachePanel.tsx rename to web-front/src/widgets/cache-panel/ui/CachePanel.tsx diff --git a/src/widgets/video-dashboard/ui/VideoDashboard.tsx b/web-front/src/widgets/video-dashboard/ui/VideoDashboard.tsx similarity index 100% rename from src/widgets/video-dashboard/ui/VideoDashboard.tsx rename to web-front/src/widgets/video-dashboard/ui/VideoDashboard.tsx diff --git a/web-front/tsconfig.json b/web-front/tsconfig.json new file mode 100644 index 0000000..f22f2c2 --- /dev/null +++ b/web-front/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "src/**/*.ts", + "src/**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +}