Серверный каталог компаний с поиском по названию, фильтром по городу и пагинацией. Данные поступают в PostgreSQL через проверяемый импорт JSON/CSV: записи валидируются, дубли не создаются, а проблемные CSV-строки сохраняются отдельно для разбора.
Проект реализован в рамках тестового задания Polza Agency.
- Каталог
/companies: поиск без учёта регистра, фильтр города и серверная пагинация по 50 компаний; параметры сохраняются в URL. - Проверка структуры JSON-страниц и значений полей; дедупликация по идентификатору источника.
- Повторяемый импорт: повторная загрузка одинаковых записей не создаёт новые компании.
- Обнаружение конфликтов: JSON-импорт откатывается целиком; конфликтующие CSV-строки изолируются без перезаписи существующих компаний.
- Staging/quarantine для CSV: исходные строки, ошибки и причины отклонения сохраняются в отдельной таблице, в каталог попадают только принятые записи.
- Unit-тесты и интеграционные тесты с настоящим PostgreSQL, включая проверку полного отката при сбое.
TypeScript, Node.js, Next.js App Router, React, PostgreSQL 17 и Docker Compose. Для доступа к БД используется pg, для валидации — Zod, для CSV — csv-parse, для проверок — Vitest и Biome.
- Ключ дедупликации —
source_id. Это ID компании в выгрузке и первичный ключ в БД. Название, сайт и телефон не используются как уникальные ключи: разные компании могут разделять эти значения. - Конфликт не означает обновление. Если у одного ID различаются данные, JSON-загрузка завершается ошибкой и откатом транзакции. В CSV такая строка получает статус
quarantined, а остальные корректные записи могут быть приняты. Ошибка записи в БД откатывает весь импорт. - Проблемные данные остаются доступными.
review_recordsхранит промежуточный срез последнего CSV-импорта (staging), включая отклонённые записи (quarantine), исходные значения и причины классификации. Это не история всех загрузок. - База доступна только серверному коду. Каталог читает данные через Server Component и параметризованный SQL: пользовательский ввод передаётся отдельно от текста запроса, а строка подключения не отправляется в браузер. B-tree индексы поддерживают сортировку и фильтр города, GIN trigram индекс — поиск по части названия.
Подробности и ограничения — в техническом отчёте.
Нужны Node.js >=20.12.0, npm >=10 и Docker с запущенным Linux daemon. Исходные JSON-страницы и CSV уже включены в data/raw.
Для нового клона и чистого Docker volume, в PowerShell:
git clone https://github.com/brodev3/company-data-explorer.git
cd company-data-explorer
npm ci
Copy-Item .env.example .env
docker compose up -d --wait
npm run import:pages -- --input ./data/raw
npm run import:review -- --input ./data/raw/review.csv
npm run devОткройте http://localhost:3000/companies.
В Bash замените только Copy-Item .env.example .env на cp .env.example .env; остальные команды и пути подходят обеим оболочкам. Уже существующий .env не перезаписывайте. При изменении настроек PostgreSQL согласуйте POSTGRES_*, DATABASE_URL и TEST_DATABASE_URL по образцу .env.example; значения примера предназначены только для локальной разработки.
Порядок загрузки: JSON → CSV. CSV-импорт сверяется с уже загруженными компаниями. На чистом volume Compose автоматически применяет полную schema.sql.
При повторном запуске Compose не применяет схему заново. До импорта приведите существующую базу к текущей схеме:
- Если
review_recordsимеет старую схему сparse_status, сначала выполнитеnpm run db:migrate:review. Старые записи сохранятся в quarantine до нового CSV-импорта. - Примените
schema.sql, чтобы создать отсутствующие таблицы и индексы каталога:
docker compose exec -T db psql -v ON_ERROR_STOP=1 -U company_app -d company_data -f /docker-entrypoint-initdb.d/001-schema.sqlКоманда использует пользователя и БД из .env.example; при своих значениях замените -U и -d. Если review_records ещё нет, шаг с миграцией пропустите: таблицу создаст схема. Если схема уже актуальна, повторное применение допустимо. Детали обновления — в техническом отчёте.
Из корня репозитория:
npm run format:check
npm run lint
npm run typecheck
npm test
npm run test:integration
npm run buildnpm test запускает unit-тесты без БД; npm run test:integration — сценарии с PostgreSQL. Оба набора можно запустить последовательно через npm run test:all.
Тесты проверяют валидацию и дедупликацию, повторный CSV-импорт, конфликты без перезаписи компаний, реальный rollback JSON/CSV после частичной записи, поиск с буквальными % и _, совместный фильтр и устойчивую пагинацию. Отдельно проверяются некорректные входные параметры и безопасные внешние ссылки. Ручные проверки интерфейса и найденные исправления описаны в разделе «Как проверял».
Условия интеграционных тестов: запущенный PostgreSQL, локальный TEST_DATABASE_URL в .env, доступ к служебной БД postgres, права CREATE DATABASE и установки pg_trgm. Разрешены только loopback-адреса (localhost, 127.0.0.1, ::1). Тесты создают и удаляют отдельные временные БД cde_test_*, не используя рабочую БД каталога. Локальный Compose подходит; удалённый Supabase этим тестовым helper не поддерживается.
Эти команды не нужны для обычного запуска. inspect:* проверяют файлы без записи в БД; db:check проверяет соединение и наличие обязательных объектов схемы.
npm run inspect:pages -- --input ./data/raw
npm run inspect:review -- --input ./data/raw/review.csv
npm run db:check
npm run db:check-conflictdb:check-conflict выполняется после импорта JSON: делает пробную запись, вызывает конфликт и проверяет откат транзакции без изменения исходных компаний.
- Технический отчёт — разбор трёх задач, результаты импортов, устройство каталога, ручные проверки и ограничения.
- ANOMALIES.md — странности CSV, конкретные строки и способ обнаружения.
- queries.sql — топ-5 категорий, средний рейтинг по городам при 10+ отзывах и доля компаний с сайтом; как выполнить запросы.
- schema.sql — таблицы, ограничения и индексы PostgreSQL.
