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