Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion app/routers/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
Common router dependencies and constants.
"""

from collections.abc import Callable

from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth.constants import AUTH_REQUIRED_MESSAGE
from app.auth.role_hierarchy import has_min_role
from app.auth.web_router import get_current_user_from_session
from app.core.db import get_db
from app.models.trading import User
from app.models.trading import User, UserRole


async def get_authenticated_user(
Expand All @@ -27,3 +30,35 @@ async def get_authenticated_user(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=AUTH_REQUIRED_MESSAGE,
)


def require_min_role_user(min_role: UserRole) -> Callable:
"""Factory that creates a dependency requiring a minimum role.

Returns a dependency that:
1. Gets the authenticated user
2. Checks if the user has at least the required role
3. Returns the user if authorized, raises HTTPException(403) otherwise

Usage:
require_trader_user = require_min_role_user(UserRole.trader)

@router.get("/protected")
async def protected_route(user: User = Depends(require_trader_user)):
...
"""

async def _dependency(request: Request, db: AsyncSession = Depends(get_db)) -> User:
user = await get_authenticated_user(request, db)
if not has_min_role(user.role, min_role):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"이 기능에 접근하려면 '{min_role.value}' 이상의 권한이 필요합니다.",
)
return user

return _dependency


# Pre-configured dependencies for common role requirements
require_trader_user = require_min_role_user(UserRole.trader)
61 changes: 40 additions & 21 deletions app/routers/kis_domestic_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- 보유 주식 조회 (KIS + 수동 잔고 통합)
- AI 분석 실행
- 자동 매수/매도 주문 (Placeholder)

접근 정책: trader, admin만 접근 가능 (viewer 차단)
"""

import logging
Expand All @@ -14,7 +16,7 @@
from app.core.db import get_db
from app.core.templates import templates
from app.models.trading import User
from app.routers.dependencies import get_authenticated_user
from app.routers.dependencies import require_trader_user
from app.services.kis import KISClient
from app.services.merged_portfolio_service import MergedPortfolioService

Expand All @@ -23,22 +25,23 @@


@router.get("/", response_class=HTMLResponse)
async def kis_domestic_trading_dashboard(request: Request):
"""KIS 국내주식 자동 매매 대시보드 페이지"""
user = getattr(request.state, "user", None)
async def kis_domestic_trading_dashboard(
request: Request, current_user: User = Depends(require_trader_user)
):
"""KIS 국내주식 자동 매매 대시보드 페이지 (trader/admin 전용)"""
return templates.TemplateResponse(
"kis_domestic_trading_dashboard.html",
{
"request": request,
"user": user,
"user": current_user,
},
)


@router.get("/api/my-stocks")
async def get_my_domestic_stocks(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_authenticated_user),
current_user: User = Depends(require_trader_user),
):
try:
kis = KISClient()
Expand Down Expand Up @@ -109,10 +112,12 @@ async def get_my_domestic_stocks(


@router.post("/api/analyze-stocks")
async def analyze_my_domestic_stocks():
async def analyze_my_domestic_stocks(current_user: User = Depends(require_trader_user)):
"""보유 국내 주식 AI 분석 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.run_analysis_for_my_domestic_stocks")
async_result = celery_app.send_task(
"kis.run_analysis_for_my_domestic_stocks", args=[current_user.id]
)

return {
"success": True,
Expand All @@ -124,7 +129,9 @@ async def analyze_my_domestic_stocks():


@router.get("/api/analyze-task/{task_id}")
async def get_analyze_task_status(task_id: str):
async def get_analyze_task_status(
task_id: str, current_user: User = Depends(require_trader_user)
):
"""Celery 작업 상태 조회 API"""

result = celery_app.AsyncResult(task_id)
Expand All @@ -149,10 +156,12 @@ async def get_analyze_task_status(task_id: str):


@router.post("/api/buy-orders")
async def execute_buy_orders():
async def execute_buy_orders(current_user: User = Depends(require_trader_user)):
"""보유 국내 주식 자동 매수 주문 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.execute_domestic_buy_orders")
async_result = celery_app.send_task(
"kis.execute_domestic_buy_orders", args=[current_user.id]
)
return {
"success": True,
"message": "매수 주문이 시작되었습니다.",
Expand All @@ -163,10 +172,12 @@ async def execute_buy_orders():


@router.post("/api/sell-orders")
async def execute_sell_orders():
async def execute_sell_orders(current_user: User = Depends(require_trader_user)):
"""보유 국내 주식 자동 매도 주문 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.execute_domestic_sell_orders")
async_result = celery_app.send_task(
"kis.execute_domestic_sell_orders", args=[current_user.id]
)
return {
"success": True,
"message": "매도 주문이 시작되었습니다.",
Expand All @@ -177,9 +188,11 @@ async def execute_sell_orders():


@router.post("/api/automation/per-stock")
async def run_per_stock_automation():
async def run_per_stock_automation(current_user: User = Depends(require_trader_user)):
"""보유 종목별 자동 실행 (분석 -> 매수 -> 매도)"""
task = celery_app.send_task("kis.run_per_domestic_stock_automation")
task = celery_app.send_task(
"kis.run_per_domestic_stock_automation", args=[current_user.id]
)
return {
"success": True,
"message": "종목별 자동 실행이 시작되었습니다.",
Expand All @@ -188,21 +201,27 @@ async def run_per_stock_automation():


@router.post("/api/analyze-stock/{symbol}")
async def analyze_stock(symbol: str):
async def analyze_stock(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 분석 요청"""
task = celery_app.send_task("kis.analyze_domestic_stock_task", args=[symbol])
task = celery_app.send_task(
"kis.analyze_domestic_stock_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 분석 요청 완료", "task_id": task.id}


@router.post("/api/buy-order/{symbol}")
async def buy_order(symbol: str):
async def buy_order(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 매수 요청"""
task = celery_app.send_task("kis.execute_domestic_buy_order_task", args=[symbol])
task = celery_app.send_task(
"kis.execute_domestic_buy_order_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 매수 요청 완료", "task_id": task.id}


@router.post("/api/sell-order/{symbol}")
async def sell_order(symbol: str):
async def sell_order(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 매도 요청"""
task = celery_app.send_task("kis.execute_domestic_sell_order_task", args=[symbol])
task = celery_app.send_task(
"kis.execute_domestic_sell_order_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 매도 요청 완료", "task_id": task.id}
61 changes: 40 additions & 21 deletions app/routers/kis_overseas_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- 보유 주식 조회 (KIS + 수동 잔고 통합)
- AI 분석 실행
- 자동 매수/매도 주문 (Placeholder)

접근 정책: trader, admin만 접근 가능 (viewer 차단)
"""

import logging
Expand All @@ -14,7 +16,7 @@
from app.core.db import get_db
from app.core.templates import templates
from app.models.trading import User
from app.routers.dependencies import get_authenticated_user
from app.routers.dependencies import require_trader_user
from app.services.kis import KISClient
from app.services.merged_portfolio_service import MergedPortfolioService

Expand All @@ -23,14 +25,15 @@


@router.get("/", response_class=HTMLResponse)
async def kis_overseas_trading_dashboard(request: Request):
"""KIS 해외주식 자동 매매 대시보드 페이지"""
user = getattr(request.state, "user", None)
async def kis_overseas_trading_dashboard(
request: Request, current_user: User = Depends(require_trader_user)
):
"""KIS 해외주식 자동 매매 대시보드 페이지 (trader/admin 전용)"""
return templates.TemplateResponse(
"kis_overseas_trading_dashboard.html",
{
"request": request,
"user": user,
"user": current_user,
},
)

Expand Down Expand Up @@ -76,7 +79,7 @@ def _orderable_amount(row: dict) -> float:
@router.get("/api/my-stocks")
async def get_my_overseas_stocks(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_authenticated_user),
current_user: User = Depends(require_trader_user),
):
try:
kis = KISClient()
Expand Down Expand Up @@ -148,10 +151,12 @@ async def get_my_overseas_stocks(


@router.post("/api/analyze-stocks")
async def analyze_my_overseas_stocks():
async def analyze_my_overseas_stocks(current_user: User = Depends(require_trader_user)):
"""보유 해외 주식 AI 분석 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.run_analysis_for_my_overseas_stocks")
async_result = celery_app.send_task(
"kis.run_analysis_for_my_overseas_stocks", args=[current_user.id]
)

return {
"success": True,
Expand All @@ -163,7 +168,9 @@ async def analyze_my_overseas_stocks():


@router.get("/api/analyze-task/{task_id}")
async def get_analyze_task_status(task_id: str):
async def get_analyze_task_status(
task_id: str, current_user: User = Depends(require_trader_user)
):
"""Celery 작업 상태 조회 API"""

result = celery_app.AsyncResult(task_id)
Expand All @@ -188,10 +195,12 @@ async def get_analyze_task_status(task_id: str):


@router.post("/api/buy-orders")
async def execute_buy_orders():
async def execute_buy_orders(current_user: User = Depends(require_trader_user)):
"""보유 해외 주식 자동 매수 주문 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.execute_overseas_buy_orders")
async_result = celery_app.send_task(
"kis.execute_overseas_buy_orders", args=[current_user.id]
)
return {
"success": True,
"message": "매수 주문이 시작되었습니다.",
Expand All @@ -202,10 +211,12 @@ async def execute_buy_orders():


@router.post("/api/sell-orders")
async def execute_sell_orders():
async def execute_sell_orders(current_user: User = Depends(require_trader_user)):
"""보유 해외 주식 자동 매도 주문 실행 (Celery)"""
try:
async_result = celery_app.send_task("kis.execute_overseas_sell_orders")
async_result = celery_app.send_task(
"kis.execute_overseas_sell_orders", args=[current_user.id]
)
return {
"success": True,
"message": "매도 주문이 시작되었습니다.",
Expand All @@ -216,9 +227,11 @@ async def execute_sell_orders():


@router.post("/api/automation/per-stock")
async def run_per_stock_automation():
async def run_per_stock_automation(current_user: User = Depends(require_trader_user)):
"""보유 종목별 자동 실행 (분석 -> 매수 -> 매도)"""
task = celery_app.send_task("kis.run_per_overseas_stock_automation")
task = celery_app.send_task(
"kis.run_per_overseas_stock_automation", args=[current_user.id]
)
return {
"success": True,
"message": "종목별 자동 실행이 시작되었습니다.",
Expand All @@ -227,21 +240,27 @@ async def run_per_stock_automation():


@router.post("/api/analyze-stock/{symbol}")
async def analyze_stock(symbol: str):
async def analyze_stock(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 분석 요청"""
task = celery_app.send_task("kis.analyze_overseas_stock_task", args=[symbol])
task = celery_app.send_task(
"kis.analyze_overseas_stock_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 분석 요청 완료", "task_id": task.id}


@router.post("/api/buy-order/{symbol}")
async def buy_order(symbol: str):
async def buy_order(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 매수 요청"""
task = celery_app.send_task("kis.execute_overseas_buy_order_task", args=[symbol])
task = celery_app.send_task(
"kis.execute_overseas_buy_order_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 매수 요청 완료", "task_id": task.id}


@router.post("/api/sell-order/{symbol}")
async def sell_order(symbol: str):
async def sell_order(symbol: str, current_user: User = Depends(require_trader_user)):
"""단일 종목 매도 요청"""
task = celery_app.send_task("kis.execute_overseas_sell_order_task", args=[symbol])
task = celery_app.send_task(
"kis.execute_overseas_sell_order_task", args=[symbol, current_user.id]
)
return {"success": True, "message": f"{symbol} 매도 요청 완료", "task_id": task.id}
Loading
Loading