#!/usr/bin/env python # -*- coding: utf-8 -*- """ 애니팜 투약지도서 API - 동물약 PDF 생성 API - 포트: 7002 """ from flask import Flask, request, jsonify, send_file, render_template_string from flask_cors import CORS import os import tempfile import uuid import sqlite3 from datetime import datetime from animal_med.renderer_v2 import AnimalMedRendererV2 app = Flask(__name__) CORS(app) # 렌더러 인스턴스 (싱글톤) renderer = AnimalMedRendererV2() # 출력 디렉토리 OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output') os.makedirs(OUTPUT_DIR, exist_ok=True) # ───────────────────────────────────────────────────────────── # SQLite 접속 로그 DB # ───────────────────────────────────────────────────────────── LOGS_DB = os.path.join(os.path.dirname(__file__), 'lecture_logs.db') def init_logs_db(): """접속 로그 DB 초기화""" conn = sqlite3.connect(LOGS_DB) c = conn.cursor() c.execute(''' CREATE TABLE IF NOT EXISTS lecture_views ( id INTEGER PRIMARY KEY AUTOINCREMENT, lecture_id INTEGER NOT NULL, ip_address TEXT, user_agent TEXT, referer TEXT, viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') c.execute('CREATE INDEX IF NOT EXISTS idx_lecture_id ON lecture_views(lecture_id)') c.execute('CREATE INDEX IF NOT EXISTS idx_viewed_at ON lecture_views(viewed_at)') conn.commit() conn.close() def log_lecture_view(lecture_id, ip, user_agent, referer): """강의 조회 로깅""" try: conn = sqlite3.connect(LOGS_DB) c = conn.cursor() c.execute(''' INSERT INTO lecture_views (lecture_id, ip_address, user_agent, referer) VALUES (?, ?, ?, ?) ''', (lecture_id, ip, user_agent[:500] if user_agent else None, referer[:500] if referer else None)) conn.commit() conn.close() except Exception as e: print(f"[LOG ERROR] {e}") # DB 초기화 init_logs_db() @app.route('/health', methods=['GET']) def health(): """헬스체크""" return jsonify({ 'status': 'ok', 'service': 'anipharm-api', 'timestamp': datetime.now().isoformat() }) @app.route('/api/products', methods=['GET']) def list_products(): """등록된 약품 목록 조회""" products = renderer.list_drugs() return jsonify({ 'success': True, 'count': len(products), 'products': products }) @app.route('/api/guide/pdf', methods=['POST']) def generate_pdf(): """ 투약지도서 PDF 생성 Request Body: { "product_ids": ["nexgard_spectra", "heartsaver", ...], "patient_name": "김남곤", "pet_name": "뽀삐", "pet_species": "푸들", "pet_age": "3세", "pharmacy_name": "청춘약국 동물약 전문상담", // optional "pharmacy_tel": "033-481-0384" // optional } Response: PDF 파일 (application/pdf) """ try: data = request.get_json() if not data: return jsonify({'success': False, 'error': 'No JSON data'}), 400 product_ids = data.get('product_ids', []) if not product_ids: return jsonify({'success': False, 'error': 'product_ids required'}), 400 # 파일명 생성 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f"guide_{timestamp}_{uuid.uuid4().hex[:8]}.pdf" output_path = os.path.join(OUTPUT_DIR, filename) # PDF 생성 result = renderer.render_to_pdf( product_ids=product_ids, output_path=output_path, patient_name=data.get('patient_name', '보호자'), pet_name=data.get('pet_name', '반려동물'), pet_species=data.get('pet_species', ''), pet_age=data.get('pet_age', ''), pharmacy_name=data.get('pharmacy_name', '청춘약국 동물약 전문상담'), pharmacy_tel=data.get('pharmacy_tel', '033-481-0384') ) if result['success']: return send_file( output_path, mimetype='application/pdf', as_attachment=True, download_name=f"투약지도서_{timestamp}.pdf" ) else: return jsonify({ 'success': False, 'error': result.get('error', 'Unknown error') }), 500 except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 @app.route('/api/guide/preview', methods=['POST']) def preview_guide(): """ 투약지도서 미리보기 (메타데이터만 반환) Request Body: generate_pdf와 동일 Response: JSON (약품 정보 + 예상 페이지 수) """ try: data = request.get_json() if not data: return jsonify({'success': False, 'error': 'No JSON data'}), 400 product_ids = data.get('product_ids', []) if not product_ids: return jsonify({'success': False, 'error': 'product_ids required'}), 400 # 약품 정보 조회 drugs = [] for pid in product_ids: drug = renderer.get_drug(pid) if drug: drugs.append({ 'id': pid, 'name': drug.get('name'), 'category': drug.get('category'), 'has_image': bool(drug.get('image_url') or drug.get('apc_code')) }) # 페이지 수 계산 (4개/페이지) page_count = (len(drugs) + 3) // 4 return jsonify({ 'success': True, 'drug_count': len(drugs), 'page_count': page_count, 'drugs': drugs }) except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 # ───────────────────────────────────────────────────────────── # 동물약 강의 콘텐츠 라우트 # ───────────────────────────────────────────────────────────── LECTURES_DIR = os.path.join(os.path.dirname(__file__), 'static', 'lectures') os.makedirs(LECTURES_DIR, exist_ok=True) @app.route('/lecture/') def serve_lecture(lecture_id): """동물약 강의 콘텐츠 서빙 (카카오톡 og 태그 포함) + 접속 로깅""" from flask import send_from_directory # 접속 로깅 ip = request.headers.get('X-Forwarded-For', request.remote_addr) user_agent = request.headers.get('User-Agent', '') referer = request.headers.get('Referer', '') log_lecture_view(lecture_id, ip, user_agent, referer) filename = f'lecture_{lecture_id:02d}.html' return send_from_directory(LECTURES_DIR, filename) # ───────────────────────────────────────────────────────────── # 관리자 페이지 - 강의 접속 통계 # ───────────────────────────────────────────────────────────── ADMIN_HTML = ''' 📊 강의 접속 통계 - 애니팜

📊 강의 접속 통계

{{ total_views }}
총 조회수
{{ today_views }}
오늘 조회수
{{ unique_ips }}
순 방문자 (IP)

📈 강의별 조회수

{% for row in lecture_stats %} {% endfor %}
강의조회수최근 조회
Lecture {{ row[0] }} {{ row[1] }} {{ row[2] }}

🕐 최근 접속 로그 (50건)

{% for row in recent_logs %} {% endfor %}
시간강의IPReferer
{{ row[0] }} Lecture {{ row[1] }} {{ row[2] }} {{ row[3] or '-' }}
''' @app.route('/admin/lectures') def admin_lectures(): """강의 접속 통계 관리자 페이지""" conn = sqlite3.connect(LOGS_DB) c = conn.cursor() # 총 조회수 c.execute('SELECT COUNT(*) FROM lecture_views') total_views = c.fetchone()[0] # 오늘 조회수 c.execute("SELECT COUNT(*) FROM lecture_views WHERE date(viewed_at) = date('now', 'localtime')") today_views = c.fetchone()[0] # 순 방문자 (unique IP) c.execute('SELECT COUNT(DISTINCT ip_address) FROM lecture_views') unique_ips = c.fetchone()[0] # 강의별 통계 c.execute(''' SELECT lecture_id, COUNT(*) as cnt, MAX(viewed_at) as last_view FROM lecture_views GROUP BY lecture_id ORDER BY cnt DESC ''') lecture_stats = c.fetchall() # 최근 로그 50건 c.execute(''' SELECT viewed_at, lecture_id, ip_address, referer FROM lecture_views ORDER BY viewed_at DESC LIMIT 50 ''') recent_logs = c.fetchall() conn.close() return render_template_string(ADMIN_HTML, total_views=total_views, today_views=today_views, unique_ips=unique_ips, lecture_stats=lecture_stats, recent_logs=recent_logs ) if __name__ == '__main__': print("=" * 50) print("🐾 애니팜 투약지도서 API") print("=" * 50) print(f"📍 http://localhost:7002") print(f"📋 GET /health - 헬스체크") print(f"📋 GET /api/products - 약품 목록") print(f"📋 POST /api/guide/pdf - PDF 생성") print(f"📋 POST /api/guide/preview - 미리보기") print("=" * 50) app.run(host='0.0.0.0', port=7002, debug=False, threaded=True)