추가된 문서:
- 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>
539 lines
14 KiB
Markdown
539 lines
14 KiB
Markdown
# farmq-admin 통합 분석 및 개선 방안
|
|
|
|
## 📅 작성일
|
|
**2025년 11월 14일**
|
|
|
|
---
|
|
|
|
## 🎯 발견 사항
|
|
|
|
### farmq-admin이 이미 존재하고 실행 중!
|
|
|
|
```bash
|
|
프로세스: /srv/headscale-tailscale-replacement/farmq-admin/venv/bin/python app.py
|
|
포트: 5001
|
|
상태: 실행 중 (Nov 05부터 계속 실행)
|
|
```
|
|
|
|
**중요:** farmq-admin은 Flask 기반 웹 애플리케이션으로, **farmq.db를 관리하는 API를 이미 제공**하고 있습니다!
|
|
|
|
---
|
|
|
|
## 📊 farmq-admin 구조 분석
|
|
|
|
### 1. 실행 정보
|
|
```bash
|
|
위치: /srv/headscale-tailscale-replacement/farmq-admin/
|
|
메인: app.py (Flask 애플리케이션)
|
|
포트: 5001
|
|
DB: farmq.db (SQLAlchemy ORM 사용)
|
|
```
|
|
|
|
### 2. 주요 API 엔드포인트
|
|
|
|
#### 약국 관리 API
|
|
```python
|
|
POST /api/pharmacy # 새 약국 생성 ✨
|
|
GET /api/pharmacy/<id> # 약국 정보 조회
|
|
PUT /api/pharmacy/<id> # 약국 정보 수정
|
|
DELETE /api/pharmacy/<id> # 약국 삭제
|
|
```
|
|
|
|
### 3. 약국 생성 API 상세 (`POST /api/pharmacy`)
|
|
|
|
**요청 예시:**
|
|
```json
|
|
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
|
|
}
|
|
```
|
|
|
|
**응답 예시:**
|
|
```json
|
|
{
|
|
"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. 자동 생성 로직 (이미 구현됨!)
|
|
|
|
```python
|
|
# 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 자동 생성
|
|
- ✅ 검증 로직 포함
|
|
- ✅ 에러 핸들링 완료
|
|
- ✅ 유지보수 용이
|
|
|
|
#### 구현
|
|
```bash
|
|
# 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 업데이트
|
|
```bash
|
|
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: 하이브리드 방식 (대안)
|
|
|
|
```bash
|
|
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 추가
|
|
```python
|
|
# 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
|
|
```
|
|
|
|
**사용:**
|
|
```bash
|
|
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 추가
|
|
```python
|
|
@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: 최소 변경 (즉시 적용 가능)
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
```python
|
|
#!/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
|