"텔레그램_연동_기능_구현_및_설정_스키마_수정"

This commit is contained in:
2026-02-03 01:02:04 +09:00
parent 3ef50f9b3a
commit 675026d51e
17 changed files with 4386 additions and 10 deletions

View File

@@ -31,7 +31,7 @@ class HoldingSchema(BaseModel):
class Config:
from_attributes = True
@router.get("/summary", response_model=AccountStatusSchema)
@router.get("/balance", response_model=AccountStatusSchema)
async def get_account_summary(db: AsyncSession = Depends(get_db)):
stmt = select(AccountStatus).where(AccountStatus.id == 1)
result = await db.execute(stmt)

View File

@@ -16,7 +16,19 @@ class SettingsSchema(BaseModel):
appKey: str | None = None
appSecret: str | None = None
accountNumber: str | None = None
# Integrations
useTelegram: bool | None = None
telegramToken: str | None = None
telegramChatId: str | None = None
useNaverNews: bool | None = None
naverClientId: str | None = None
naverClientSecret: str | None = None
# Configs
kisApiDelayMs: int | None = None
newsScrapIntervalMin: int | None = None
class Config:
from_attributes = True
@@ -69,7 +81,18 @@ async def update_settings(payload: SettingsSchema, db: AsyncSession = Depends(ge
if payload.accountNumber is not None:
settings.accountNumber = encrypt_str(payload.accountNumber)
# Integrations
if payload.useTelegram is not None: settings.useTelegram = payload.useTelegram
if payload.telegramToken is not None: settings.telegramToken = payload.telegramToken
if payload.telegramChatId is not None: settings.telegramChatId = payload.telegramChatId
if payload.useNaverNews is not None: settings.useNaverNews = payload.useNaverNews
if payload.naverClientId is not None: settings.naverClientId = payload.naverClientId
if payload.naverClientSecret is not None: settings.naverClientSecret = payload.naverClientSecret
# Configs
if payload.kisApiDelayMs is not None: settings.kisApiDelayMs = payload.kisApiDelayMs
if payload.newsScrapIntervalMin is not None: settings.newsScrapIntervalMin = payload.newsScrapIntervalMin
await db.commit()
await db.refresh(settings)

View File

@@ -57,13 +57,13 @@ async def get_trade_history(limit: int = 100, db: AsyncSession = Depends(get_db)
result = await db.execute(stmt)
return result.scalars().all()
@router.get("/reserved-orders", response_model=List[ReservedOrderSchema])
@router.get("/", response_model=List[ReservedOrderSchema])
async def get_reserved_orders(db: AsyncSession = Depends(get_db)):
stmt = select(ReservedOrder)
result = await db.execute(stmt)
return result.scalars().all()
@router.post("/reserved-orders")
@router.post("/")
async def create_reserved_order(req: CreateReservedOrderRequest, db: AsyncSession = Depends(get_db)):
import uuid
new_id = str(uuid.uuid4())
@@ -92,7 +92,7 @@ async def create_reserved_order(req: CreateReservedOrderRequest, db: AsyncSessio
await db.commit()
return {"id": new_id, "status": "MONITORING"}
@router.delete("/reserved-orders/{order_id}")
@router.delete("/{order_id}")
async def delete_reserved_order(order_id: str, db: AsyncSession = Depends(get_db)):
from sqlalchemy import delete
await db.execute(delete(ReservedOrder).where(ReservedOrder.id == order_id))

View File

@@ -38,7 +38,7 @@ class UpdateGroupRequest(BaseModel):
# --- Endpoints ---
@router.get("/", response_model=List[WatchlistGroupSchema])
@router.get("/groups", response_model=List[WatchlistGroupSchema])
async def get_watchlists(db: AsyncSession = Depends(get_db)):
# Load groups with items (Need relationship setup?
# Current models.py WatchlistGroup doesn't have `items` relationship defined explicitly in snippet provided?

Binary file not shown.

View File

@@ -6,6 +6,7 @@ from app.db.models import ApiSettings
from app.core.config import settings
from app.services.kis_auth import kis_auth
from app.services.sync_service import sync_service
from app.services.telegram_service import telegram_service
logger = logging.getLogger(__name__)
@@ -48,12 +49,17 @@ async def run_startup_sequence():
logger.error(f" [FAILED] Authentication Failed: {e}")
logger.error(" Please check your AppKey/Secret and ensure KIS API Server is reachable.")
# Phase 2.5: Telegram (Placeholder)
if settings_obj.useTelegram and settings_obj.telegramToken:
logger.info(">> [Phase 2] Telegram Token Found. Sending Startup Message...")
# TODO: Implement Telegram Sender
# Phase 2.5: Telegram Integration
if settings_obj.useTelegram and settings_obj.telegramToken and settings_obj.telegramChatId:
logger.info(">> [Phase 2.5] Telegram Integration Enabled. Sending Startup Notification...")
msg = "🚀 <b>BatchuKis 배취키스</b> 시스템이 시작되었습니다.\n자동매매 엔진이 가동 중입니다."
await telegram_service.send_message(
settings_obj.telegramToken,
settings_obj.telegramChatId,
msg
)
else:
logger.info(">> [Phase 2] Telegram Disabled or Token missing.")
logger.info(">> [Phase 2.5] Telegram Disabled or Token/ChatID missing.")
# Phase 3: Data Sync (Master Stocks & Account)
logger.info(">> [Phase 3-1] Syncing Account Data...")

View File

@@ -0,0 +1,33 @@
import httpx
import logging
from typing import Optional
logger = logging.getLogger(__name__)
class TelegramService:
def __init__(self):
self.base_url = "https://api.telegram.org/bot"
async def send_message(self, token: str, chat_id: str, text: str) -> bool:
if not token or not chat_id:
logger.warning("Telegram: Token or Chat ID is missing.")
return False
url = f"{self.base_url}{token}/sendMessage"
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": "HTML"
}
try:
async with httpx.AsyncClient() as client:
resp = await client.post(url, json=payload, timeout=10.0)
resp.raise_for_status()
logger.info(f"Telegram: Message sent successfully to {chat_id}")
return True
except Exception as e:
logger.error(f"Telegram: Failed to send message: {e}")
return False
telegram_service = TelegramService()

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.