feat: add Postgres MCP integration for database testing
- Add postgres-mcp service to docker-compose.yml (SSE mode on port 8000) - Add .mcp.json.example with SSE configuration template - Add .gitignore entries for .claude/settings.local.json and .mcp.json - Add MCP_EXAMPLES.md with query examples for testing scraping results - Add analysis scripts: analyze-category-nulls.ts, check-product-details.ts, inspect-api-response.ts Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
97
src/scripts/analyze-category-nulls.ts
Normal file
97
src/scripts/analyze-category-nulls.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'dotenv/config';
|
||||
import { connectDatabase, disconnectDatabase, prisma } from '../config/database.js';
|
||||
import { Logger } from '../utils/logger.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await connectDatabase();
|
||||
|
||||
// Check total products and null categoryId count
|
||||
const totalProducts = await prisma.product.count();
|
||||
const nullCategoryCount = await prisma.product.count({
|
||||
where: { categoryId: null }
|
||||
});
|
||||
const withCategoryCount = await prisma.product.count({
|
||||
where: { categoryId: { not: null } }
|
||||
});
|
||||
|
||||
Logger.info('\n📊 СТАТИСТИКА ПО КАТЕГОРИЯМ:');
|
||||
Logger.info(`Всего товаров: ${totalProducts}`);
|
||||
Logger.info(`Товаров без категории (null): ${nullCategoryCount} (${((nullCategoryCount / totalProducts) * 100).toFixed(2)}%)`);
|
||||
Logger.info(`Товаров с категорией: ${withCategoryCount} (${((withCategoryCount / totalProducts) * 100).toFixed(2)}%)`);
|
||||
|
||||
// Check total categories
|
||||
const totalCategories = await prisma.category.count();
|
||||
Logger.info(`\nВсего категорий в БД: ${totalCategories}`);
|
||||
|
||||
// Sample categories
|
||||
if (totalCategories > 0) {
|
||||
const sampleCategories = await prisma.category.findMany({
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
externalId: true,
|
||||
name: true,
|
||||
_count: {
|
||||
select: { products: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Logger.info('\n📁 Примеры категорий:');
|
||||
sampleCategories.forEach(cat => {
|
||||
Logger.info(` - [${cat.externalId}] ${cat.name} (товаров: ${cat._count.products})`);
|
||||
});
|
||||
}
|
||||
|
||||
// Sample products without categories
|
||||
const productsWithoutCategory = await prisma.product.findMany({
|
||||
where: { categoryId: null },
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
externalId: true,
|
||||
name: true,
|
||||
currentPrice: true
|
||||
}
|
||||
});
|
||||
|
||||
Logger.info('\n❌ Примеры товаров БЕЗ категории:');
|
||||
productsWithoutCategory.forEach(p => {
|
||||
Logger.info(` - [${p.externalId}] ${p.name} (₽${p.currentPrice})`);
|
||||
});
|
||||
|
||||
// Sample products with categories
|
||||
const productsWithCategory = await prisma.product.findMany({
|
||||
where: { categoryId: { not: null } },
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
externalId: true,
|
||||
name: true,
|
||||
currentPrice: true,
|
||||
category: {
|
||||
select: {
|
||||
externalId: true,
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (productsWithCategory.length > 0) {
|
||||
Logger.info('\n✅ Примеры товаров С категорией:');
|
||||
productsWithCategory.forEach(p => {
|
||||
Logger.info(` - [${p.externalId}] ${p.name} → [${p.category?.externalId}] ${p.category?.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
Logger.error('❌ Ошибка при анализе:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await disconnectDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
38
src/scripts/check-product-details.ts
Normal file
38
src/scripts/check-product-details.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dotenv/config';
|
||||
import { connectDatabase, disconnectDatabase, prisma } from '../config/database.js';
|
||||
import { Logger } from '../utils/logger.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await connectDatabase();
|
||||
|
||||
// Get a sample product with all fields
|
||||
const product = await prisma.product.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
externalId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
currentPrice: true,
|
||||
unit: true,
|
||||
weight: true,
|
||||
brand: true,
|
||||
categoryId: true,
|
||||
badges: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (product) {
|
||||
Logger.info('=== ДЕТАЛИ ТОВАРА ИЗ БД ===');
|
||||
Logger.info(JSON.stringify(product, null, 2));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
Logger.error('❌ Ошибка:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await disconnectDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
67
src/scripts/inspect-api-response.ts
Normal file
67
src/scripts/inspect-api-response.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'dotenv/config';
|
||||
import { MagnitApiScraper } from '../scrapers/api/magnit/MagnitApiScraper.js';
|
||||
import { Logger } from '../utils/logger.js';
|
||||
|
||||
async function main() {
|
||||
const storeCode = process.env.MAGNIT_STORE_CODE || '992301';
|
||||
|
||||
const scraper = new MagnitApiScraper({
|
||||
storeCode,
|
||||
storeType: process.env.MAGNIT_STORE_TYPE || '6',
|
||||
catalogType: process.env.MAGNIT_CATALOG_TYPE || '1',
|
||||
headless: process.env.MAGNIT_HEADLESS !== 'false',
|
||||
});
|
||||
|
||||
try {
|
||||
await scraper.initialize();
|
||||
|
||||
Logger.info('Запрос первых 5 товаров для инспекции...\n');
|
||||
|
||||
const response = await scraper.searchGoods({ limit: 5, offset: 0 }, []);
|
||||
|
||||
Logger.info(`Получено товаров: ${response.items.length}\n`);
|
||||
|
||||
if (response.items.length > 0) {
|
||||
Logger.info('=== СТРУКТУРА ПЕРВОГО ТОВАРА ===');
|
||||
const firstProduct = response.items[0];
|
||||
Logger.info(JSON.stringify(firstProduct, null, 2));
|
||||
|
||||
Logger.info('\n=== ПРОВЕРКА НАЛИЧИЯ КАТЕГОРИЙ ===');
|
||||
response.items.forEach((item, index) => {
|
||||
Logger.info(
|
||||
`${index + 1}. [${item.id}] ${item.name.substring(0, 50)}...`
|
||||
);
|
||||
if (item.category) {
|
||||
Logger.info(` ✅ Категория: [${item.category.id}] ${item.category.title}`);
|
||||
} else {
|
||||
Logger.info(` ❌ Категория отсутствует (undefined)`);
|
||||
}
|
||||
});
|
||||
|
||||
Logger.info('\n=== ОТВЕТ API (response.category) ===');
|
||||
if (response.category) {
|
||||
Logger.info(`Категория уровня ответа: [${response.category.id}] ${response.category.title}`);
|
||||
} else {
|
||||
Logger.info('Категория уровня ответа отсутствует');
|
||||
}
|
||||
|
||||
Logger.info('\n=== БЫСТРЫЕ КАТЕГОРИИ (fastCategoriesExtended) ===');
|
||||
if (response.fastCategoriesExtended && response.fastCategoriesExtended.length > 0) {
|
||||
Logger.info(`Найдено ${response.fastCategoriesExtended.length} быстрых категорий:`);
|
||||
response.fastCategoriesExtended.slice(0, 10).forEach(cat => {
|
||||
Logger.info(` - [${cat.id}] ${cat.title}`);
|
||||
});
|
||||
} else {
|
||||
Logger.info('Быстрые категории отсутствуют');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
Logger.error('❌ Ошибка:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await scraper.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user