From 764984402304f0c4e786cd67ee7bcc752eff122e Mon Sep 17 00:00:00 2001 From: jungwoo choi Date: Tue, 4 Nov 2025 16:16:09 +0900 Subject: [PATCH] feat: Initialize News Engine Console project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create comprehensive news pipeline management and monitoring system with backend API structure and detailed implementation roadmap. Core Features (7): 1. Keyword Management - Pipeline keyword CRUD and control 2. Pipeline Monitoring - Processing stats and utilization metrics 3. Pipeline Control - Step-wise start/stop and scheduling 4. Logging System - Pipeline status logs and error tracking 5. User Management - User CRUD with role-based access (Admin/Editor/Viewer) 6. Application Management - OAuth2/JWT-based Application CRUD 7. System Monitoring - Service health checks and resource monitoring Technology Stack: - Backend: FastAPI + Motor (MongoDB async) + Redis - Frontend: React 18 + TypeScript + Material-UI v7 (planned) - Auth: JWT + OAuth2 - Infrastructure: Docker + Kubernetes Project Structure: - backend/app/api/ - 5 API routers (keywords, pipelines, users, applications, monitoring) - backend/app/core/ - Core configurations (config, database, auth) - backend/app/models/ - Data models (planned) - backend/app/services/ - Business logic (planned) - backend/app/schemas/ - Pydantic schemas (planned) - frontend/ - React application (planned) - k8s/ - Kubernetes manifests (planned) Documentation: - README.md - Project overview, current status, API endpoints, DB schema - TODO.md - Detailed implementation plan for next sessions Current Status: βœ… Project structure initialized βœ… Backend basic configuration (config, database, auth) βœ… API router skeletons (5 routers) βœ… Requirements and environment setup 🚧 Models, services, and schemas pending πŸ“‹ Frontend implementation pending πŸ“‹ Docker and Kubernetes deployment pending Next Steps (See TODO.md): 1. MongoDB schema and indexes 2. Pydantic schemas with validation 3. Service layer implementation 4. Redis integration 5. Login/authentication API 6. Frontend basic setup This provides a solid foundation for building a comprehensive news pipeline management console system. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- services/news-engine-console/README.md | 289 +++++++++++ services/news-engine-console/TODO.md | 475 ++++++++++++++++++ .../news-engine-console/backend/.env.example | 19 + .../backend/app/__init__.py | 1 + .../backend/app/api/__init__.py | 1 + .../backend/app/api/applications.py | 14 + .../backend/app/api/keywords.py | 39 ++ .../backend/app/api/monitoring.py | 14 + .../backend/app/api/pipelines.py | 24 + .../backend/app/api/users.py | 19 + .../backend/app/core/auth.py | 75 +++ .../backend/app/core/config.py | 32 ++ .../backend/app/core/database.py | 24 + services/news-engine-console/backend/main.py | 52 ++ .../backend/requirements.txt | 11 + 15 files changed, 1089 insertions(+) create mode 100644 services/news-engine-console/README.md create mode 100644 services/news-engine-console/TODO.md create mode 100644 services/news-engine-console/backend/.env.example create mode 100644 services/news-engine-console/backend/app/__init__.py create mode 100644 services/news-engine-console/backend/app/api/__init__.py create mode 100644 services/news-engine-console/backend/app/api/applications.py create mode 100644 services/news-engine-console/backend/app/api/keywords.py create mode 100644 services/news-engine-console/backend/app/api/monitoring.py create mode 100644 services/news-engine-console/backend/app/api/pipelines.py create mode 100644 services/news-engine-console/backend/app/api/users.py create mode 100644 services/news-engine-console/backend/app/core/auth.py create mode 100644 services/news-engine-console/backend/app/core/config.py create mode 100644 services/news-engine-console/backend/app/core/database.py create mode 100644 services/news-engine-console/backend/main.py create mode 100644 services/news-engine-console/backend/requirements.txt diff --git a/services/news-engine-console/README.md b/services/news-engine-console/README.md new file mode 100644 index 0000000..1620114 --- /dev/null +++ b/services/news-engine-console/README.md @@ -0,0 +1,289 @@ +# News Engine Console + +λ‰΄μŠ€ νŒŒμ΄ν”„λΌμΈ 관리 및 λͺ¨λ‹ˆν„°λ§ 톡합 μ½˜μ†” μ‹œμŠ€ν…œ + +## ν”„λ‘œμ νŠΈ κ°œμš” + +News Engine Console은 λ‰΄μŠ€ νŒŒμ΄ν”„λΌμΈμ˜ 전체 lifecycle을 κ΄€λ¦¬ν•˜κ³  λͺ¨λ‹ˆν„°λ§ν•˜λŠ” 톡합 관리 μ‹œμŠ€ν…œμž…λ‹ˆλ‹€. + +### 핡심 κΈ°λŠ₯ + +1. **ν‚€μ›Œλ“œ 관리** - νŒŒμ΄ν”„λΌμΈ ν‚€μ›Œλ“œ CRUD, ν™œμ„±ν™”/λΉ„ν™œμ„±ν™” +2. **νŒŒμ΄ν”„λΌμΈ λͺ¨λ‹ˆν„°λ§** - νŒŒμ΄ν”„λΌμΈλ³„ 처리 μˆ˜λŸ‰, ν™œμš©λ„ 톡계 +3. **νŒŒμ΄ν”„λΌμΈ μ œμ–΄** - μŠ€ν…λ³„ μ‹œμž‘/쀑지, μŠ€μΌ€μ€„λ§ +4. **λ‘œκΉ… μ‹œμŠ€ν…œ** - νŒŒμ΄ν”„λΌμΈ μƒνƒœ 둜그, μ—λŸ¬ 좔적 +5. **μ‚¬μš©μž 관리** - User CRUD, μ—­ν•  기반 κΆŒν•œ (Admin/Editor/Viewer) +6. **μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 관리** - OAuth2/JWT 기반 Application CRUD +7. **μ‹œμŠ€ν…œ λͺ¨λ‹ˆν„°λ§** - μ„œλΉ„μŠ€ ν—¬μŠ€μ²΄ν¬, λ¦¬μ†ŒμŠ€ μ‚¬μš©λŸ‰ + +## 기술 μŠ€νƒ + +### Backend +- FastAPI (Python 3.11) +- Motor (MongoDB async driver) +- Redis (캐싱, Pub/Sub) +- JWT + OAuth2 인증 + +### Frontend (μ˜ˆμ •) +- React 18 + TypeScript +- Material-UI v7 +- React Query +- Recharts (톡계 차트) + +### Infrastructure +- Docker +- Kubernetes +- MongoDB (ai_writer_db) +- Redis + +## ν”„λ‘œμ νŠΈ ꡬ쑰 + +``` +services/news-engine-console/ +β”œβ”€β”€ README.md +β”œβ”€β”€ TODO.md # 상세 κ΅¬ν˜„ κ³„νš +β”œβ”€β”€ backend/ +β”‚ β”œβ”€β”€ Dockerfile +β”‚ β”œβ”€β”€ requirements.txt +β”‚ β”œβ”€β”€ main.py +β”‚ β”œβ”€β”€ .env.example +β”‚ └── app/ +β”‚ β”œβ”€β”€ api/ # API μ—”λ“œν¬μΈνŠΈ +β”‚ β”‚ β”œβ”€β”€ keywords.py # βœ… ν‚€μ›Œλ“œ 관리 +β”‚ β”‚ β”œβ”€β”€ pipelines.py # βœ… νŒŒμ΄ν”„λΌμΈ μ œμ–΄/λͺ¨λ‹ˆν„°λ§ +β”‚ β”‚ β”œβ”€β”€ users.py # βœ… μ‚¬μš©μž 관리 +β”‚ β”‚ β”œβ”€β”€ applications.py # βœ… Application 관리 +β”‚ β”‚ └── monitoring.py # βœ… μ‹œμŠ€ν…œ λͺ¨λ‹ˆν„°λ§ +β”‚ β”œβ”€β”€ core/ # 핡심 μ„€μ • +β”‚ β”‚ β”œβ”€β”€ config.py # βœ… μ„€μ • 관리 +β”‚ β”‚ β”œβ”€β”€ database.py # βœ… MongoDB μ—°κ²° +β”‚ β”‚ └── auth.py # βœ… JWT/OAuth2 인증 +β”‚ β”œβ”€β”€ models/ # 데이터 λͺ¨λΈ (TODO) +β”‚ β”œβ”€β”€ services/ # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 (TODO) +β”‚ └── schemas/ # Pydantic μŠ€ν‚€λ§ˆ (TODO) +β”œβ”€β”€ frontend/ # TODO +β”‚ └── src/ +β”‚ β”œβ”€β”€ api/ +β”‚ β”œβ”€β”€ components/ +β”‚ β”œβ”€β”€ pages/ +β”‚ └── types/ +└── k8s/ # TODO + β”œβ”€β”€ namespace.yaml + β”œβ”€β”€ backend-deployment.yaml + β”œβ”€β”€ frontend-deployment.yaml + └── service.yaml +``` + +## ν˜„μž¬ κ΅¬ν˜„ μƒνƒœ + +### βœ… μ™„λ£Œ +- [x] ν”„λ‘œμ νŠΈ 디렉토리 ꡬ쑰 +- [x] Backend κΈ°λ³Έ μ„€μ • (config, database, auth) +- [x] API λΌμš°ν„° κΈ°λ³Έ ꡬ쑰 (5개 λΌμš°ν„°) + - Keywords API + - Pipelines API + - Users API + - Applications API + - Monitoring API + +### 🚧 μ§„ν–‰ 쀑 +- [ ] Backend 상세 κ΅¬ν˜„ (models, services, schemas) +- [ ] MongoDB μ»¬λ ‰μ…˜ 및 인덱슀 섀계 +- [ ] Redis μ—°κ²° 및 캐싱 둜직 + +### πŸ“‹ μ˜ˆμ • +- [ ] Frontend κ΅¬ν˜„ +- [ ] Dockerfile μž‘μ„± +- [ ] Kubernetes 배포 μ„€μ • +- [ ] CI/CD νŒŒμ΄ν”„λΌμΈ +- [ ] API λ¬Έμ„œ (OpenAPI/Swagger) + +## API μ—”λ“œν¬μΈνŠΈ + +### Keywords API (`/api/v1/keywords`) +- `GET /` - ν‚€μ›Œλ“œ λͺ©λ‘ 쑰회 +- `POST /` - ν‚€μ›Œλ“œ 생성 +- `PUT /{keyword_id}` - ν‚€μ›Œλ“œ μˆ˜μ • +- `DELETE /{keyword_id}` - ν‚€μ›Œλ“œ μ‚­μ œ + +### Pipelines API (`/api/v1/pipelines`) +- `GET /` - νŒŒμ΄ν”„λΌμΈ λͺ©λ‘ 및 μƒνƒœ +- `GET /{pipeline_id}/stats` - νŒŒμ΄ν”„λΌμΈ 톡계 +- `POST /{pipeline_id}/start` - νŒŒμ΄ν”„λΌμΈ μ‹œμž‘ +- `POST /{pipeline_id}/stop` - νŒŒμ΄ν”„λΌμΈ 쀑지 + +### Users API (`/api/v1/users`) +- `GET /` - μ‚¬μš©μž λͺ©λ‘ +- `POST /` - μ‚¬μš©μž 생성 +- `GET /me` - ν˜„μž¬ μ‚¬μš©μž 정보 + +### Applications API (`/api/v1/applications`) +- `GET /` - Application λͺ©λ‘ +- `POST /` - Application 생성 (OAuth2 ν΄λΌμ΄μ–ΈνŠΈ 등둝) + +### Monitoring API (`/api/v1/monitoring`) +- `GET /system` - μ‹œμŠ€ν…œ μƒνƒœ +- `GET /logs` - νŒŒμ΄ν”„λΌμΈ 둜그 + +## 둜컬 개발 ν™˜κ²½ μ„€μ • + +### Prerequisites +- Python 3.11+ +- MongoDB (localhost:27017) +- Redis (localhost:6379) + +### Backend μ‹€ν–‰ + +```bash +cd services/news-engine-console/backend + +# κ°€μƒν™˜κ²½ 생성 (선택) +python3 -m venv venv +source venv/bin/activate + +# μ˜μ‘΄μ„± μ„€μΉ˜ +pip install -r requirements.txt + +# ν™˜κ²½ λ³€μˆ˜ μ„€μ • +cp .env.example .env +# .env 파일 μˆ˜μ • + +# μ„œλ²„ μ‹€ν–‰ +python main.py +``` + +μ„œλ²„ μ‹€ν–‰ ν›„: http://localhost:8100/docs (Swagger UI) + +## ν™˜κ²½ λ³€μˆ˜ + +```env +# MongoDB +MONGODB_URL=mongodb://localhost:27017 +DB_NAME=ai_writer_db + +# Redis +REDIS_URL=redis://localhost:6379 + +# JWT +SECRET_KEY=your-secret-key-here +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 + +# Service +SERVICE_NAME=news-engine-console +API_V1_STR=/api/v1 +PORT=8100 + +# CORS +ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3100 +``` + +## λ‹€μŒ 단계 (TODO.md μ°Έμ‘°) + +### Phase 1: Backend μ™„μ„± (μš°μ„ μˆœμœ„ λ†’μŒ) +1. MongoDB μŠ€ν‚€λ§ˆ 섀계 + - keywords μ»¬λ ‰μ…˜ + - pipelines μ»¬λ ‰μ…˜ + - users μ»¬λ ‰μ…˜ + - applications μ»¬λ ‰μ…˜ + - logs μ»¬λ ‰μ…˜ + +2. Pydantic λͺ¨λΈ 및 μŠ€ν‚€λ§ˆ μž‘μ„± + - Request/Response λͺ¨λΈ + - μœ νš¨μ„± 검증 + +3. λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 κ΅¬ν˜„ + - KeywordService + - PipelineService + - UserService + - ApplicationService + - MonitoringService + +4. Redis 톡합 + - 캐싱 λ ˆμ΄μ–΄ + - Pub/Sub for real-time updates + +### Phase 2: Frontend κ΅¬ν˜„ +1. React ν”„λ‘œμ νŠΈ μ„€μ • +2. Material-UI λ ˆμ΄μ•„μ›ƒ +3. νŽ˜μ΄μ§€ κ΅¬ν˜„ + - Dashboard (톡계 μš”μ•½) + - Keywords Management + - Pipelines Control + - Users Management + - Applications Management + - System Monitoring + +### Phase 3: 배포 +1. Dockerfile μž‘μ„± +2. Kubernetes λ§€λ‹ˆνŽ˜μŠ€νŠΈ +3. CI/CD μ„€μ • + +## λ°μ΄ν„°λ² μ΄μŠ€ 섀계 (Draft) + +### keywords μ»¬λ ‰μ…˜ +```json +{ + "_id": "ObjectId", + "keyword": "string", + "category": "string", + "status": "active|inactive", + "created_at": "datetime", + "updated_at": "datetime", + "created_by": "user_id" +} +``` + +### pipelines μ»¬λ ‰μ…˜ +```json +{ + "_id": "ObjectId", + "name": "string", + "type": "rss|translation|image", + "status": "running|stopped|error", + "config": {}, + "stats": { + "total_processed": 0, + "success_count": 0, + "error_count": 0, + "last_run": "datetime" + } +} +``` + +### users μ»¬λ ‰μ…˜ +```json +{ + "_id": "ObjectId", + "username": "string", + "email": "string", + "hashed_password": "string", + "full_name": "string", + "role": "admin|editor|viewer", + "disabled": false, + "created_at": "datetime" +} +``` + +## μ—­ν•  기반 κΆŒν•œ + +- **Admin**: λͺ¨λ“  κΈ°λŠ₯ μ ‘κ·Ό +- **Editor**: ν‚€μ›Œλ“œ/νŒŒμ΄ν”„λΌμΈ 관리, λͺ¨λ‹ˆν„°λ§ 쑰회 +- **Viewer**: 쑰회만 κ°€λŠ₯ + +## κΈ°μ—¬ κ°€μ΄λ“œ + +1. κΈ°λŠ₯ κ΅¬ν˜„ μ „ TODO.md 확인 +2. API μ—”λ“œν¬μΈνŠΈ μΆ”κ°€ μ‹œ λ¬Έμ„œ μ—…λ°μ΄νŠΈ +3. ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± +4. Commit λ©”μ‹œμ§€ κ·œμΉ™ μ€€μˆ˜ + +## λΌμ΄μ„ μŠ€ + +Part of Site11 Platform - Internal Use + +--- + +**μ΅œμ’… μ—…λ°μ΄νŠΈ**: 2024-01-15 +**버전**: 0.1.0 (Alpha) +**μž‘μ„±μž**: Site11 Development Team diff --git a/services/news-engine-console/TODO.md b/services/news-engine-console/TODO.md new file mode 100644 index 0000000..606c25f --- /dev/null +++ b/services/news-engine-console/TODO.md @@ -0,0 +1,475 @@ +# News Engine Console - κ΅¬ν˜„ κ³„νš + +λ‹€μŒ μ„Έμ…˜μ„ μœ„ν•œ 상세 κ΅¬ν˜„ κ³„νš + +--- + +## 🎯 Phase 1: Backend μ™„μ„± (μš°μ„ μˆœμœ„) + +### 1.1 데이터 λͺ¨λΈ κ΅¬ν˜„ + +**models/keyword.py** +```python +class Keyword: + _id: ObjectId + keyword: str + category: str # 'people', 'topics', 'companies' + status: str # 'active', 'inactive' + pipeline_type: str # 'rss', 'translation', 'all' + priority: int # 1-10 + metadata: dict + created_at: datetime + updated_at: datetime + created_by: str +``` + +**models/pipeline.py** +```python +class Pipeline: + _id: ObjectId + name: str + type: str # 'rss_collector', 'translator', 'image_generator' + status: str # 'running', 'stopped', 'error' + config: dict + schedule: str # cron expression + stats: PipelineStats + last_run: datetime + next_run: datetime +``` + +**models/user.py** +```python +class User: + _id: ObjectId + username: str (unique) + email: str (unique) + hashed_password: str + full_name: str + role: str # 'admin', 'editor', 'viewer' + disabled: bool + created_at: datetime + last_login: datetime +``` + +**models/application.py** +```python +class Application: + _id: ObjectId + name: str + client_id: str (unique) + client_secret: str (hashed) + redirect_uris: List[str] + grant_types: List[str] + scopes: List[str] + owner_id: str + created_at: datetime +``` + +### 1.2 Pydantic μŠ€ν‚€λ§ˆ μž‘μ„± + +**schemas/keyword.py** +- KeywordCreate +- KeywordUpdate +- KeywordResponse +- KeywordList + +**schemas/pipeline.py** +- PipelineCreate +- PipelineUpdate +- PipelineResponse +- PipelineStats +- PipelineList + +**schemas/user.py** +- UserCreate +- UserUpdate +- UserResponse +- UserLogin + +**schemas/application.py** +- ApplicationCreate +- ApplicationUpdate +- ApplicationResponse + +### 1.3 μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ κ΅¬ν˜„ + +**services/keyword_service.py** +- `async def get_keywords(filters, pagination)` +- `async def create_keyword(keyword_data)` +- `async def update_keyword(keyword_id, update_data)` +- `async def delete_keyword(keyword_id)` +- `async def toggle_keyword_status(keyword_id)` +- `async def get_keyword_stats(keyword_id)` + +**services/pipeline_service.py** +- `async def get_pipelines()` +- `async def get_pipeline_stats(pipeline_id)` +- `async def start_pipeline(pipeline_id)` +- `async def stop_pipeline(pipeline_id)` +- `async def restart_pipeline(pipeline_id)` +- `async def get_pipeline_logs(pipeline_id, limit)` +- `async def update_pipeline_config(pipeline_id, config)` + +**services/user_service.py** +- `async def create_user(user_data)` +- `async def authenticate_user(username, password)` +- `async def get_user_by_username(username)` +- `async def update_user(user_id, update_data)` +- `async def delete_user(user_id)` + +**services/application_service.py** +- `async def create_application(app_data)` +- `async def get_applications(user_id)` +- `async def regenerate_client_secret(app_id)` +- `async def delete_application(app_id)` + +**services/monitoring_service.py** +- `async def get_system_health()` +- `async def get_service_status()` +- `async def get_database_stats()` +- `async def get_redis_stats()` +- `async def get_recent_logs(limit)` + +### 1.4 Redis 톡합 + +**core/redis_client.py** +```python +class RedisClient: + async def get(key) + async def set(key, value, expire) + async def delete(key) + async def publish(channel, message) + async def subscribe(channel, callback) +``` + +**μ‚¬μš© μΌ€μ΄μŠ€**: +- νŒŒμ΄ν”„λΌμΈ μƒνƒœ 캐싱 +- μ‹€μ‹œκ°„ 톡계 μ—…λ°μ΄νŠΈ (Pub/Sub) +- μ‚¬μš©μž μ„Έμ…˜ 관리 +- Rate limiting + +### 1.5 API μ—”λ“œν¬μΈνŠΈ μ™„μ„± + +**keywords.py** +- [x] GET / - λͺ©λ‘ 쑰회 (κΈ°λ³Έ ꡬ쑰) +- [ ] 필터링 (category, status, search) +- [ ] νŽ˜μ΄μ§€λ„€μ΄μ…˜ +- [ ] μ •λ ¬ (created_at, priority) +- [ ] GET /{id}/stats - ν‚€μ›Œλ“œ 톡계 +- [ ] POST /{id}/toggle - ν™œμ„±ν™”/λΉ„ν™œμ„±ν™” + +**pipelines.py** +- [x] GET / - λͺ©λ‘ 쑰회 (κΈ°λ³Έ ꡬ쑰) +- [ ] GET /{id}/logs - 둜그 쑰회 +- [ ] POST /{id}/restart - μž¬μ‹œμž‘ +- [ ] PUT /{id}/config - μ„€μ • μ—…λ°μ΄νŠΈ +- [ ] GET /types - νŒŒμ΄ν”„λΌμΈ νƒ€μž… λͺ©λ‘ + +**users.py** +- [x] GET / - λͺ©λ‘ 쑰회 (κΈ°λ³Έ ꡬ쑰) +- [ ] PUT /{id} - μ‚¬μš©μž μˆ˜μ • +- [ ] DELETE /{id} - μ‚¬μš©μž μ‚­μ œ +- [ ] POST /login - 둜그인 (JWT λ°œκΈ‰) +- [ ] POST /register - νšŒμ›κ°€μž… + +**applications.py** +- [x] GET / - λͺ©λ‘ 쑰회 (κΈ°λ³Έ ꡬ쑰) +- [ ] GET /{id} - 상세 쑰회 +- [ ] PUT /{id} - μˆ˜μ • +- [ ] DELETE /{id} - μ‚­μ œ +- [ ] POST /{id}/regenerate-secret - μ‹œν¬λ¦Ώ μž¬μƒμ„± + +**monitoring.py** +- [x] GET /system - μ‹œμŠ€ν…œ μƒνƒœ (κΈ°λ³Έ ꡬ쑰) +- [ ] GET /services - μ„œλΉ„μŠ€λ³„ μƒνƒœ +- [ ] GET /database - DB 톡계 +- [ ] GET /redis - Redis μƒνƒœ +- [ ] GET /pipelines/activity - νŒŒμ΄ν”„λΌμΈ ν™œλ™ 둜그 + +--- + +## 🎨 Phase 2: Frontend κ΅¬ν˜„ + +### 2.1 ν”„λ‘œμ νŠΈ μ„€μ • + +```bash +cd frontend +npm create vite@latest . -- --template react-ts +npm install @mui/material @emotion/react @emotion/styled +npm install @tanstack/react-query axios react-router-dom +npm install recharts date-fns +``` + +### 2.2 λ ˆμ΄μ•„μ›ƒ κ΅¬ν˜„ + +**components/Layout/AppLayout.tsx** +- Sidebar with navigation +- Top bar with user info +- Main content area + +**components/Layout/Sidebar.tsx** +- Dashboard +- Keywords +- Pipelines +- Users +- Applications +- Monitoring + +### 2.3 νŽ˜μ΄μ§€ κ΅¬ν˜„ + +**pages/Dashboard.tsx** +- 전체 톡계 μš”μ•½ +- νŒŒμ΄ν”„λΌμΈ μƒνƒœ 차트 +- 졜근 ν™œλ™ 둜그 +- ν‚€μ›Œλ“œ ν™œμš©λ„ TOP 10 + +**pages/Keywords.tsx** +- ν‚€μ›Œλ“œ λͺ©λ‘ ν…Œμ΄λΈ” +- 검색, ν•„ν„°, μ •λ ¬ +- μΆ”κ°€/μˆ˜μ •/μ‚­μ œ λͺ¨λ‹¬ +- ν™œμ„±ν™”/λΉ„ν™œμ„±ν™” ν† κΈ€ +- ν‚€μ›Œλ“œλ³„ 톡계 차트 + +**pages/Pipelines.tsx** +- νŒŒμ΄ν”„λΌμΈ μΉ΄λ“œ κ·Έλ¦¬λ“œ +- μƒνƒœλ³„ ν•„ν„° (Running, Stopped, Error) +- μ‹œμž‘/쀑지 λ²„νŠΌ +- μ‹€μ‹œκ°„ 둜그 슀트림 +- 톡계 차트 + +**pages/Users.tsx** +- μ‚¬μš©μž λͺ©λ‘ ν…Œμ΄λΈ” +- μ—­ν•  ν•„ν„° (Admin, Editor, Viewer) +- μΆ”κ°€/μˆ˜μ •/μ‚­μ œ λͺ¨λ‹¬ +- λ§ˆμ§€λ§‰ 둜그인 μ‹œκ°„ + +**pages/Applications.tsx** +- Application μΉ΄λ“œ κ·Έλ¦¬λ“œ +- Client ID/Secret ν‘œμ‹œ +- 생성/μˆ˜μ •/μ‚­μ œ +- Secret μž¬μƒμ„± κΈ°λŠ₯ + +**pages/Monitoring.tsx** +- μ‹œμŠ€ν…œ ν—¬μŠ€μ²΄ν¬ λŒ€μ‹œλ³΄λ“œ +- μ„œλΉ„μŠ€λ³„ μƒνƒœ (MongoDB, Redis, etc.) +- CPU/λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 차트 +- μ‹€μ‹œκ°„ 둜그 슀트림 + +### 2.4 API ν΄λΌμ΄μ–ΈνŠΈ + +**api/client.ts** +```typescript +import axios from 'axios'; + +const apiClient = axios.create({ + baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8100/api/v1', + headers: { + 'Content-Type': 'application/json' + } +}); + +// Interceptors for auth token +apiClient.interceptors.request.use(config => { + const token = localStorage.getItem('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export default apiClient; +``` + +### 2.5 TypeScript νƒ€μž… μ •μ˜ + +**types/index.ts** +- Keyword +- Pipeline +- User +- Application +- PipelineStats +- SystemStatus + +--- + +## 🐳 Phase 3: Docker & Kubernetes + +### 3.1 Backend Dockerfile + +```dockerfile +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8100 + +CMD ["python", "main.py"] +``` + +### 3.2 Frontend Dockerfile + +```dockerfile +FROM node:18-alpine AS builder +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +``` + +### 3.3 Kubernetes λ§€λ‹ˆνŽ˜μŠ€νŠΈ + +**k8s/namespace.yaml** +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: site11-console +``` + +**k8s/backend-deployment.yaml** +- Deployment with 2 replicas +- ConfigMap for env vars +- Secret for sensitive data +- Service (ClusterIP) + +**k8s/frontend-deployment.yaml** +- Deployment with 2 replicas +- Service (LoadBalancer or Ingress) + +--- + +## πŸ“Š Phase 4: κ³ κΈ‰ κΈ°λŠ₯ + +### 4.1 μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ + +- WebSocket μ—°κ²° (νŒŒμ΄ν”„λΌμΈ μƒνƒœ) +- Server-Sent Events (둜그 슀트림) +- Redis Pub/Sub ν™œμš© + +### 4.2 μ•Œλ¦Ό μ‹œμŠ€ν…œ + +- νŒŒμ΄ν”„λΌμΈ μ—λŸ¬ μ‹œ μ•Œλ¦Ό +- ν‚€μ›Œλ“œ 처리 μ™„λ£Œ μ•Œλ¦Ό +- 이메일/Slack 톡합 + +### 4.3 μŠ€μΌ€μ€„λ§ + +- Cron 기반 νŒŒμ΄ν”„λΌμΈ μŠ€μΌ€μ€„ +- μˆ˜λ™ μ‹€ν–‰ vs μžλ™ μ‹€ν–‰ +- μŠ€μΌ€μ€„ νžˆμŠ€ν† λ¦¬ + +### 4.4 톡계 & 뢄석 + +- 일/μ£Ό/월별 처리 톡계 +- ν‚€μ›Œλ“œλ³„ μ„±κ³Ό 뢄석 +- νŒŒμ΄ν”„λΌμΈ μ„±λŠ₯ λ©”νŠΈλ¦­ +- CSV λ‹€μš΄λ‘œλ“œ + +--- + +## πŸ§ͺ Phase 5: ν…ŒμŠ€νŠΈ & λ¬Έμ„œν™” + +### 5.1 Backend ν…ŒμŠ€νŠΈ + +- pytest fixtures +- API endpoint tests +- Integration tests +- Coverage report + +### 5.2 Frontend ν…ŒμŠ€νŠΈ + +- React Testing Library +- Component tests +- E2E tests (Playwright) + +### 5.3 API λ¬Έμ„œ + +- OpenAPI/Swagger μžλ™ 생성 +- API μ˜ˆμ‹œ μ½”λ“œ +- μ—λŸ¬ 응닡 λͺ…μ„Έ + +--- + +## πŸš€ μš°μ„ μˆœμœ„ + +### μ¦‰μ‹œ μ‹œμž‘ (λ‹€μŒ μ„Έμ…˜) + +1. **MongoDB μŠ€ν‚€λ§ˆ 및 인덱슀 생성** + - keywords, pipelines, users μ»¬λ ‰μ…˜ + - 인덱슀 섀계 + +2. **Pydantic μŠ€ν‚€λ§ˆ μž‘μ„±** + - Request/Response λͺ¨λΈ + - μœ νš¨μ„± 검증 + +3. **ν‚€μ›Œλ“œ 관리 κΈ°λŠ₯ μ™„μ„±** + - KeywordService κ΅¬ν˜„ + - CRUD API μ™„μ„± + - λ‹¨μœ„ ν…ŒμŠ€νŠΈ + +4. **둜그인 API κ΅¬ν˜„** + - JWT 토큰 λ°œκΈ‰ + - User 인증 + +### 쀑기 λͺ©ν‘œ (1-2μ£Ό) + +1. νŒŒμ΄ν”„λΌμΈ μ œμ–΄ API μ™„μ„± +2. Frontend κΈ°λ³Έ ꡬ쑰 +3. Dashboard νŽ˜μ΄μ§€ +4. Dockerfile μž‘μ„± + +### μž₯κΈ° λͺ©ν‘œ (1κ°œμ›”) + +1. Frontend 전체 νŽ˜μ΄μ§€ +2. Kubernetes 배포 +3. μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ +4. μ•Œλ¦Ό μ‹œμŠ€ν…œ + +--- + +## πŸ“ 체크리슀트 + +### Backend +- [x] ν”„λ‘œμ νŠΈ ꡬ쑰 +- [x] κΈ°λ³Έ μ„€μ • (config, database, auth) +- [x] API λΌμš°ν„° κΈ°λ³Έ ꡬ쑰 +- [ ] Pydantic μŠ€ν‚€λ§ˆ +- [ ] MongoDB μ»¬λ ‰μ…˜ 및 인덱슀 +- [ ] μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ κ΅¬ν˜„ +- [ ] Redis 톡합 +- [ ] 둜그인/인증 API +- [ ] μ—λŸ¬ 핸듀링 +- [ ] λ‘œκΉ… μ‹œμŠ€ν…œ + +### Frontend +- [ ] ν”„λ‘œμ νŠΈ μ„€μ • +- [ ] λ ˆμ΄μ•„μ›ƒ 및 λΌμš°νŒ… +- [ ] 둜그인 νŽ˜μ΄μ§€ +- [ ] Dashboard +- [ ] Keywords νŽ˜μ΄μ§€ +- [ ] Pipelines νŽ˜μ΄μ§€ +- [ ] Users νŽ˜μ΄μ§€ +- [ ] Applications νŽ˜μ΄μ§€ +- [ ] Monitoring νŽ˜μ΄μ§€ + +### DevOps +- [ ] Backend Dockerfile +- [ ] Frontend Dockerfile +- [ ] docker-compose.yml +- [ ] Kubernetes λ§€λ‹ˆνŽ˜μŠ€νŠΈ +- [ ] CI/CD μ„€μ • + +--- + +**λ‹€μŒ μ„Έμ…˜ μ‹œμž‘ μ‹œ**: 이 TODO.mdλ₯Ό ν™•μΈν•˜κ³  체크리슀트 μ—…λ°μ΄νŠΈ diff --git a/services/news-engine-console/backend/.env.example b/services/news-engine-console/backend/.env.example new file mode 100644 index 0000000..836b275 --- /dev/null +++ b/services/news-engine-console/backend/.env.example @@ -0,0 +1,19 @@ +# MongoDB +MONGODB_URL=mongodb://localhost:27017 +DB_NAME=ai_writer_db + +# Redis +REDIS_URL=redis://localhost:6379 + +# JWT +SECRET_KEY=your-secret-key-here-change-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 + +# Service +SERVICE_NAME=news-engine-console +API_V1_STR=/api/v1 +PORT=8100 + +# CORS +ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3100 diff --git a/services/news-engine-console/backend/app/__init__.py b/services/news-engine-console/backend/app/__init__.py new file mode 100644 index 0000000..a8a1d36 --- /dev/null +++ b/services/news-engine-console/backend/app/__init__.py @@ -0,0 +1 @@ +# News Engine Console Backend diff --git a/services/news-engine-console/backend/app/api/__init__.py b/services/news-engine-console/backend/app/api/__init__.py new file mode 100644 index 0000000..8db0b57 --- /dev/null +++ b/services/news-engine-console/backend/app/api/__init__.py @@ -0,0 +1 @@ +# API Routers diff --git a/services/news-engine-console/backend/app/api/applications.py b/services/news-engine-console/backend/app/api/applications.py new file mode 100644 index 0000000..7951544 --- /dev/null +++ b/services/news-engine-console/backend/app/api/applications.py @@ -0,0 +1,14 @@ +from fastapi import APIRouter, Depends +from app.core.auth import get_current_active_user, User + +router = APIRouter() + +@router.get("/") +async def get_applications(current_user: User = Depends(get_current_active_user)): + """Get all OAuth2 applications""" + return {"applications": [], "total": 0} + +@router.post("/") +async def create_application(app_data: dict, current_user: User = Depends(get_current_active_user)): + """Create new OAuth2 application""" + return {"message": "Application created"} diff --git a/services/news-engine-console/backend/app/api/keywords.py b/services/news-engine-console/backend/app/api/keywords.py new file mode 100644 index 0000000..902d316 --- /dev/null +++ b/services/news-engine-console/backend/app/api/keywords.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, Depends, HTTPException +from typing import List +from app.core.auth import get_current_active_user, User + +router = APIRouter() + +@router.get("/") +async def get_keywords(current_user: User = Depends(get_current_active_user)): + """Get all keywords""" + # TODO: Implement keyword retrieval from MongoDB + return {"keywords": [], "total": 0} + +@router.post("/") +async def create_keyword( + keyword_data: dict, + current_user: User = Depends(get_current_active_user) +): + """Create new keyword""" + # TODO: Implement keyword creation + return {"message": "Keyword created", "keyword": keyword_data} + +@router.put("/{keyword_id}") +async def update_keyword( + keyword_id: str, + keyword_data: dict, + current_user: User = Depends(get_current_active_user) +): + """Update keyword""" + # TODO: Implement keyword update + return {"message": "Keyword updated", "keyword_id": keyword_id} + +@router.delete("/{keyword_id}") +async def delete_keyword( + keyword_id: str, + current_user: User = Depends(get_current_active_user) +): + """Delete keyword""" + # TODO: Implement keyword deletion + return {"message": "Keyword deleted", "keyword_id": keyword_id} diff --git a/services/news-engine-console/backend/app/api/monitoring.py b/services/news-engine-console/backend/app/api/monitoring.py new file mode 100644 index 0000000..03b2212 --- /dev/null +++ b/services/news-engine-console/backend/app/api/monitoring.py @@ -0,0 +1,14 @@ +from fastapi import APIRouter, Depends +from app.core.auth import get_current_active_user, User + +router = APIRouter() + +@router.get("/system") +async def get_system_status(current_user: User = Depends(get_current_active_user)): + """Get system status""" + return {"status": "healthy", "services": []} + +@router.get("/logs") +async def get_logs(current_user: User = Depends(get_current_active_user)): + """Get pipeline logs""" + return {"logs": []} diff --git a/services/news-engine-console/backend/app/api/pipelines.py b/services/news-engine-console/backend/app/api/pipelines.py new file mode 100644 index 0000000..66e86c7 --- /dev/null +++ b/services/news-engine-console/backend/app/api/pipelines.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from app.core.auth import get_current_active_user, User + +router = APIRouter() + +@router.get("/") +async def get_pipelines(current_user: User = Depends(get_current_active_user)): + """Get all pipelines and their status""" + return {"pipelines": [], "total": 0} + +@router.get("/{pipeline_id}/stats") +async def get_pipeline_stats(pipeline_id: str, current_user: User = Depends(get_current_active_user)): + """Get pipeline statistics""" + return {"pipeline_id": pipeline_id, "stats": {}} + +@router.post("/{pipeline_id}/start") +async def start_pipeline(pipeline_id: str, current_user: User = Depends(get_current_active_user)): + """Start pipeline""" + return {"message": "Pipeline started", "pipeline_id": pipeline_id} + +@router.post("/{pipeline_id}/stop") +async def stop_pipeline(pipeline_id: str, current_user: User = Depends(get_current_active_user)): + """Stop pipeline""" + return {"message": "Pipeline stopped", "pipeline_id": pipeline_id} diff --git a/services/news-engine-console/backend/app/api/users.py b/services/news-engine-console/backend/app/api/users.py new file mode 100644 index 0000000..619b269 --- /dev/null +++ b/services/news-engine-console/backend/app/api/users.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, Depends +from app.core.auth import get_current_active_user, User + +router = APIRouter() + +@router.get("/") +async def get_users(current_user: User = Depends(get_current_active_user)): + """Get all users""" + return {"users": [], "total": 0} + +@router.post("/") +async def create_user(user_data: dict, current_user: User = Depends(get_current_active_user)): + """Create new user""" + return {"message": "User created"} + +@router.get("/me") +async def get_current_user_info(current_user: User = Depends(get_current_active_user)): + """Get current user info""" + return current_user diff --git a/services/news-engine-console/backend/app/core/auth.py b/services/news-engine-console/backend/app/core/auth.py new file mode 100644 index 0000000..17c2466 --- /dev/null +++ b/services/news-engine-console/backend/app/core/auth.py @@ -0,0 +1,75 @@ +from datetime import datetime, timedelta +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from pydantic import BaseModel +from app.core.config import settings + +# Password hashing +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login") + +# Models +class Token(BaseModel): + access_token: str + token_type: str + +class TokenData(BaseModel): + username: Optional[str] = None + +class User(BaseModel): + username: str + email: Optional[str] = None + full_name: Optional[str] = None + disabled: Optional[bool] = None + role: str = "viewer" # admin, editor, viewer + +class UserInDB(User): + hashed_password: str + +# Password functions +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + +# JWT functions +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + +async def get_current_user(token: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + token_data = TokenData(username=username) + except JWTError: + raise credentials_exception + + # TODO: Get user from database + user = User(username=token_data.username, role="admin") + if user is None: + raise credentials_exception + return user + +async def get_current_active_user(current_user: User = Depends(get_current_user)): + if current_user.disabled: + raise HTTPException(status_code=400, detail="Inactive user") + return current_user diff --git a/services/news-engine-console/backend/app/core/config.py b/services/news-engine-console/backend/app/core/config.py new file mode 100644 index 0000000..6ba9c6b --- /dev/null +++ b/services/news-engine-console/backend/app/core/config.py @@ -0,0 +1,32 @@ +from pydantic_settings import BaseSettings +from typing import List + +class Settings(BaseSettings): + # MongoDB + MONGODB_URL: str = "mongodb://localhost:27017" + DB_NAME: str = "ai_writer_db" + + # Redis + REDIS_URL: str = "redis://localhost:6379" + + # JWT + SECRET_KEY: str = "dev-secret-key-change-in-production" + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + + # Service + SERVICE_NAME: str = "news-engine-console" + API_V1_STR: str = "/api/v1" + PORT: int = 8100 + + # CORS + ALLOWED_ORIGINS: List[str] = [ + "http://localhost:3000", + "http://localhost:3100" + ] + + class Config: + env_file = ".env" + case_sensitive = True + +settings = Settings() diff --git a/services/news-engine-console/backend/app/core/database.py b/services/news-engine-console/backend/app/core/database.py new file mode 100644 index 0000000..c9bd006 --- /dev/null +++ b/services/news-engine-console/backend/app/core/database.py @@ -0,0 +1,24 @@ +from motor.motor_asyncio import AsyncIOMotorClient +from app.core.config import settings + +class Database: + client: AsyncIOMotorClient = None + db = None + +db_instance = Database() + +async def connect_to_mongo(): + """Connect to MongoDB""" + db_instance.client = AsyncIOMotorClient(settings.MONGODB_URL) + db_instance.db = db_instance.client[settings.DB_NAME] + print(f"Connected to MongoDB: {settings.DB_NAME}") + +async def close_mongo_connection(): + """Close MongoDB connection""" + if db_instance.client: + db_instance.client.close() + print("Closed MongoDB connection") + +def get_database(): + """Get database instance""" + return db_instance.db diff --git a/services/news-engine-console/backend/main.py b/services/news-engine-console/backend/main.py new file mode 100644 index 0000000..a0d69f4 --- /dev/null +++ b/services/news-engine-console/backend/main.py @@ -0,0 +1,52 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from contextlib import asynccontextmanager +import uvicorn + +from app.core.config import settings +from app.core.database import connect_to_mongo, close_mongo_connection +from app.api import keywords, pipelines, users, applications, monitoring + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + await connect_to_mongo() + yield + # Shutdown + await close_mongo_connection() + +app = FastAPI( + title="News Engine Console API", + description="λ‰΄μŠ€ νŒŒμ΄ν”„λΌμΈ 관리 및 λͺ¨λ‹ˆν„°λ§ μ‹œμŠ€ν…œ", + version="1.0.0", + lifespan=lifespan +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=settings.ALLOWED_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Health check +@app.get("/health") +async def health_check(): + return {"status": "healthy", "service": settings.SERVICE_NAME} + +# Include routers +app.include_router(keywords.router, prefix=f"{settings.API_V1_STR}/keywords", tags=["Keywords"]) +app.include_router(pipelines.router, prefix=f"{settings.API_V1_STR}/pipelines", tags=["Pipelines"]) +app.include_router(users.router, prefix=f"{settings.API_V1_STR}/users", tags=["Users"]) +app.include_router(applications.router, prefix=f"{settings.API_V1_STR}/applications", tags=["Applications"]) +app.include_router(monitoring.router, prefix=f"{settings.API_V1_STR}/monitoring", tags=["Monitoring"]) + +if __name__ == "__main__": + uvicorn.run( + "main:app", + host="0.0.0.0", + port=settings.PORT, + reload=True + ) diff --git a/services/news-engine-console/backend/requirements.txt b/services/news-engine-console/backend/requirements.txt new file mode 100644 index 0000000..ce8ced2 --- /dev/null +++ b/services/news-engine-console/backend/requirements.txt @@ -0,0 +1,11 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +motor==3.3.2 +pydantic==2.5.0 +pydantic-settings==2.1.0 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +python-multipart==0.0.6 +redis==5.0.1 +httpx==0.25.2 +python-dotenv==1.0.0