# Руководство по API для фронтенда ## Обзор изменений После рефакторинга старый `ParserController` был разделен на три специализированных контроллера для лучшей организации и масштабируемости: - **`MarketItemController`** (`/api/items`) - для получения данных - **`ParserAdminController`** (`/api/admin/parsers`) - для управления парсерами - **`HealthCheckController`** (`/api/health`) - для мониторинга системы --- ## 📊 MarketItemController - Получение данных **Базовый URL**: `/api/items` ### 1. Получение списка новостей с фильтрацией и пагинацией ```http GET /api/items ``` **Параметры запроса:** - `sourceName` (optional) - фильтр по источнику (например: "Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости") - `startDate` (optional) - начальная дата в формате ISO (например: "2024-01-01T00:00:00") - `endDate` (optional) - конечная дата в формате ISO (например: "2024-12-31T23:59:59") - `page` (optional) - номер страницы (по умолчанию: 0) - `size` (optional) - размер страницы (по умолчанию: 20) - `sort` (optional) - сортировка (по умолчанию: "publishedAt,desc") **Примеры запросов:** ```javascript // Получить все новости fetch('/api/items'); // Получить новости с пагинацией fetch('/api/items?page=0&size=10'); // Получить новости от конкретного источника fetch('/api/items?sourceName=Kursiv Media'); // Получить новости за последние 7 дней const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); fetch(`/api/items?startDate=${weekAgo}`); // Получить новости с сортировкой по дате (старые сначала) fetch('/api/items?sort=publishedAt,asc'); ``` **Ответ:** ```json { "success": true, "message": "Записи получены успешно", "data": { "content": [ { "id": "64f1a2b3c4d5e6f7g8h9i0j1", "sourceName": "Kursiv Media", "url": "https://kursiv.media/news/example", "title": "Заголовок новости", "publishedAt": "2024-01-15T10:30:00", "addedAt": "2024-01-15T10:35:00", "rawText": "Текст новости без HTML тегов", "hash": "abc123def456...", "category": "Бизнес", "analytics": { "summary": null, "sentiment": null, "tags": [], "entities": {} } } ], "pageable": { "sort": { "sorted": true, "unsorted": false }, "pageNumber": 0, "pageSize": 20, "offset": 0, "paged": true, "unpaged": false }, "totalElements": 150, "totalPages": 8, "last": false, "first": true, "numberOfElements": 20, "size": 20, "number": 0, "empty": false } } ``` ### 2. Получение новости по ID ```http GET /api/items/{id} ``` **Пример:** ```javascript fetch('/api/items/64f1a2b3c4d5e6f7g8h9i0j1'); ``` **Ответ:** ```json { "success": false, "message": "Функция поиска по ID пока не реализована" } ``` ### 3. Получение статистики ```http GET /api/items/stats ``` **Ответ:** ```json { "success": true, "message": "Статистика получена успешно", "data": { "totalItems": 1250, "sourceStatistics": { "Kursiv Media": 300, "Kapital.kz": 250, "LSM.kz": 200, "РБК": 300, "Ведомости": 200 }, "lastUpdate": "2024-01-15T10:35:00" } } ``` ### 4. Получение списка источников (sourceName) ```http GET /api/items/sources ``` **Описание:** Возвращает список всех уникальных источников новостей, которые есть в базе данных. Этот эндпоинт полезен для: - Построения фильтров по источникам - Отображения списка доступных источников в UI - Динамического создания выпадающих списков **Ответ:** ```json { "success": true, "message": "Источники получены успешно", "data": ["Kursiv (Бизнес/экономика)", "Kapital.kz (Бизнес)", "LSM.kz", "РБК", "Ведомости"] } ``` **Примеры использования:** ```javascript // Получить список всех источников const response = await fetch('/api/items/sources'); const result = await response.json(); if (result.success) { const sources = result.data; console.log('Доступные источники:', sources); // sources = ["Kursiv (Бизнес/экономика)", "Kapital.kz (Бизнес)", "LSM.kz", "РБК", "Ведомости"] } // Использование в React для создания фильтра function SourceFilter({ onSourceChange }) { const [sources, setSources] = useState([]); useEffect(() => { fetch('/api/items/sources') .then((res) => res.json()) .then((data) => { if (data.success) { setSources(data.data); } }); }, []); return ( ); } // Использование в Vue.js export default { data() { return { sources: [], selectedSource: '' }; }, async mounted() { const response = await fetch('/api/items/sources'); const result = await response.json(); if (result.success) { this.sources = result.data; } } }; ``` **Важные замечания:** - Список источников формируется на основе реальных данных в базе - Если в базе нет данных от какого-то источника, он не будет включен в список - Названия источников точно соответствуют тем, что используются в поле `sourceName` при фильтрации - Порядок источников может изменяться в зависимости от того, как MongoDB возвращает уникальные значения ### 5. Получение новостей по конкретному источнику ```http GET /api/items/source/{sourceName} ``` **Пример:** ```javascript fetch('/api/items/source/Kursiv Media?page=0&size=10'); ``` **Ответ:** Аналогичен ответу от `/api/items`, но только с новостями от указанного источника. --- ## ⚙️ ParserAdminController - Управление парсерами **Базовый URL**: `/api/admin/parsers` > ⚠️ **Важно**: Эти эндпоинты предназначены для административных функций и могут потребовать аутентификации в будущем. ### 1. Запуск конкретного парсера ```http POST /api/admin/parsers/parse/{sourceName} ``` **Доступные источники:** - `kursiv` - `kapital` - `lsm` - `rbc` - `vedomosti` **Пример:** ```javascript // Запустить парсер Kursiv fetch('/api/admin/parsers/parse/kursiv', { method: 'POST' }); // Запустить парсер РБК fetch('/api/admin/parsers/parse/rbc', { method: 'POST' }); ``` **Ответ:** ```json { "success": true, "message": "Парсинг kursiv завершен", "data": { "source": "kursiv", "processedItems": 15, "totalItemsInDb": 1265, "status": "completed" } } ``` ### 2. Запуск всех парсеров ```http POST /api/admin/parsers/parse/all ``` **Пример:** ```javascript fetch('/api/admin/parsers/parse/all', { method: 'POST' }); ``` **Ответ:** ```json { "success": true, "message": "Парсинг всех источников завершен", "data": [ { "source": "kursiv", "processedItems": 12, "totalItemsInDb": 1277, "status": "completed" }, { "source": "kapital", "processedItems": 8, "totalItemsInDb": 1285, "status": "completed" }, { "source": "lsm", "processedItems": 5, "totalItemsInDb": 1290, "status": "completed" }, { "source": "rbc", "processedItems": 20, "totalItemsInDb": 1310, "status": "completed" }, { "source": "vedomosti", "processedItems": 7, "totalItemsInDb": 1317, "status": "completed" } ] } ``` ### 3. Получение списка доступных парсеров ```http GET /api/admin/parsers ``` **Ответ:** ```json { "success": true, "message": "Список парсеров получен", "data": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"] } ``` ### 4. Получение информации о парсерах ```http GET /api/admin/parsers/info ``` **Ответ:** ```json { "success": true, "message": "Информация о парсерах получена", "data": { "totalParsers": 5, "availableParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], "totalItems": 1317, "sourceStatistics": { "Kursiv Media": 300, "Kapital.kz": 250, "LSM.kz": 200, "РБК": 300, "Ведомости": 200 } } } ``` ### 5. Проверка существования парсера ```http GET /api/admin/parsers/exists/{sourceName} ``` **Пример:** ```javascript fetch('/api/admin/parsers/exists/kursiv'); ``` **Ответ:** ```json { "success": true, "message": "Проверка завершена", "data": true } ``` --- ## 🔍 HealthCheckController - Мониторинг системы **Базовый URL**: `/api/health` ### 1. Базовая проверка состояния ```http GET /api/health ``` **Ответ:** ```json { "success": true, "message": "Сервис работает", "data": { "status": "UP", "service": "RSS Parser System", "timestamp": 1705312500000, "details": "Enabled - runs every 30 minutes" } } ``` ### 2. Проверка подключения к MongoDB ```http GET /api/health/mongodb ``` **Ответ (успех):** ```json { "success": true, "message": "MongoDB подключение успешно", "data": { "status": "UP", "service": "MongoDB подключение успешно", "timestamp": 1705312500000, "details": "Всего записей: 1317" } } ``` **Ответ (ошибка):** ```json { "success": false, "message": "Ошибка подключения к MongoDB", "data": { "status": "DOWN", "service": "Ошибка подключения к MongoDB: Connection refused", "timestamp": 1705312500000, "suggestion": "Проверьте учетные данные в application.properties" } } ``` ### 3. Информация о планировщике ```http GET /api/health/scheduler ``` **Ответ:** ```json { "success": true, "message": "Информация о планировщике получена", "data": { "schedulerEnabled": true, "cronExpression": "0 0/30 * * * ?", "description": "Запуск каждые 30 минут (в 0 и 30 минут каждого часа)", "nextRun": "Следующий запуск будет в ближайшие 0 или 30 минут часа", "activeParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], "totalParsers": 5, "sources": ["Kursiv Media", "Kapital.kz", "LSM.kz", "РБК", "Ведомости"] } } ``` ### 4. Общая информация о системе ```http GET /api/health/system ``` **Ответ:** ```json { "success": true, "message": "Информация о системе получена", "data": { "parsers": { "total": 5, "available": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"] }, "data": { "totalItems": 1317, "sourceStatistics": { "Kursiv Media": 300, "Kapital.kz": 250, "LSM.kz": 200, "РБК": 300, "Ведомости": 200 } }, "system": { "javaVersion": "17.0.2", "osName": "Linux", "osVersion": "5.4.0-74-generic", "uptime": 1705312500000 } } } ``` ### 5. Проверка состояния парсеров ```http GET /api/health/parsers ``` **Ответ:** ```json { "success": true, "message": "Проверка парсеров завершена", "data": { "totalParsers": 5, "availableParsers": ["kapital", "kursiv", "lsm", "rbc", "vedomosti"], "status": "UP", "message": "Все парсеры доступны" } } ``` --- ## 🚀 Примеры использования для фронтенда ### React/JavaScript примеры ```javascript // Сервис для работы с API class NewsApiService { constructor(baseUrl = '') { this.baseUrl = baseUrl; } // Получить новости с фильтрацией async getNews(filters = {}) { const params = new URLSearchParams(); if (filters.sourceName) params.append('sourceName', filters.sourceName); if (filters.startDate) params.append('startDate', filters.startDate); if (filters.endDate) params.append('endDate', filters.endDate); if (filters.page !== undefined) params.append('page', filters.page); if (filters.size) params.append('size', filters.size); if (filters.sort) params.append('sort', filters.sort); const response = await fetch(`${this.baseUrl}/api/items?${params}`); return response.json(); } // Получить статистику async getStats() { const response = await fetch(`${this.baseUrl}/api/items/stats`); return response.json(); } // Получить источники async getSources() { const response = await fetch(`${this.baseUrl}/api/items/sources`); return response.json(); } // Получить источники с обработкой ошибок async getSourcesWithErrorHandling() { try { const response = await fetch(`${this.baseUrl}/api/items/sources`); const result = await response.json(); if (!result.success) { throw new Error(result.message || 'Ошибка при получении источников'); } return result.data; // Возвращаем только массив источников } catch (error) { console.error('Ошибка при получении источников:', error); return []; // Возвращаем пустой массив в случае ошибки } } // Запустить парсер (админ функция) async runParser(sourceName) { const response = await fetch(`${this.baseUrl}/api/admin/parsers/parse/${sourceName}`, { method: 'POST' }); return response.json(); } // Запустить все парсеры (админ функция) async runAllParsers() { const response = await fetch(`${this.baseUrl}/api/admin/parsers/parse/all`, { method: 'POST' }); return response.json(); } // Проверить состояние системы async getHealthStatus() { const response = await fetch(`${this.baseUrl}/api/health`); return response.json(); } } // Использование const apiService = new NewsApiService(); // Получить последние новости const news = await apiService.getNews({ page: 0, size: 20, sort: 'publishedAt,desc' }); // Получить новости от конкретного источника const kursivNews = await apiService.getNews({ sourceName: 'Kursiv Media', page: 0, size: 10 }); // Получить новости за последнюю неделю const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); const recentNews = await apiService.getNews({ startDate: weekAgo, sort: 'publishedAt,desc' }); // Получить список источников для фильтра const sources = await apiService.getSourcesWithErrorHandling(); console.log('Доступные источники:', sources); // Получить новости от конкретного источника if (sources.length > 0) { const newsFromFirstSource = await apiService.getNews({ sourceName: sources[0], page: 0, size: 10 }); } ``` ### Vue.js примеры ```javascript // composable для работы с API export function useNewsApi() { const baseUrl = ''; const getNews = async (filters = {}) => { const params = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null) { params.append(key, value); } }); const response = await fetch(`${baseUrl}/api/items?${params}`); return response.json(); }; const getStats = async () => { const response = await fetch(`${baseUrl}/api/items/stats`); return response.json(); }; const getSources = async () => { const response = await fetch(`${baseUrl}/api/items/sources`); return response.json(); }; const getSourcesList = async () => { try { const response = await fetch(`${baseUrl}/api/items/sources`); const result = await response.json(); return result.success ? result.data : []; } catch (error) { console.error('Ошибка при получении источников:', error); return []; } }; return { getNews, getStats, getSources, getSourcesList }; } ``` --- ## 🎯 Практические примеры использования эндпоинта источников ### 1. Создание динамического фильтра источников ```javascript // React компонент для фильтрации по источникам import React, { useState, useEffect } from 'react'; function NewsFilter({ onFilterChange }) { const [sources, setSources] = useState([]); const [selectedSource, setSelectedSource] = useState(''); useEffect(() => { // Загружаем список источников при монтировании компонента fetch('/api/items/sources') .then((res) => res.json()) .then((data) => { if (data.success) { setSources(data.data); } }) .catch((err) => console.error('Ошибка загрузки источников:', err)); }, []); const handleSourceChange = (source) => { setSelectedSource(source); onFilterChange({ sourceName: source || undefined }); }; return (
); } export default NewsFilter; ``` ### 2. Отображение статистики по источникам ```javascript // Vue.js компонент для отображения статистики ``` ### 3. Автодополнение для поиска по источникам ```javascript // Angular сервис для автодополнения import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, BehaviorSubject } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class SourceService { private sourcesSubject = new BehaviorSubject([]); public sources$ = this.sourcesSubject.asObservable(); constructor(private http: HttpClient) { this.loadSources(); } private loadSources(): void { this.http.get('/api/items/sources') .pipe( map(response => response.success ? response.data : []), catchError(error => { console.error('Ошибка загрузки источников:', error); return []; }) ) .subscribe(sources => { this.sourcesSubject.next(sources); }); } searchSources(query: string): Observable { return this.sources$.pipe( map(sources => sources.filter(source => source.toLowerCase().includes(query.toLowerCase()) ) ) ); } getSources(): Observable { return this.sources$; } } // Компонент автодополнения @Component({ selector: 'app-source-autocomplete', template: `
{{ source }}
` }) export class SourceAutocompleteComponent { searchQuery = ''; filteredSources: string[] = []; constructor(private sourceService: SourceService) {} onSearch(query: string): void { if (query.length > 0) { this.sourceService.searchSources(query) .subscribe(sources => { this.filteredSources = sources; }); } else { this.filteredSources = []; } } selectSource(source: string): void { this.searchQuery = source; this.filteredSources = []; // Эмитим событие выбора источника } } ``` ### 4. Кэширование списка источников ```javascript // Сервис с кэшированием для React class SourceCacheService { constructor() { this.cache = null; this.cacheTime = null; this.cacheTimeout = 5 * 60 * 1000; // 5 минут } async getSources() { // Проверяем, есть ли актуальный кэш if (this.cache && this.cacheTime && Date.now() - this.cacheTime < this.cacheTimeout) { return this.cache; } try { const response = await fetch('/api/items/sources'); const result = await response.json(); if (result.success) { this.cache = result.data; this.cacheTime = Date.now(); return result.data; } else { throw new Error(result.message); } } catch (error) { console.error('Ошибка загрузки источников:', error); // Возвращаем кэш, если есть, даже если он устарел return this.cache || []; } } clearCache() { this.cache = null; this.cacheTime = null; } } // Использование в React хуке function useSources() { const [sources, setSources] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const sourceService = new SourceCacheService(); sourceService .getSources() .then((data) => { setSources(data); setLoading(false); }) .catch((err) => { setError(err.message); setLoading(false); }); }, []); return { sources, loading, error }; } ``` --- ## 📝 Важные замечания 1. **Пагинация**: Все эндпоинты для получения данных поддерживают пагинацию через параметры `page` и `size`. 2. **Сортировка**: По умолчанию новости сортируются по дате публикации (новые сначала). Можно изменить через параметр `sort`. 3. **Фильтрация**: Поддерживается фильтрация по источнику и диапазону дат. 4. **Формат дат**: Используйте ISO 8601 формат для дат (например: "2024-01-15T10:30:00"). 5. **Обработка ошибок**: Все ответы содержат поле `success` для проверки успешности операции. 6. **Административные функции**: Эндпоинты `/api/admin/parsers/*` предназначены для административных функций. 7. **Мониторинг**: Используйте эндпоинты `/api/health/*` для проверки состояния системы. --- ## 🔄 Миграция с старого API Если у вас был код, использующий старый `ParserController`, вот соответствие эндпоинтов: | Старый эндпоинт | Новый эндпоинт | | ---------------------------------- | ----------------------------------------- | | `POST /api/parser/parse/kursiv` | `POST /api/admin/parsers/parse/kursiv` | | `POST /api/parser/parse/kapital` | `POST /api/admin/parsers/parse/kapital` | | `POST /api/parser/parse/lsm` | `POST /api/admin/parsers/parse/lsm` | | `POST /api/parser/parse/rbc` | `POST /api/admin/parsers/parse/rbc` | | `POST /api/parser/parse/vedomosti` | `POST /api/admin/parsers/parse/vedomosti` | | `POST /api/parser/parse/all` | `POST /api/admin/parsers/parse/all` | | `GET /api/parser/items` | `GET /api/items` | | `GET /api/parser/stats` | `GET /api/items/stats` | | `GET /api/parser/health` | `GET /api/health` | | `GET /api/parser/health/mongodb` | `GET /api/health/mongodb` | | `GET /api/parser/scheduler/info` | `GET /api/health/scheduler` |