yeonghoon.kim

  • 게시판
  • 갤러리

Tech Support 플랫폼

김영훈 2026.03.04 10:09 조회 수 : 80

CHT 토큰을 결제 수단으로 쓰는 기술 지원 플랫폼 작업.

프로젝트.

0030-tech-support

구성.

FastAPI
React
PostgreSQL
CHT / Sepolia
Docker Compose
Traefik
Caddy WAF

흐름.

회원가입
이메일 인증
지갑 생성
KRW 200,000 지급
KRW -> CHT 스왑
티켓 생성
CHT 결제
기술 지원 처리

구조.

0030-tech-support/
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── config.py
│   │   ├── database.py
│   │   ├── models.py
│   │   ├── schemas.py
│   │   ├── dependencies.py
│   │   ├── routers/
│   │   │   ├── auth.py
│   │   │   ├── tickets.py
│   │   │   ├── wallet.py
│   │   │   └── admin.py
│   │   └── utils/
│   │       └── jwt.py
│   ├── scripts/
│   │   ├── seed_categories.py
│   │   ├── seed_categories_hierarchy.py
│   │   └── seed_categories_v2.py
│   ├── tests/
│   ├── requirements.txt
│   └── Dockerfile
├── frontend/
│   ├── src/
│   │   ├── App.jsx
│   │   ├── pages/
│   │   ├── components/
│   │   ├── context/
│   │   └── api/
│   ├── nginx.conf
│   ├── package.json
│   └── Dockerfile
├── docker-compose.yml
└── scripts/send_eth.py

주요 API.

POST /api/v1/auth/register
POST /api/v1/auth/login
GET  /api/v1/auth/me

GET  /api/v1/categories
POST /api/v1/tickets
GET  /api/v1/tickets
GET  /api/v1/tickets/{id}

GET  /api/v1/wallet/balance
POST /api/v1/wallet/charge-krw
POST /api/v1/wallet/swap
POST /api/v1/tickets/{id}/payment

GET   /api/v1/admin/dashboard/stats
GET   /api/v1/admin/tickets
PATCH /api/v1/admin/tickets/{id}/status
GET   /api/v1/admin/transactions

이메일 인증 시 지갑 생성.

@router.get("/verify-email")
async def verify_email(token: str, db: Session = Depends(get_db)):
    payload = decode_email_token(token)
    user_id = payload.get("user_id")
    
    user = db.query(User).filter(User.id == user_id).first()
    
    if user.email_verified:
        raise HTTPException(400, "이미 인증됨")
    
    from eth_account import Account
    account = Account.create()
    
    from cryptography.fernet import Fernet
    fernet = Fernet(settings.WALLET_ENCRYPTION_KEY.encode())
    encrypted_key = fernet.encrypt(account.key.hex().encode())
    
    wallet_key = WalletKey(
        user_id=user.id,
        encrypted_private_key=encrypted_key
    )
    db.add(wallet_key)
    
    user.wallet_address = account.address
    user.email_verified = True
    
    wallet = Wallet(
        user_id=user.id,
        krw_balance=200000.0,
        cht_balance=0.0
    )
    db.add(wallet)
    db.commit()
    
    return {"message": "이메일 인증 완료! 지갑이 생성되었습니다."}

환율 테이블.

class ExchangeRate(Base):
    __tablename__ = "exchange_rates"
    
    id = Column(Integer, primary_key=True)
    base_currency = Column(String, default="USD")
    target_currency = Column(String, default="KRW")
    rate = Column(Numeric(precision=10, scale=4))
    updated_at = Column(DateTime, default=datetime.utcnow)

외부 환율 API.

async def fetch_exchange_rate() -> float:
    url = "https://api.exchangerate-api.com/v4/latest/USD"
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        data = response.json()
        return data["rates"]["KRW"]

스왑 API.

@router.post("/swap")
async def swap_currency(
    request: SwapRequest,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    wallet = db.query(Wallet).filter(Wallet.user_id == current_user.id).first()
    
    rate = get_latest_exchange_rate(db)
    
    if request.direction == "krw_to_cht":
        krw_amount = request.amount
        cht_amount = krw_amount / rate.rate
        
        if wallet.krw_balance < krw_amount:
            raise HTTPException(400, "KRW 잔액 부족")
        
        wallet.krw_balance -= krw_amount
        wallet.cht_balance += cht_amount
    
    elif request.direction == "cht_to_krw":
        cht_amount = request.amount
        krw_amount = cht_amount * rate.rate
        
        if wallet.cht_balance < cht_amount:
            raise HTTPException(400, "CHT 잔액 부족")
        
        wallet.cht_balance -= cht_amount
        wallet.krw_balance += krw_amount
    
    db.commit()
    return {"new_krw_balance": wallet.krw_balance, "new_cht_balance": wallet.cht_balance}

티켓 카테고리.

categories = [
    {"name": "DevOps", "rate_per_hour": 50000},
    {"name": "인프라/보안", "rate_per_hour": 60000},
    {"name": "개발 컨설팅", "rate_per_hour": 70000},
    {"name": "모니터링/로깅", "rate_per_hour": 50000},
    {"name": "기타", "rate_per_hour": 0}
]

티켓 생성.

@router.post("/tickets")
async def create_ticket(
    request: TicketCreateRequest,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    category = db.query(Category).filter(Category.id == request.category_id).first()
    
    estimated_cost = category.rate_per_hour * request.estimated_hours
    
    ticket = Ticket(
        user_id=current_user.id,
        category_id=request.category_id,
        title=request.title,
        description=request.description,
        estimated_hours=request.estimated_hours,
        estimated_cost_krw=estimated_cost,
        status="requested"
    )
    db.add(ticket)
    db.commit()
    
    return TicketResponse.from_orm(ticket)

상태.

requested
approved
processing
completed
paid

Docker 실행.

docker compose up --build -d

상태 확인.

docker compose ps

로그.

docker compose logs -f backend

테스트.

pytest backend/tests/ -v --cov=backend/app --cov-report=term-missing

결과.

112 tests passed
Coverage 81%

커밋 기록.

2026-03-05
CHT 기반 가격 체계 전환

2026-03-05
API 응답에 fixed_price_cht 필드 추가

2026-03-05
지갑에 ETH 잔액 추가

2026-03-05
에스크로 방식 복구: 티켓 생성 시 CHT 즉시 차감

2026-03-05
티켓 비용 계산 수정

2026-03-06
Header 컴포넌트 추가

2026-03-06
파스텔톤 배경 제거 및 이모지 삭제

2026-03-06
모든 파일에서 이모지 제거

처음엔 회원가입 시 지갑을 만들었는데 이메일 인증 완료 시점으로 변경.

자원 할당은 인증 이후.

Traefik 라우팅 문제는 restart로 안 잡혀서 force recreate 사용.

docker compose up -d --force-recreate

  • 추천 0

  • 비추천 0
이 게시물을
목록

댓글 0

번호 제목 글쓴이 날짜 조회 수
공지 2025 일본 여행 계획 김영훈 2024.10.10 4118
공지 현금, 저축, 투자, 지출, 예산, 보험 내역(2024-05-30) 김영훈 2024.03.10 3752
317 OpenClaw 복원과 GLM-5.1 전환 김영훈 2026.04.07 145
316 교토 4박 5일 여행 일정: Google Calendar로 관리하는 일정짜기 김영훈 2026.04.05 124
315 7일간의 일본 여행 일정 짜기: 신칸센 + JR 패스 + Google Calendar 활용 프로젝트 김영훈 2026.03.28 128
314 JSON-LD 학습 - 메일과 Calendar 자동 인식 김영훈 2026.03.28 151
313 WAF 운영 6주 분석 김영훈 2026.03.13 199
» Tech Support 플랫폼 김영훈 2026.03.04 80
311 ChloeToken 테스트넷 배포 김영훈 2026.03.02 232
310 Redis 대기열 시스템 김영훈 2026.03.01 212
309 Apache RewriteRule 쿼리스트링 처리 김영훈 2026.02.28 258
308 비트코인 자동매매 봇 대시보드 김영훈 2026.02.21 144
307 ARM Kubernetes 구축 6 - 무중단 업그레이드 김영훈 2026.02.20 208
306 ARM Kubernetes 구축 8 - 실전 보안 RBAC 네트워크 정책 Pod 보안 김영훈 2026.02.20 194
305 ARM Kubernetes 구축 7 - 클러스터 무중단 업그레이드 v1.29→v1.35 김영훈 2026.02.20 252
304 ARM Kubernetes 구축 4 - 외부 접근 환경 VPN MetalLB Ingress 김영훈 2026.02.18 246
303 ARM Kubernetes 구축 5 - 실전 업그레이드 경험기 v1.29→v1.35 김영훈 2026.02.18 238
302 ARM Kubernetes 구축 3 - 모니터링 스택 Prometheus Grafana 김영훈 2026.02.16 179
301 ARM Kubernetes 구축 1 - VNC & KVM 설치 김영훈 2026.02.14 70
300 ARM Kubernetes 구축 2 - VM 생성과 클러스터 설치 김영훈 2026.02.14 138
299 OpenClaw 모델 삭제 사고 김영훈 2026.02.13 114
298 LLM 모델 선정 김영훈 2026.02.12 141
쓰기 태그
 첫 페이지 1 2 3 4 5 6 7 8 9 10 끝 페이지