Files
tradeing/backend/app/routes/auth_route.py
T
chpark d16456cb92 사용자별 격리 시스템 + 사용자 관리 + 라이브 PnL%
# 사용자별 격리
- JWT 토큰에 uid 추가 (auth.get_uid 헬퍼)
- PostgreSQL — exchange_credentials/automation_config/trades/signal_events 에 user_id BIGINT
- SQLite user_settings 테이블 신설 (글로벌 settings 는 옛 호환)
- 모든 DB 함수 시그니처에 user_id 인자 추가 — 다른 사용자 데이터 절대 접근 불가
- alert_state — 모든 dict key 가 (user_id, ...) tuple 로 계층화
- core_logic alert_loop — 활성 사용자 순회 + 각자 settings/symbol/텔레그램 적용
- ensure_user_defaults() / ensure_user_automation() — 첫 사용 시 자동 시드

# 사용자 관리 (admin only)
- users_db: delete_user / admin_reset_password / set_role
- /api/users POST DELETE PUT password PUT role (본인 강등 / 마지막 admin 보호)
- /admin/users 페이지 — 등록/삭제/role 토글/비번 reset 모달
- 사이드바 adminOnly 필터 — admin role 만 메뉴 노출

# 대시보드 개선
- 모바일 / 범례 토글 (모바일 60 캔들, 데스크톱 200)
- 트레이드 이력: open 트레이드 실시간 PnL% (Binance ticker 호출 + 방향별 계산)
- 메트릭 카드 분리 (실거래 vs 실시간 open)

# 안정성
- api.ts: error.detail array/object 안전 처리 ([object Object] 방지)
- Chart.tsx: Plotly yaxis title 객체 형태 + 모바일 height 동적 조정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:14:23 +09:00

55 lines
1.7 KiB
Python

from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
import users_db
from ..auth import create_token, require_user
router = APIRouter()
class LoginIn(BaseModel):
username: str
password: str
class LoginOut(BaseModel):
access_token: str
token_type: str = "bearer"
user: dict
@router.post("/login", response_model=LoginOut)
def login(body: LoginIn):
user = users_db.authenticate(body.username.strip(), body.password)
if not user:
raise HTTPException(status_code=401, detail="아이디 또는 비밀번호가 올바르지 않습니다.")
token = create_token(user["id"], user["username"], user.get("role", "user"))
# datetime 직렬화 위해 string 변환
user_safe = {
"id": user.get("id"),
"username": user.get("username"),
"role": user.get("role"),
"created_at": str(user.get("created_at")) if user.get("created_at") else None,
"last_login_at": str(user.get("last_login_at")) if user.get("last_login_at") else None,
}
return {"access_token": token, "user": user_safe}
@router.get("/me")
def me(payload: dict = Depends(require_user)):
return {"username": payload.get("sub"), "role": payload.get("role")}
class ChangePasswordIn(BaseModel):
old_password: str
new_password: str
@router.put("/password")
def change_password(body: ChangePasswordIn, payload: dict = Depends(require_user)):
username = payload.get("sub")
if len(body.new_password) < 6:
raise HTTPException(status_code=400, detail="새 비밀번호는 6자 이상")
if not users_db.change_password(username, body.old_password, body.new_password):
raise HTTPException(status_code=400, detail="현재 비밀번호 불일치")
return {"ok": True}