추가된 문서:
- 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>
14 KiB
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에 다음 추가:
PUT /api/pharmacy/<code>/vpn-ip- VPN IP 업데이트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 자동 생성 ✅
- 검증 로직 완료 ✅
- 에러 핸들링 완료 ✅
개선 방안
- farmq.db: farmq-admin API 활용 (POST /api/pharmacy)
- VPN IP: 직접 DB 업데이트 (또는 API 추가)
- gateway.db: Python 스크립트로 생성
장점
- ✅ 기존 시스템 재사용
- ✅ 중복 코드 방지
- ✅ 유지보수 용이
- ✅ 빠른 구현 가능
작성일: 2025년 11월 14일 작성자: Claude Code 버전: farmq-admin Integration Analysis v1.0