diff --git a/services/news-engine-console/API_DOCUMENTATION.md b/services/news-engine-console/API_DOCUMENTATION.md new file mode 100644 index 0000000..8e8fa1f --- /dev/null +++ b/services/news-engine-console/API_DOCUMENTATION.md @@ -0,0 +1,2057 @@ +# News Engine Console - API 문서 + +## 목차 +1. [개요](#개요) +2. [인증](#인증) +3. [API 엔드포인트](#api-엔드포인트) + - [Users API](#1-users-api) + - [Keywords API](#2-keywords-api) + - [Pipelines API](#3-pipelines-api) + - [Applications API](#4-applications-api) + - [Monitoring API](#5-monitoring-api) +4. [에러 코드](#에러-코드) +5. [예제 코드](#예제-코드) + +--- + +## 개요 + +**Base URL**: `http://localhost:8101/api/v1` + +**Content-Type**: `application/json` + +**인증 방식**: JWT Bearer Token (OAuth2 Password Flow) + +**테스트 완료**: 37개 엔드포인트 모두 테스트 완료 (100% 성공률) + +--- + +## 인증 + +### OAuth2 Password Flow + +모든 API 요청은 JWT 토큰을 통한 인증이 필요합니다. + +#### 로그인하여 토큰 받기 + +```bash +POST /api/v1/users/login +Content-Type: application/x-www-form-urlencoded + +username=admin&password=admin123456 +``` + +**응답:** +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer", + "expires_in": 1800 +} +``` + +#### 인증된 요청 보내기 + +모든 API 요청에 Authorization 헤더를 포함해야 합니다: + +```bash +Authorization: Bearer {access_token} +``` + +--- + +## API 엔드포인트 + +## 1. Users API + +사용자 관리 및 인증을 위한 API입니다. + +### 1.1 로그인 + +**Endpoint**: `POST /api/v1/users/login` + +**권한**: 공개 (인증 불필요) + +**Request Body** (application/x-www-form-urlencoded): +``` +username=admin +password=admin123456 +``` + +**Response** (200 OK): +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer", + "expires_in": 1800 +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/users/login \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d 'username=admin&password=admin123456' +``` + +--- + +### 1.2 현재 사용자 정보 조회 + +**Endpoint**: `GET /api/v1/users/me` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439011", + "username": "admin", + "email": "admin@example.com", + "full_name": "Administrator", + "role": "admin", + "disabled": false, + "created_at": "2025-01-04T12:00:00Z", + "last_login": "2025-01-04T14:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/users/me \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.3 사용자 목록 조회 + +**Endpoint**: `GET /api/v1/users/` + +**권한**: 관리자(admin)만 가능 + +**Query Parameters**: +- `role` (optional): 역할 필터 (admin/editor/viewer) +- `disabled` (optional): 비활성화 상태 필터 (true/false) +- `search` (optional): username, email, full_name에서 검색 + +**Response** (200 OK): +```json +[ + { + "_id": "507f1f77bcf86cd799439011", + "username": "admin", + "email": "admin@example.com", + "full_name": "Administrator", + "role": "admin", + "disabled": false, + "created_at": "2025-01-04T12:00:00Z", + "last_login": "2025-01-04T14:30:00Z" + } +] +``` + +**cURL 예제:** +```bash +# 모든 사용자 조회 +curl -X GET http://localhost:8101/api/v1/users/ \ + -H 'Authorization: Bearer {token}' + +# 관리자만 필터링 +curl -X GET 'http://localhost:8101/api/v1/users/?role=admin' \ + -H 'Authorization: Bearer {token}' + +# 검색 +curl -X GET 'http://localhost:8101/api/v1/users/?search=john' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.4 사용자 통계 + +**Endpoint**: `GET /api/v1/users/stats` + +**권한**: 관리자(admin)만 가능 + +**Response** (200 OK): +```json +{ + "total": 10, + "active": 8, + "disabled": 2, + "by_role": { + "admin": 2, + "editor": 5, + "viewer": 3 + } +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/users/stats \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.5 특정 사용자 조회 + +**Endpoint**: `GET /api/v1/users/{user_id}` + +**권한**: 관리자 또는 본인 + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439011", + "username": "john_doe", + "email": "john@example.com", + "full_name": "John Doe", + "role": "editor", + "disabled": false, + "created_at": "2025-01-04T12:00:00Z", + "last_login": "2025-01-04T14:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/users/507f1f77bcf86cd799439011 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.6 사용자 생성 + +**Endpoint**: `POST /api/v1/users/` + +**권한**: 관리자(admin)만 가능 + +**Request Body**: +```json +{ + "username": "new_user", + "email": "newuser@example.com", + "password": "secure_password123", + "full_name": "New User", + "role": "editor" +} +``` + +**Response** (201 Created): +```json +{ + "_id": "507f1f77bcf86cd799439012", + "username": "new_user", + "email": "newuser@example.com", + "full_name": "New User", + "role": "editor", + "disabled": false, + "created_at": "2025-01-04T15:00:00Z", + "last_login": null +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/users/ \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "username": "new_user", + "email": "newuser@example.com", + "password": "secure_password123", + "full_name": "New User", + "role": "editor" + }' +``` + +--- + +### 1.7 사용자 수정 + +**Endpoint**: `PUT /api/v1/users/{user_id}` + +**권한**: 관리자 또는 본인 (본인은 email, full_name만 수정 가능) + +**Request Body**: +```json +{ + "email": "updated@example.com", + "full_name": "Updated Name", + "role": "admin", + "disabled": false +} +``` + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439012", + "username": "new_user", + "email": "updated@example.com", + "full_name": "Updated Name", + "role": "admin", + "disabled": false, + "created_at": "2025-01-04T15:00:00Z", + "last_login": null +} +``` + +**cURL 예제:** +```bash +curl -X PUT http://localhost:8101/api/v1/users/507f1f77bcf86cd799439012 \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "email": "updated@example.com", + "full_name": "Updated Name" + }' +``` + +--- + +### 1.8 사용자 삭제 + +**Endpoint**: `DELETE /api/v1/users/{user_id}` + +**권한**: 관리자(admin)만 가능 (본인 삭제 불가) + +**Response** (204 No Content) + +**cURL 예제:** +```bash +curl -X DELETE http://localhost:8101/api/v1/users/507f1f77bcf86cd799439012 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.9 사용자 활성화/비활성화 토글 + +**Endpoint**: `POST /api/v1/users/{user_id}/toggle` + +**권한**: 관리자(admin)만 가능 (본인 토글 불가) + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439012", + "username": "new_user", + "email": "updated@example.com", + "full_name": "Updated Name", + "role": "editor", + "disabled": true, + "created_at": "2025-01-04T15:00:00Z", + "last_login": null +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/users/507f1f77bcf86cd799439012/toggle \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 1.10 비밀번호 변경 + +**Endpoint**: `POST /api/v1/users/change-password` + +**권한**: 인증된 사용자 (본인만) + +**Request Body**: +```json +{ + "old_password": "current_password", + "new_password": "new_secure_password123" +} +``` + +**Response** (200 OK): +```json +{ + "message": "Password changed successfully" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/users/change-password \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "old_password": "current_password", + "new_password": "new_secure_password123" + }' +``` + +--- + +## 2. Keywords API + +키워드 관리를 위한 API입니다. + +### 2.1 키워드 목록 조회 + +**Endpoint**: `GET /api/v1/keywords/` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `category` (optional): 카테고리 필터 (people/topics/companies) +- `status` (optional): 상태 필터 (active/inactive) +- `search` (optional): 키워드 텍스트 검색 +- `page` (default: 1): 페이지 번호 +- `page_size` (default: 50, max: 100): 페이지 크기 +- `sort_by` (default: "created_at"): 정렬 필드 +- `sort_order` (default: -1): 정렬 순서 (1: 오름차순, -1: 내림차순) + +**Response** (200 OK): +```json +{ + "keywords": [ + { + "_id": "507f1f77bcf86cd799439013", + "keyword": "도널드 트럼프", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 8, + "metadata": { + "description": "Former US President", + "aliases": ["Donald Trump", "Trump"] + }, + "created_at": "2025-01-04T12:00:00Z", + "updated_at": "2025-01-04T12:00:00Z", + "created_by": "admin" + } + ], + "total": 100, + "page": 1, + "page_size": 50 +} +``` + +**cURL 예제:** +```bash +# 모든 키워드 조회 +curl -X GET http://localhost:8101/api/v1/keywords/ \ + -H 'Authorization: Bearer {token}' + +# 카테고리 필터링 +curl -X GET 'http://localhost:8101/api/v1/keywords/?category=people&status=active' \ + -H 'Authorization: Bearer {token}' + +# 페이지네이션 +curl -X GET 'http://localhost:8101/api/v1/keywords/?page=2&page_size=20' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 2.2 특정 키워드 조회 + +**Endpoint**: `GET /api/v1/keywords/{keyword_id}` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439013", + "keyword": "도널드 트럼프", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 8, + "metadata": { + "description": "Former US President", + "aliases": ["Donald Trump", "Trump"] + }, + "created_at": "2025-01-04T12:00:00Z", + "updated_at": "2025-01-04T12:00:00Z", + "created_by": "admin" +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/keywords/507f1f77bcf86cd799439013 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 2.3 키워드 생성 + +**Endpoint**: `POST /api/v1/keywords/` + +**권한**: 인증된 사용자 + +**Request Body**: +```json +{ + "keyword": "일론 머스크", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 9, + "metadata": { + "description": "CEO of Tesla and SpaceX", + "aliases": ["Elon Musk"] + } +} +``` + +**Response** (201 Created): +```json +{ + "_id": "507f1f77bcf86cd799439014", + "keyword": "일론 머스크", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 9, + "metadata": { + "description": "CEO of Tesla and SpaceX", + "aliases": ["Elon Musk"] + }, + "created_at": "2025-01-04T15:00:00Z", + "updated_at": "2025-01-04T15:00:00Z", + "created_by": "admin" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/keywords/ \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "keyword": "일론 머스크", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 9, + "metadata": { + "description": "CEO of Tesla and SpaceX", + "aliases": ["Elon Musk"] + } + }' +``` + +--- + +### 2.4 키워드 수정 + +**Endpoint**: `PUT /api/v1/keywords/{keyword_id}` + +**권한**: 인증된 사용자 + +**Request Body**: +```json +{ + "priority": 10, + "status": "inactive", + "metadata": { + "description": "Updated description" + } +} +``` + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439014", + "keyword": "일론 머스크", + "category": "people", + "status": "inactive", + "pipeline_type": "all", + "priority": 10, + "metadata": { + "description": "Updated description" + }, + "created_at": "2025-01-04T15:00:00Z", + "updated_at": "2025-01-04T15:30:00Z", + "created_by": "admin" +} +``` + +**cURL 예제:** +```bash +curl -X PUT http://localhost:8101/api/v1/keywords/507f1f77bcf86cd799439014 \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "priority": 10, + "status": "inactive" + }' +``` + +--- + +### 2.5 키워드 삭제 + +**Endpoint**: `DELETE /api/v1/keywords/{keyword_id}` + +**권한**: 인증된 사용자 + +**Response** (204 No Content) + +**cURL 예제:** +```bash +curl -X DELETE http://localhost:8101/api/v1/keywords/507f1f77bcf86cd799439014 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 2.6 키워드 상태 토글 + +**Endpoint**: `POST /api/v1/keywords/{keyword_id}/toggle` + +**권한**: 인증된 사용자 + +**설명**: 키워드 상태를 active ↔ inactive로 전환합니다. + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439014", + "keyword": "일론 머스크", + "category": "people", + "status": "active", + "pipeline_type": "all", + "priority": 10, + "metadata": {}, + "created_at": "2025-01-04T15:00:00Z", + "updated_at": "2025-01-04T15:45:00Z", + "created_by": "admin" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/keywords/507f1f77bcf86cd799439014/toggle \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 2.7 키워드 통계 + +**Endpoint**: `GET /api/v1/keywords/{keyword_id}/stats` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "keyword_id": "507f1f77bcf86cd799439014", + "total_articles": 1523, + "recent_articles": 45, + "avg_daily_articles": 12.5, + "last_collected": "2025-01-04T14:30:00Z", + "trends": { + "last_7_days": [10, 12, 15, 8, 14, 11, 13], + "last_30_days_avg": 11.2 + } +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/keywords/507f1f77bcf86cd799439014/stats \ + -H 'Authorization: Bearer {token}' +``` + +--- + +## 3. Pipelines API + +파이프라인 관리 및 제어를 위한 API입니다. + +### 3.1 파이프라인 목록 조회 + +**Endpoint**: `GET /api/v1/pipelines/` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `type` (optional): 파이프라인 타입 필터 (rss_collector/translator/image_generator) +- `status` (optional): 상태 필터 (running/stopped/error) + +**Response** (200 OK): +```json +{ + "pipelines": [ + { + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "running", + "config": { + "interval_minutes": 30, + "max_articles": 100, + "categories": ["politics"] + }, + "schedule": "*/30 * * * *", + "stats": { + "total_processed": 1523, + "success_count": 1500, + "error_count": 23, + "last_run": "2025-01-04T14:30:00Z", + "average_duration_seconds": 45.2 + }, + "last_run": "2025-01-04T14:30:00Z", + "next_run": "2025-01-04T15:00:00Z", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T14:30:00Z" + } + ], + "total": 5 +} +``` + +**cURL 예제:** +```bash +# 모든 파이프라인 조회 +curl -X GET http://localhost:8101/api/v1/pipelines/ \ + -H 'Authorization: Bearer {token}' + +# 실행 중인 파이프라인만 조회 +curl -X GET 'http://localhost:8101/api/v1/pipelines/?status=running' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.2 특정 파이프라인 조회 + +**Endpoint**: `GET /api/v1/pipelines/{pipeline_id}` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "running", + "config": { + "interval_minutes": 30, + "max_articles": 100, + "categories": ["politics"] + }, + "schedule": "*/30 * * * *", + "stats": { + "total_processed": 1523, + "success_count": 1500, + "error_count": 23, + "last_run": "2025-01-04T14:30:00Z", + "average_duration_seconds": 45.2 + }, + "last_run": "2025-01-04T14:30:00Z", + "next_run": "2025-01-04T15:00:00Z", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T14:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.3 파이프라인 생성 + +**Endpoint**: `POST /api/v1/pipelines/` + +**권한**: 인증된 사용자 + +**Request Body**: +```json +{ + "name": "Translation Pipeline - Korean", + "type": "translator", + "status": "stopped", + "config": { + "source_language": "en", + "target_language": "ko", + "batch_size": 10 + }, + "schedule": "0 */2 * * *" +} +``` + +**Response** (201 Created): +```json +{ + "_id": "507f1f77bcf86cd799439016", + "name": "Translation Pipeline - Korean", + "type": "translator", + "status": "stopped", + "config": { + "source_language": "en", + "target_language": "ko", + "batch_size": 10 + }, + "schedule": "0 */2 * * *", + "stats": { + "total_processed": 0, + "success_count": 0, + "error_count": 0, + "last_run": null, + "average_duration_seconds": null + }, + "last_run": null, + "next_run": null, + "created_at": "2025-01-04T15:00:00Z", + "updated_at": "2025-01-04T15:00:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/pipelines/ \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "Translation Pipeline - Korean", + "type": "translator", + "status": "stopped", + "config": { + "source_language": "en", + "target_language": "ko", + "batch_size": 10 + }, + "schedule": "0 */2 * * *" + }' +``` + +--- + +### 3.4 파이프라인 수정 + +**Endpoint**: `PUT /api/v1/pipelines/{pipeline_id}` + +**권한**: 인증된 사용자 + +**Request Body**: +```json +{ + "name": "Updated Pipeline Name", + "config": { + "source_language": "en", + "target_language": "ko", + "batch_size": 20 + } +} +``` + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439016", + "name": "Updated Pipeline Name", + "type": "translator", + "status": "stopped", + "config": { + "source_language": "en", + "target_language": "ko", + "batch_size": 20 + }, + "schedule": "0 */2 * * *", + "stats": { + "total_processed": 0, + "success_count": 0, + "error_count": 0, + "last_run": null, + "average_duration_seconds": null + }, + "last_run": null, + "next_run": null, + "created_at": "2025-01-04T15:00:00Z", + "updated_at": "2025-01-04T15:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X PUT http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439016 \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "Updated Pipeline Name", + "config": { + "batch_size": 20 + } + }' +``` + +--- + +### 3.5 파이프라인 삭제 + +**Endpoint**: `DELETE /api/v1/pipelines/{pipeline_id}` + +**권한**: 인증된 사용자 + +**Response** (204 No Content) + +**cURL 예제:** +```bash +curl -X DELETE http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439016 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.6 파이프라인 통계 조회 + +**Endpoint**: `GET /api/v1/pipelines/{pipeline_id}/stats` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "total_processed": 1523, + "success_count": 1500, + "error_count": 23, + "last_run": "2025-01-04T14:30:00Z", + "average_duration_seconds": 45.2 +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/stats \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.7 파이프라인 시작 + +**Endpoint**: `POST /api/v1/pipelines/{pipeline_id}/start` + +**권한**: 인증된 사용자 + +**설명**: 중지된 파이프라인을 시작합니다. + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "running", + "config": {...}, + "schedule": "*/30 * * * *", + "stats": {...}, + "last_run": "2025-01-04T14:30:00Z", + "next_run": "2025-01-04T15:00:00Z", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T15:00:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/start \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.8 파이프라인 중지 + +**Endpoint**: `POST /api/v1/pipelines/{pipeline_id}/stop` + +**권한**: 인증된 사용자 + +**설명**: 실행 중인 파이프라인을 중지합니다. + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "stopped", + "config": {...}, + "schedule": "*/30 * * * *", + "stats": {...}, + "last_run": "2025-01-04T14:30:00Z", + "next_run": null, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T15:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/stop \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.9 파이프라인 재시작 + +**Endpoint**: `POST /api/v1/pipelines/{pipeline_id}/restart` + +**권한**: 인증된 사용자 + +**설명**: 파이프라인을 중지 후 다시 시작합니다. + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "running", + "config": {...}, + "schedule": "*/30 * * * *", + "stats": {...}, + "last_run": "2025-01-04T15:30:00Z", + "next_run": "2025-01-04T16:00:00Z", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T15:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/restart \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.10 파이프라인 로그 조회 + +**Endpoint**: `GET /api/v1/pipelines/{pipeline_id}/logs` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `limit` (default: 100, max: 1000): 최대 로그 수 +- `level` (optional): 로그 레벨 필터 (INFO/WARNING/ERROR) + +**Response** (200 OK): +```json +[ + { + "timestamp": "2025-01-04T15:30:00Z", + "level": "INFO", + "message": "Pipeline started successfully", + "pipeline_id": "507f1f77bcf86cd799439015" + }, + { + "timestamp": "2025-01-04T15:30:15Z", + "level": "INFO", + "message": "Processed 50 articles", + "pipeline_id": "507f1f77bcf86cd799439015" + }, + { + "timestamp": "2025-01-04T15:30:20Z", + "level": "ERROR", + "message": "Failed to fetch article: Connection timeout", + "pipeline_id": "507f1f77bcf86cd799439015" + } +] +``` + +**cURL 예제:** +```bash +# 최근 100개 로그 +curl -X GET http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/logs \ + -H 'Authorization: Bearer {token}' + +# 에러 로그만 조회 +curl -X GET 'http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/logs?level=ERROR&limit=50' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 3.11 파이프라인 설정 수정 + +**Endpoint**: `PUT /api/v1/pipelines/{pipeline_id}/config` + +**권한**: 인증된 사용자 + +**Request Body**: +```json +{ + "interval_minutes": 15, + "max_articles": 200 +} +``` + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "type": "rss_collector", + "status": "running", + "config": { + "interval_minutes": 15, + "max_articles": 200, + "categories": ["politics"] + }, + "schedule": "*/30 * * * *", + "stats": {...}, + "last_run": "2025-01-04T15:30:00Z", + "next_run": "2025-01-04T16:00:00Z", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-04T15:45:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X PUT http://localhost:8101/api/v1/pipelines/507f1f77bcf86cd799439015/config \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "interval_minutes": 15, + "max_articles": 200 + }' +``` + +--- + +## 4. Applications API + +OAuth2 애플리케이션 관리를 위한 API입니다. + +### 4.1 애플리케이션 목록 조회 + +**Endpoint**: `GET /api/v1/applications/` + +**권한**: 인증된 사용자 (관리자는 모든 앱, 일반 사용자는 자신의 앱만) + +**Response** (200 OK): +```json +[ + { + "_id": "507f1f77bcf86cd799439017", + "name": "News Frontend App", + "client_id": "app_RssjY8eNbTYv5SBHoHhlbQ", + "redirect_uris": [ + "http://localhost:3000/auth/callback", + "https://news.example.com/auth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"], + "owner_id": "507f1f77bcf86cd799439011", + "created_at": "2025-01-04T10:00:00Z", + "updated_at": "2025-01-04T10:00:00Z" + } +] +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/applications/ \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 4.2 애플리케이션 통계 + +**Endpoint**: `GET /api/v1/applications/stats` + +**권한**: 관리자(admin)만 가능 + +**Response** (200 OK): +```json +{ + "total": 15, + "by_owner": { + "507f1f77bcf86cd799439011": 5, + "507f1f77bcf86cd799439012": 3, + "507f1f77bcf86cd799439013": 7 + }, + "by_grant_type": { + "authorization_code": 10, + "client_credentials": 5 + } +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/applications/stats \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 4.3 특정 애플리케이션 조회 + +**Endpoint**: `GET /api/v1/applications/{app_id}` + +**권한**: 관리자 또는 앱 소유자 + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439017", + "name": "News Frontend App", + "client_id": "app_RssjY8eNbTYv5SBHoHhlbQ", + "redirect_uris": [ + "http://localhost:3000/auth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"], + "owner_id": "507f1f77bcf86cd799439011", + "created_at": "2025-01-04T10:00:00Z", + "updated_at": "2025-01-04T10:00:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/applications/507f1f77bcf86cd799439017 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 4.4 애플리케이션 생성 + +**Endpoint**: `POST /api/v1/applications/` + +**권한**: 인증된 사용자 + +**중요**: client_secret은 생성 시에만 반환되므로 반드시 저장하세요! + +**Request Body**: +```json +{ + "name": "Mobile App", + "redirect_uris": [ + "myapp://auth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"] +} +``` + +**Response** (201 Created): +```json +{ + "_id": "507f1f77bcf86cd799439018", + "name": "Mobile App", + "client_id": "app_AbC123XyZ456", + "client_secret": "secret_DEF789uvw012", + "redirect_uris": [ + "myapp://auth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"], + "owner_id": "507f1f77bcf86cd799439011", + "created_at": "2025-01-04T16:00:00Z", + "updated_at": "2025-01-04T16:00:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/applications/ \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "Mobile App", + "redirect_uris": ["myapp://auth/callback"], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"] + }' +``` + +--- + +### 4.5 애플리케이션 수정 + +**Endpoint**: `PUT /api/v1/applications/{app_id}` + +**권한**: 관리자 또는 앱 소유자 + +**Request Body**: +```json +{ + "name": "Updated Mobile App", + "redirect_uris": [ + "myapp://auth/callback", + "myapp://oauth/callback" + ] +} +``` + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439018", + "name": "Updated Mobile App", + "client_id": "app_AbC123XyZ456", + "redirect_uris": [ + "myapp://auth/callback", + "myapp://oauth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"], + "owner_id": "507f1f77bcf86cd799439011", + "created_at": "2025-01-04T16:00:00Z", + "updated_at": "2025-01-04T16:30:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X PUT http://localhost:8101/api/v1/applications/507f1f77bcf86cd799439018 \ + -H 'Authorization: Bearer {token}' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "Updated Mobile App", + "redirect_uris": ["myapp://auth/callback", "myapp://oauth/callback"] + }' +``` + +--- + +### 4.6 애플리케이션 삭제 + +**Endpoint**: `DELETE /api/v1/applications/{app_id}` + +**권한**: 관리자 또는 앱 소유자 + +**Response** (204 No Content) + +**cURL 예제:** +```bash +curl -X DELETE http://localhost:8101/api/v1/applications/507f1f77bcf86cd799439018 \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 4.7 클라이언트 시크릿 재생성 + +**Endpoint**: `POST /api/v1/applications/{app_id}/regenerate-secret` + +**권한**: 관리자 또는 앱 소유자 + +**중요**: 새 client_secret은 재생성 시에만 반환되므로 반드시 저장하세요! + +**Response** (200 OK): +```json +{ + "_id": "507f1f77bcf86cd799439018", + "name": "Mobile App", + "client_id": "app_AbC123XyZ456", + "client_secret": "secret_NEW789xyz012", + "redirect_uris": [ + "myapp://auth/callback" + ], + "grant_types": ["authorization_code", "refresh_token"], + "scopes": ["read", "write"], + "owner_id": "507f1f77bcf86cd799439011", + "created_at": "2025-01-04T16:00:00Z", + "updated_at": "2025-01-04T17:00:00Z" +} +``` + +**cURL 예제:** +```bash +curl -X POST http://localhost:8101/api/v1/applications/507f1f77bcf86cd799439018/regenerate-secret \ + -H 'Authorization: Bearer {token}' +``` + +--- + +## 5. Monitoring API + +시스템 모니터링 및 통계를 위한 API입니다. + +### 5.1 시스템 상태 조회 + +**Endpoint**: `GET /api/v1/monitoring/health` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "status": "healthy", + "timestamp": "2025-01-04T17:00:00Z", + "components": { + "mongodb": { + "status": "up", + "response_time_ms": 5 + }, + "redis": { + "status": "up", + "response_time_ms": 2 + }, + "pipelines": { + "status": "healthy", + "running": 3, + "stopped": 2, + "error": 0 + } + } +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/monitoring/health \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.2 시스템 메트릭 조회 + +**Endpoint**: `GET /api/v1/monitoring/metrics` + +**권한**: 인증된 사용자 + +**Response** (200 OK): +```json +{ + "timestamp": "2025-01-04T17:00:00Z", + "keywords": { + "total": 150, + "active": 120, + "inactive": 30, + "by_category": { + "people": 60, + "topics": 50, + "companies": 40 + } + }, + "pipelines": { + "total": 5, + "running": 3, + "stopped": 2, + "error": 0 + }, + "users": { + "total": 25, + "active": 20, + "disabled": 5, + "by_role": { + "admin": 3, + "editor": 12, + "viewer": 10 + } + }, + "applications": { + "total": 15 + } +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/monitoring/metrics \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.3 활동 로그 조회 + +**Endpoint**: `GET /api/v1/monitoring/logs` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `limit` (default: 100, max: 1000): 최대 로그 수 +- `level` (optional): 로그 레벨 필터 (INFO/WARNING/ERROR) +- `start_date` (optional): 시작 날짜 (ISO 8601 형식) +- `end_date` (optional): 종료 날짜 (ISO 8601 형식) + +**Response** (200 OK): +```json +{ + "logs": [ + { + "timestamp": "2025-01-04T17:00:00Z", + "level": "INFO", + "message": "User admin logged in", + "source": "auth", + "user_id": "507f1f77bcf86cd799439011" + }, + { + "timestamp": "2025-01-04T16:55:00Z", + "level": "WARNING", + "message": "Pipeline RSS Collector - Politics slowing down", + "source": "pipeline", + "pipeline_id": "507f1f77bcf86cd799439015" + } + ], + "total": 2 +} +``` + +**cURL 예제:** +```bash +# 최근 100개 로그 +curl -X GET http://localhost:8101/api/v1/monitoring/logs \ + -H 'Authorization: Bearer {token}' + +# 에러 로그만 조회 +curl -X GET 'http://localhost:8101/api/v1/monitoring/logs?level=ERROR&limit=50' \ + -H 'Authorization: Bearer {token}' + +# 날짜 범위로 필터링 +curl -X GET 'http://localhost:8101/api/v1/monitoring/logs?start_date=2025-01-04T00:00:00Z&end_date=2025-01-04T23:59:59Z' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.4 데이터베이스 통계 + +**Endpoint**: `GET /api/v1/monitoring/database/stats` + +**권한**: 관리자(admin)만 가능 + +**Response** (200 OK): +```json +{ + "database": "news_engine_console_db", + "collections": 5, + "data_size": 15728640, + "storage_size": 20971520, + "indexes": 12, + "index_size": 262144, + "avg_obj_size": 512, + "objects": 30720 +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/monitoring/database/stats \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.5 컬렉션 통계 + +**Endpoint**: `GET /api/v1/monitoring/database/collections` + +**권한**: 관리자(admin)만 가능 + +**Response** (200 OK): +```json +{ + "collections": [ + { + "name": "users", + "count": 25, + "size": 51200, + "avg_obj_size": 2048, + "storage_size": 102400, + "indexes": 3, + "index_size": 32768 + }, + { + "name": "keywords", + "count": 150, + "size": 307200, + "avg_obj_size": 2048, + "storage_size": 409600, + "indexes": 4, + "index_size": 65536 + } + ], + "total": 5 +} +``` + +**cURL 예제:** +```bash +curl -X GET http://localhost:8101/api/v1/monitoring/database/collections \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.6 파이프라인 성능 조회 + +**Endpoint**: `GET /api/v1/monitoring/pipelines/performance` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `hours` (default: 24, min: 1, max: 168): 조회할 시간 범위 + +**Response** (200 OK): +```json +{ + "period": "24 hours", + "pipelines": [ + { + "pipeline_id": "507f1f77bcf86cd799439015", + "name": "RSS Collector - Politics", + "total_runs": 48, + "successful_runs": 47, + "failed_runs": 1, + "success_rate": 97.9, + "avg_duration_seconds": 45.2, + "total_processed": 2400, + "errors": [ + { + "timestamp": "2025-01-04T15:30:20Z", + "message": "Connection timeout" + } + ] + } + ], + "total_pipelines": 5 +} +``` + +**cURL 예제:** +```bash +# 최근 24시간 +curl -X GET http://localhost:8101/api/v1/monitoring/pipelines/performance \ + -H 'Authorization: Bearer {token}' + +# 최근 7일 +curl -X GET 'http://localhost:8101/api/v1/monitoring/pipelines/performance?hours=168' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +### 5.7 에러 요약 + +**Endpoint**: `GET /api/v1/monitoring/errors/summary` + +**권한**: 인증된 사용자 + +**Query Parameters**: +- `hours` (default: 24, min: 1, max: 168): 조회할 시간 범위 + +**Response** (200 OK): +```json +{ + "period": "24 hours", + "total_errors": 15, + "by_source": { + "pipeline": 10, + "auth": 3, + "database": 2 + }, + "by_level": { + "ERROR": 12, + "CRITICAL": 3 + }, + "recent_errors": [ + { + "timestamp": "2025-01-04T17:00:00Z", + "level": "ERROR", + "message": "Failed to fetch RSS feed: Connection timeout", + "source": "pipeline", + "pipeline_id": "507f1f77bcf86cd799439015" + }, + { + "timestamp": "2025-01-04T16:45:00Z", + "level": "CRITICAL", + "message": "MongoDB connection lost", + "source": "database" + } + ] +} +``` + +**cURL 예제:** +```bash +# 최근 24시간 에러 +curl -X GET http://localhost:8101/api/v1/monitoring/errors/summary \ + -H 'Authorization: Bearer {token}' + +# 최근 48시간 에러 +curl -X GET 'http://localhost:8101/api/v1/monitoring/errors/summary?hours=48' \ + -H 'Authorization: Bearer {token}' +``` + +--- + +## 에러 코드 + +### HTTP 상태 코드 + +| 코드 | 의미 | 설명 | +|------|------|------| +| 200 | OK | 요청 성공 | +| 201 | Created | 리소스 생성 성공 | +| 204 | No Content | 요청 성공, 응답 본문 없음 (주로 DELETE) | +| 400 | Bad Request | 잘못된 요청 (유효성 검증 실패) | +| 401 | Unauthorized | 인증 실패 (토큰 없음/만료/잘못됨) | +| 403 | Forbidden | 권한 없음 (인증은 되었으나 권한 부족) | +| 404 | Not Found | 리소스를 찾을 수 없음 | +| 500 | Internal Server Error | 서버 내부 오류 | + +### 에러 응답 형식 + +모든 에러는 다음 형식으로 반환됩니다: + +```json +{ + "detail": "Error message describing what went wrong" +} +``` + +### 일반적인 에러 예제 + +#### 1. 인증 실패 (401) +```json +{ + "detail": "Incorrect username or password" +} +``` + +#### 2. 권한 부족 (403) +```json +{ + "detail": "Only admins can list users" +} +``` + +#### 3. 리소스 없음 (404) +```json +{ + "detail": "User with ID 507f1f77bcf86cd799439011 not found" +} +``` + +#### 4. 유효성 검증 실패 (400) +```json +{ + "detail": "Username already exists" +} +``` + +#### 5. 토큰 만료 (401) +```json +{ + "detail": "Could not validate credentials" +} +``` + +--- + +## 예제 코드 + +### Python 예제 + +```python +import requests +import json + +# Base URL +BASE_URL = "http://localhost:8101/api/v1" + +# 1. 로그인하여 토큰 받기 +def login(username, password): + response = requests.post( + f"{BASE_URL}/users/login", + data={"username": username, "password": password}, + headers={"Content-Type": "application/x-www-form-urlencoded"} + ) + response.raise_for_status() + return response.json()["access_token"] + +# 2. 헤더에 토큰 추가 +def get_headers(token): + return { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + +# 3. 사용 예제 +def main(): + # 로그인 + token = login("admin", "admin123456") + headers = get_headers(token) + + # 현재 사용자 정보 조회 + response = requests.get(f"{BASE_URL}/users/me", headers=headers) + print("Current user:", response.json()) + + # 키워드 목록 조회 + response = requests.get( + f"{BASE_URL}/keywords/", + headers=headers, + params={"category": "people", "page_size": 10} + ) + print("Keywords:", response.json()) + + # 키워드 생성 + new_keyword = { + "keyword": "테스트 키워드", + "category": "topics", + "status": "active", + "priority": 7 + } + response = requests.post( + f"{BASE_URL}/keywords/", + headers=headers, + json=new_keyword + ) + print("Created keyword:", response.json()) + + # 파이프라인 시작 + pipeline_id = "507f1f77bcf86cd799439015" + response = requests.post( + f"{BASE_URL}/pipelines/{pipeline_id}/start", + headers=headers + ) + print("Pipeline started:", response.json()) + +if __name__ == "__main__": + main() +``` + +### JavaScript (Node.js) 예제 + +```javascript +const axios = require('axios'); + +const BASE_URL = 'http://localhost:8101/api/v1'; + +// 1. 로그인하여 토큰 받기 +async function login(username, password) { + const response = await axios.post(`${BASE_URL}/users/login`, + new URLSearchParams({ username, password }), + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + ); + return response.data.access_token; +} + +// 2. API 클라이언트 생성 +function createClient(token) { + return axios.create({ + baseURL: BASE_URL, + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); +} + +// 3. 사용 예제 +async function main() { + try { + // 로그인 + const token = await login('admin', 'admin123456'); + const client = createClient(token); + + // 현재 사용자 정보 조회 + const userResponse = await client.get('/users/me'); + console.log('Current user:', userResponse.data); + + // 키워드 목록 조회 + const keywordsResponse = await client.get('/keywords/', { + params: { category: 'people', page_size: 10 } + }); + console.log('Keywords:', keywordsResponse.data); + + // 키워드 생성 + const newKeyword = { + keyword: '테스트 키워드', + category: 'topics', + status: 'active', + priority: 7 + }; + const createResponse = await client.post('/keywords/', newKeyword); + console.log('Created keyword:', createResponse.data); + + // 파이프라인 시작 + const pipelineId = '507f1f77bcf86cd799439015'; + const startResponse = await client.post(`/pipelines/${pipelineId}/start`); + console.log('Pipeline started:', startResponse.data); + + } catch (error) { + console.error('Error:', error.response?.data || error.message); + } +} + +main(); +``` + +### JavaScript (Browser/Fetch) 예제 + +```javascript +const BASE_URL = 'http://localhost:8101/api/v1'; + +// 1. 로그인하여 토큰 받기 +async function login(username, password) { + const response = await fetch(`${BASE_URL}/users/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ username, password }) + }); + + if (!response.ok) { + throw new Error(`Login failed: ${response.statusText}`); + } + + const data = await response.json(); + return data.access_token; +} + +// 2. 인증된 요청 함수 +async function authenticatedRequest(url, options = {}, token) { + const response = await fetch(url, { + ...options, + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + ...options.headers, + } + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || response.statusText); + } + + // 204 No Content인 경우 빈 응답 + if (response.status === 204) { + return null; + } + + return response.json(); +} + +// 3. 사용 예제 +async function main() { + try { + // 로그인 + const token = await login('admin', 'admin123456'); + console.log('Logged in successfully'); + + // 현재 사용자 정보 조회 + const user = await authenticatedRequest( + `${BASE_URL}/users/me`, + { method: 'GET' }, + token + ); + console.log('Current user:', user); + + // 키워드 목록 조회 + const keywords = await authenticatedRequest( + `${BASE_URL}/keywords/?category=people&page_size=10`, + { method: 'GET' }, + token + ); + console.log('Keywords:', keywords); + + // 키워드 생성 + const newKeyword = { + keyword: '테스트 키워드', + category: 'topics', + status: 'active', + priority: 7 + }; + const created = await authenticatedRequest( + `${BASE_URL}/keywords/`, + { + method: 'POST', + body: JSON.stringify(newKeyword) + }, + token + ); + console.log('Created keyword:', created); + + } catch (error) { + console.error('Error:', error.message); + } +} + +// 브라우저에서 실행 +main(); +``` + +--- + +## 부록: 권한 요약표 + +| 엔드포인트 | 필요 권한 | 비고 | +|-----------|---------|------| +| POST /users/login | 공개 | 인증 불필요 | +| GET /users/me | 인증됨 | 본인 정보만 | +| GET /users/ | 관리자 | 전체 사용자 목록 | +| GET /users/stats | 관리자 | 통계 조회 | +| GET /users/{id} | 관리자 또는 본인 | | +| POST /users/ | 관리자 | 사용자 생성 | +| PUT /users/{id} | 관리자 또는 본인 | 본인은 제한적 수정 | +| DELETE /users/{id} | 관리자 | 본인 삭제 불가 | +| POST /users/{id}/toggle | 관리자 | 본인 토글 불가 | +| POST /users/change-password | 인증됨 | 본인만 | +| GET /keywords/* | 인증됨 | | +| POST /keywords/ | 인증됨 | | +| PUT /keywords/{id} | 인증됨 | | +| DELETE /keywords/{id} | 인증됨 | | +| GET /pipelines/* | 인증됨 | | +| POST /pipelines/ | 인증됨 | | +| PUT /pipelines/{id} | 인증됨 | | +| DELETE /pipelines/{id} | 인증됨 | | +| POST /pipelines/{id}/start | 인증됨 | | +| POST /pipelines/{id}/stop | 인증됨 | | +| POST /pipelines/{id}/restart | 인증됨 | | +| GET /applications/ | 인증됨 | 관리자는 모두, 일반은 본인만 | +| GET /applications/stats | 관리자 | | +| GET /applications/{id} | 관리자 또는 소유자 | | +| POST /applications/ | 인증됨 | | +| PUT /applications/{id} | 관리자 또는 소유자 | | +| DELETE /applications/{id} | 관리자 또는 소유자 | | +| POST /applications/{id}/regenerate-secret | 관리자 또는 소유자 | | +| GET /monitoring/health | 인증됨 | | +| GET /monitoring/metrics | 인증됨 | | +| GET /monitoring/logs | 인증됨 | | +| GET /monitoring/database/stats | 관리자 | | +| GET /monitoring/database/collections | 관리자 | | +| GET /monitoring/pipelines/performance | 인증됨 | | +| GET /monitoring/errors/summary | 인증됨 | | + +--- + +## 변경 이력 + +| 버전 | 날짜 | 변경 내용 | +|------|------|---------| +| 1.0.0 | 2025-01-04 | 초기 버전 (37개 엔드포인트) | + +--- + +**문서 생성일**: 2025-01-04 +**API 버전**: v1 +**테스트 상태**: 100% 완료 (37/37 엔드포인트) +**서버 포트**: 8101 diff --git a/services/news-engine-console/backend/fix_objectid.py b/services/news-engine-console/backend/fix_objectid.py new file mode 100644 index 0000000..9a658c7 --- /dev/null +++ b/services/news-engine-console/backend/fix_objectid.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Script to add ObjectId conversion before creating model instances""" +import re + +def add_objectid_conversion(file_path, model_name): + """Add doc['_id'] = str(doc['_id']) before Model(**doc) calls""" + + with open(file_path, 'r') as f: + lines = f.readlines() + + modified_lines = [] + i = 0 + changes_made = 0 + + while i < len(lines): + line = lines[i] + + # Pattern 1: Single line creation like: return Keyword(**doc) + if re.search(rf'{model_name}\(\*\*doc\)', line): + indent = len(line) - len(line.lstrip()) + # Check if previous line already has the conversion + if i > 0 and 'doc["_id"] = str(doc["_id"])' in lines[i-1]: + modified_lines.append(line) + i += 1 + continue + # Check if this is inside a loop or conditional that already has conversion + if i > 0 and any('doc["_id"] = str(doc["_id"])' in lines[j] for j in range(max(0, i-5), i)): + modified_lines.append(line) + i += 1 + continue + # Add conversion before this line + modified_lines.append(' ' * indent + 'doc["_id"] = str(doc["_id"]) # Convert ObjectId to string\n') + modified_lines.append(line) + changes_made += 1 + i += 1 + continue + + # Pattern 2: In loops like: keywords.append(Keyword(**doc)) + if re.search(rf'{model_name}\(\*\*doc\)', line) and ('append' in line or 'for' in line): + indent = len(line) - len(line.lstrip()) + # Check if previous line already has the conversion + if i > 0 and 'doc["_id"] = str(doc["_id"])' in lines[i-1]: + modified_lines.append(line) + i += 1 + continue + # Add conversion before this line + modified_lines.append(' ' * indent + 'doc["_id"] = str(doc["_id"]) # Convert ObjectId to string\n') + modified_lines.append(line) + changes_made += 1 + i += 1 + continue + + # Pattern 3: Dictionary creation like: return Keyword(**keyword_dict) + match = re.search(rf'{model_name}\(\*\*(\w+)\)', line) + if match and match.group(1) != 'doc': + dict_name = match.group(1) + indent = len(line) - len(line.lstrip()) + # Check if previous line already has the conversion + if i > 0 and f'{dict_name}["_id"] = str({dict_name}["_id"])' in lines[i-1]: + modified_lines.append(line) + i += 1 + continue + # Add conversion before this line + modified_lines.append(' ' * indent + f'{dict_name}["_id"] = str({dict_name}["_id"]) # Convert ObjectId to string\n') + modified_lines.append(line) + changes_made += 1 + i += 1 + continue + + # Pattern 4: List comprehension like: [Keyword(**kw) for kw in keywords_dicts] + if re.search(rf'\[{model_name}\(\*\*\w+\)', line): + # This needs manual fixing as it's a list comprehension + print(f"Line {i+1}: List comprehension found, needs manual fixing: {line.strip()}") + + modified_lines.append(line) + i += 1 + + # Write back + with open(file_path, 'w') as f: + f.writelines(modified_lines) + + print(f"Modified {file_path}: {changes_made} changes made") + return changes_made + +if __name__ == "__main__": + total = 0 + total += add_objectid_conversion("app/services/keyword_service.py", "Keyword") + total += add_objectid_conversion("app/services/pipeline_service.py", "Pipeline") + total += add_objectid_conversion("app/services/application_service.py", "Application") + print(f"\nTotal changes: {total}")