pve9-repo-fix/FARMQ_ADMIN_INTEGRATION_ANALYSIS.md
Claude 60be9daff4 docs: headscale 자동 등록 개선 계획 문서 추가
추가된 문서:
- SCRIPT_IMPROVEMENT_PLAN.md: 스크립트 개선 계획
- FARMQ_ADMIN_INTEGRATION_ANALYSIS.md: farmq-admin API 분석
- HEADSCALE_AUTO_REGISTER_PLAN.md: 초기 계획

주요 내용:
- Headscale VPN 등록 시 자동 DB 생성
- API 엔드포인트: demo.pharmq.kr, gateway.pharmq.kr

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 09:40:54 +00:00

14 KiB

farmq-admin 통합 분석 및 개선 방안

📅 작성일

2025년 11월 14일


🎯 발견 사항

farmq-admin이 이미 존재하고 실행 중!

프로세스: /srv/headscale-tailscale-replacement/farmq-admin/venv/bin/python app.py
포트: 5001
상태: 실행 중 (Nov 05부터 계속 실행)

중요: farmq-admin은 Flask 기반 웹 애플리케이션으로, farmq.db를 관리하는 API를 이미 제공하고 있습니다!


📊 farmq-admin 구조 분석

1. 실행 정보

위치: /srv/headscale-tailscale-replacement/farmq-admin/
메인: app.py (Flask 애플리케이션)
포트: 5001
DB: farmq.db (SQLAlchemy ORM 사용)

2. 주요 API 엔드포인트

약국 관리 API

POST   /api/pharmacy          # 새 약국 생성 ✨
GET    /api/pharmacy/<id>     # 약국 정보 조회
PUT    /api/pharmacy/<id>     # 약국 정보 수정
DELETE /api/pharmacy/<id>     # 약국 삭제

3. 약국 생성 API 상세 (POST /api/pharmacy)

요청 예시:

POST http://localhost:5001/api/pharmacy
Content-Type: application/json

{
  "pharmacy_name": "행복약국",
  "owner_name": "홍길동",
  "owner_phone": "010-1234-5678",
  "owner_email": "happy@pharmq.kr",
  "phone": "02-1234-5678",
  "address": "서울시 강남구...",
  "api_port": 8082
}

응답 예시:

{
  "success": true,
  "message": "약국 \"행복약국\" (코드: P004) 생성 완료",
  "pharmacy": {
    "id": 4,
    "pharmacy_code": "P004",
    "pharmacy_name": "행복약국",
    "tailscale_ip": null,
    "api_port": 8082,
    "status": "active",
    "owner_name": "홍길동",
    "owner_phone": "010-1234-5678",
    "owner_email": "happy@pharmq.kr"
  }
}

4. 자동 생성 로직 (이미 구현됨!)

# app.py Line 418-433
# pharmacy_code 자동 생성 (P001~P999)
last_pharmacy = farmq_session.query(PharmacyInfo)\
    .filter(PharmacyInfo.pharmacy_code.like('P%'))\
    .order_by(PharmacyInfo.pharmacy_code.desc())\
    .first()

if last_pharmacy and last_pharmacy.pharmacy_code:
    try:
        last_num = int(last_pharmacy.pharmacy_code[1:])
        new_num = last_num + 1
    except:
        new_num = 1
else:
    new_num = 1

pharmacy_code = f"P{new_num:03d}"  # P001, P002, P003...

결과: 마지막 약국 코드를 찾아서 자동으로 +1 증가!


🔄 개선된 통합 방안

기존 계획 vs 새로운 발견

기존 계획 (불필요)

Python 스크립트로 직접 farmq.db INSERT
→ SQL 쿼리 작성
→ 에러 핸들링 직접 구현
→ 검증 로직 직접 구현

새로운 방안 (API 활용)

farmq-admin API 호출
→ 이미 모든 로직 구현됨
→ 검증, 에러 핸들링 완료
→ pharmacy_code 자동 생성

🚀 최종 개선 방안

방법 1: farmq-admin API 활용 (권장 )

장점

  • 이미 구현된 API 활용
  • pharmacy_code 자동 생성
  • 검증 로직 포함
  • 에러 핸들링 완료
  • 유지보수 용이

구현

# headscale-quick-install.sh에서 호출
register_to_farmq_api() {
    print_status "farmq-admin API를 통해 약국 등록 중..."

    # JSON 데이터 생성
    JSON_DATA=$(cat <<EOF
{
  "pharmacy_name": "$PHARMACY_NAME",
  "owner_name": "$OWNER_NAME",
  "owner_phone": "$OWNER_PHONE",
  "owner_email": "$OWNER_EMAIL",
  "phone": "$PHARMACY_PHONE",
  "address": "$PHARMACY_ADDRESS",
  "api_port": 8082
}
EOF
)

    # API 호출
    RESPONSE=$(curl -s -X POST \
        http://localhost:5001/api/pharmacy \
        -H "Content-Type: application/json" \
        -d "$JSON_DATA")

    # 응답 확인
    SUCCESS=$(echo "$RESPONSE" | grep -o '"success"[[:space:]]*:[[:space:]]*true')

    if [ -n "$SUCCESS" ]; then
        # pharmacy_code 추출
        PHARMACY_CODE=$(echo "$RESPONSE" | grep -o '"pharmacy_code"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)

        print_success "약국 등록 완료! 코드: $PHARMACY_CODE"

        # VPN IP 업데이트 (별도 API 호출 필요)
        update_pharmacy_vpn_ip "$PHARMACY_CODE" "$TAILSCALE_IP"

        return 0
    else
        print_error "약국 등록 실패"
        echo "$RESPONSE"
        return 1
    fi
}

VPN IP 업데이트

update_pharmacy_vpn_ip() {
    local PHARMACY_CODE=$1
    local VPN_IP=$2

    # pharmacy_id 조회 필요 (또는 UPDATE API 수정)
    # 현재는 PUT /api/pharmacy/<id>만 있음

    # 임시 방안: 직접 DB 업데이트
    python3 << EOF
import sqlite3
conn = sqlite3.connect('/srv/headscale-tailscale-replacement/farmq-admin/farmq.db')
cursor = conn.cursor()
cursor.execute("""
    UPDATE pharmacies
    SET tailscale_ip = ?
    WHERE pharmacy_code = ?
""", ("$VPN_IP", "$PHARMACY_CODE"))
conn.commit()
conn.close()
print("VPN IP 업데이트 완료: $PHARMACY_CODE → $VPN_IP")
EOF
}

방법 2: 하이브리드 방식 (대안)

register_pharmacy_hybrid() {
    # 1. farmq-admin API로 기본 정보 등록
    PHARMACY_CODE=$(call_farmq_api_create)

    # 2. VPN IP는 직접 업데이트 (API에 없는 필드)
    update_vpn_ip_direct "$PHARMACY_CODE" "$TAILSCALE_IP"

    # 3. gateway.db는 Python 스크립트로 생성
    create_gateway_user "$PHARMACY_CODE"
}

📋 수정된 전체 흐름

새로운 스크립트 흐름

1. OS 감지
2. Tailscale 설치
3. Tailscale 서비스 시작
4. Headscale 등록 (preauth key 사용)
5. VPN IP 할당 받음 (예: 100.64.0.15)
6. 연결 확인
7. ✨ 약국 정보 입력 받기 (대화형)
8. ✨ farmq-admin API 호출 → farmq.db 등록
   - pharmacy_code 자동 생성 (API가 처리)
   - 기본 정보 저장
9. ✨ VPN IP 업데이트 (직접 DB 또는 API 개선)
10. ✨ gateway.db에 사용자 생성 (Python 스크립트)
11. ✨ 로그인 정보 출력
12. 종료 ✅

🛠️ 필요한 개선 사항

farmq-admin API 개선 (선택사항)

1. VPN IP 업데이트 API 추가

# app.py에 추가
@app.route('/api/pharmacy/<pharmacy_code>/vpn-ip', methods=['PUT'])
def api_update_pharmacy_vpn_ip(pharmacy_code):
    """약국 VPN IP 업데이트"""
    try:
        data = request.get_json()
        vpn_ip = data.get('vpn_ip', '').strip()

        if not vpn_ip:
            return jsonify({
                'success': False,
                'error': 'VPN IP는 필수입니다.'
            }), 400

        farmq_session = get_farmq_session()
        try:
            pharmacy = farmq_session.query(PharmacyInfo).filter(
                PharmacyInfo.pharmacy_code == pharmacy_code
            ).first()

            if not pharmacy:
                return jsonify({
                    'success': False,
                    'error': '약국을 찾을 수 없습니다.'
                }), 404

            pharmacy.tailscale_ip = vpn_ip
            farmq_session.commit()

            return jsonify({
                'success': True,
                'message': f'VPN IP 업데이트 완료: {pharmacy_code}{vpn_ip}'
            })

        finally:
            farmq_session.close()

    except Exception as e:
        return jsonify({
            'success': False,
            'error': f'서버 오류: {str(e)}'
        }), 500

사용:

curl -X PUT http://localhost:5001/api/pharmacy/P004/vpn-ip \
  -H "Content-Type: application/json" \
  -d '{"vpn_ip": "100.64.0.15"}'

2. pharmacy_code로 조회 API 추가

@app.route('/api/pharmacy/code/<pharmacy_code>', methods=['GET'])
def api_get_pharmacy_by_code(pharmacy_code):
    """약국 코드로 약국 정보 조회"""
    try:
        farmq_session = get_farmq_session()
        try:
            pharmacy = farmq_session.query(PharmacyInfo).filter(
                PharmacyInfo.pharmacy_code == pharmacy_code
            ).first()

            if not pharmacy:
                return jsonify({
                    'success': False,
                    'error': '약국을 찾을 수 없습니다.'
                }), 404

            return jsonify({
                'success': True,
                'pharmacy': pharmacy.to_dict()
            })

        finally:
            farmq_session.close()

    except Exception as e:
        return jsonify({
            'success': False,
            'error': f'서버 오류: {str(e)}'
        }), 500

📊 비교표

항목 직접 DB 접근 farmq-admin API
구현 난이도 쉬움
pharmacy_code 생성 직접 구현 필요 자동 처리
검증 로직 직접 구현 필요 이미 구현됨
에러 핸들링 직접 구현 필요 이미 구현됨
유지보수 어려움 쉬움
VPN IP 업데이트 직접 가능 API 개선 필요
의존성 SQLite3만 curl 필요

🎯 최종 권장 사항

Phase 1: 최소 변경 (즉시 적용 가능)

# headscale-quick-install.sh 수정

register_pharmacy() {
    print_status "약국 등록 중..."

    # 1. farmq-admin API로 약국 생성
    RESPONSE=$(curl -s -X POST \
        http://localhost:5001/api/pharmacy \
        -H "Content-Type: application/json" \
        -d "{
            \"pharmacy_name\": \"$PHARMACY_NAME\",
            \"owner_name\": \"$OWNER_NAME\",
            \"owner_phone\": \"$OWNER_PHONE\",
            \"owner_email\": \"$OWNER_EMAIL\",
            \"api_port\": 8082
        }")

    # 2. pharmacy_code 추출
    PHARMACY_CODE=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('pharmacy',{}).get('pharmacy_code',''))")

    # 3. VPN IP 업데이트 (직접 DB)
    python3 << EOF
import sqlite3
conn = sqlite3.connect('/srv/headscale-tailscale-replacement/farmq-admin/farmq.db')
cursor = conn.cursor()
cursor.execute("UPDATE pharmacies SET tailscale_ip = ? WHERE pharmacy_code = ?",
               ("$TAILSCALE_IP", "$PHARMACY_CODE"))
conn.commit()
conn.close()
EOF

    # 4. gateway.db 사용자 생성 (별도 스크립트)
    python3 /srv/pharmq-gateway/scripts/create_gateway_user.py \
        --pharmacy-code "$PHARMACY_CODE" \
        --owner "$OWNER_NAME" \
        --phone "$OWNER_PHONE" \
        --email "$OWNER_EMAIL"
}

Phase 2: farmq-admin API 개선 (선택)

app.py에 다음 추가:

  1. PUT /api/pharmacy/<code>/vpn-ip - VPN IP 업데이트
  2. GET /api/pharmacy/code/<code> - 코드로 조회

Phase 3: 완전 통합

gateway.db 생성도 API로 통합 (gateway API 서버에서)


📝 구현 예시 (최소 변경)

1. gateway 사용자 생성 스크립트

파일: /srv/pharmq-gateway/scripts/create_gateway_user.py

#!/usr/bin/env python3
import sqlite3
import sys
import argparse
from datetime import datetime
import secrets
import string
from passlib.hash import pbkdf2_sha256

def generate_password(length=8):
    alphabet = string.ascii_letters + string.digits
    return ''.join(secrets.choice(alphabet) for _ in range(length))

def create_gateway_user(args):
    username = f"{args.pharmacy_code.lower()}_admin"
    password = generate_password(8)
    password_hash = pbkdf2_sha256.hash(password)

    conn = sqlite3.connect(args.gateway_db)
    cursor = conn.cursor()

    try:
        # users 테이블
        cursor.execute("""
            INSERT INTO users (
                username, email, password_hash, name, phone,
                primary_pharmacy_code, role, status,
                failed_login_attempts, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            username,
            args.email,
            password_hash,
            args.owner,
            args.phone,
            args.pharmacy_code,
            'admin',
            'active',
            0,
            datetime.now().isoformat(),
            datetime.now().isoformat()
        ))

        user_id = cursor.lastrowid

        # pharmacy_members 테이블
        cursor.execute("""
            INSERT INTO pharmacy_members (
                pharmacy_code, user_id, position, access_level,
                can_view_sales, can_view_inventory, can_manage_staff,
                is_active, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            args.pharmacy_code, user_id, '대표약사', 'owner',
            True, True, True, True,
            datetime.now().isoformat(),
            datetime.now().isoformat()
        ))

        conn.commit()

        # 결과 출력 (JSON)
        import json
        print(json.dumps({
            'success': True,
            'username': username,
            'password': password,
            'pharmacy_code': args.pharmacy_code
        }))

    except Exception as e:
        import json
        print(json.dumps({
            'success': False,
            'error': str(e)
        }))
        sys.exit(1)
    finally:
        conn.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--pharmacy-code', required=True)
    parser.add_argument('--owner', required=True)
    parser.add_argument('--phone', required=True)
    parser.add_argument('--email', required=True)
    parser.add_argument('--gateway-db',
                        default='/srv/pharmq-gateway/gateway.db')

    args = parser.parse_args()
    create_gateway_user(args)

결론

핵심 발견

farmq-admin이 이미 약국 생성 API를 제공하고 있음!

  • pharmacy_code 자동 생성
  • 검증 로직 완료
  • 에러 핸들링 완료

개선 방안

  1. farmq.db: farmq-admin API 활용 (POST /api/pharmacy)
  2. VPN IP: 직접 DB 업데이트 (또는 API 추가)
  3. gateway.db: Python 스크립트로 생성

장점

  • 기존 시스템 재사용
  • 중복 코드 방지
  • 유지보수 용이
  • 빠른 구현 가능

작성일: 2025년 11월 14일 작성자: Claude Code 버전: farmq-admin Integration Analysis v1.0