feat: 반려동물 등록 기능 및 확장 마이페이지 추가
- pets 테이블 추가 (이름, 종류, 품종, 사진 등) - 반려동물 CRUD API (/api/pets) - 확장 마이페이지 (/mypage) - 카카오 로그인 기반 - 기존 마이페이지에 퀵 메뉴 추가 (반려동물/쿠폰/구매내역/내정보) - 카카오 로그인 시 세션에 user_id 저장 - 동물약 APC 매핑 가이드 문서 추가
This commit is contained in:
435
backend/app.py
435
backend/app.py
@@ -492,8 +492,8 @@ def normalize_kakao_phone(kakao_phone):
|
||||
return None
|
||||
|
||||
|
||||
def link_kakao_identity(user_id, kakao_id, kakao_data):
|
||||
"""카카오 계정을 customer_identities에 연결"""
|
||||
def link_kakao_identity(user_id, kakao_id, kakao_data, token_data=None):
|
||||
"""카카오 계정을 customer_identities에 연결 (토큰 포함)"""
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
@@ -504,13 +504,33 @@ def link_kakao_identity(user_id, kakao_id, kakao_data):
|
||||
|
||||
is_new_link = cursor.fetchone() is None
|
||||
|
||||
store_data = {k: v for k, v in kakao_data.items() if k != 'raw_data'}
|
||||
|
||||
if is_new_link:
|
||||
# raw_data 제외 (세션 크기 절약)
|
||||
store_data = {k: v for k, v in kakao_data.items() if k != 'raw_data'}
|
||||
cursor.execute("""
|
||||
INSERT INTO customer_identities (user_id, provider, provider_user_id, provider_data)
|
||||
VALUES (?, 'kakao', ?, ?)
|
||||
""", (user_id, kakao_id, json.dumps(store_data, ensure_ascii=False)))
|
||||
INSERT INTO customer_identities
|
||||
(user_id, provider, provider_user_id, provider_data, access_token, refresh_token, token_expires_at)
|
||||
VALUES (?, 'kakao', ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
user_id, kakao_id,
|
||||
json.dumps(store_data, ensure_ascii=False),
|
||||
token_data.get('access_token') if token_data else None,
|
||||
token_data.get('refresh_token') if token_data else None,
|
||||
token_data.get('expires_at') if token_data else None,
|
||||
))
|
||||
elif token_data:
|
||||
# 기존 레코드: 토큰 + 프로필 데이터 업데이트
|
||||
cursor.execute("""
|
||||
UPDATE customer_identities
|
||||
SET access_token = ?, refresh_token = ?, token_expires_at = ?, provider_data = ?
|
||||
WHERE provider = 'kakao' AND provider_user_id = ?
|
||||
""", (
|
||||
token_data.get('access_token'),
|
||||
token_data.get('refresh_token'),
|
||||
token_data.get('expires_at'),
|
||||
json.dumps(store_data, ensure_ascii=False),
|
||||
kakao_id,
|
||||
))
|
||||
|
||||
# 프로필 이미지, 이메일 업데이트
|
||||
updates = []
|
||||
@@ -545,6 +565,52 @@ def find_user_by_kakao_id(kakao_id):
|
||||
return row['user_id'] if row else None
|
||||
|
||||
|
||||
def get_kakao_tokens(user_id):
|
||||
"""사용자의 저장된 카카오 토큰 조회"""
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT provider_user_id, access_token, refresh_token, token_expires_at
|
||||
FROM customer_identities
|
||||
WHERE provider = 'kakao' AND user_id = ?
|
||||
""", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
if row and row['access_token']:
|
||||
return {
|
||||
'kakao_id': row['provider_user_id'],
|
||||
'access_token': row['access_token'],
|
||||
'refresh_token': row['refresh_token'],
|
||||
'token_expires_at': row['token_expires_at'],
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def update_kakao_tokens(kakao_id, token_data):
|
||||
"""카카오 토큰 업데이트 (갱신 후 저장용)"""
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
updates = ["access_token = ?"]
|
||||
params = [token_data['access_token']]
|
||||
|
||||
if 'expires_at' in token_data:
|
||||
updates.append("token_expires_at = ?")
|
||||
params.append(token_data['expires_at'])
|
||||
|
||||
# refresh_token은 갱신 응답에 포함된 경우에만 업데이트
|
||||
if 'refresh_token' in token_data:
|
||||
updates.append("refresh_token = ?")
|
||||
params.append(token_data['refresh_token'])
|
||||
|
||||
params.append(kakao_id)
|
||||
cursor.execute(f"""
|
||||
UPDATE customer_identities
|
||||
SET {', '.join(updates)}
|
||||
WHERE provider = 'kakao' AND provider_user_id = ?
|
||||
""", params)
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 라우트
|
||||
# ============================================================================
|
||||
@@ -807,7 +873,7 @@ def claim_kakao_start():
|
||||
return redirect(auth_url)
|
||||
|
||||
|
||||
def _handle_mypage_kakao_callback(code, kakao_client):
|
||||
def _handle_mypage_kakao_callback(code, kakao_client, redirect_to=None):
|
||||
"""
|
||||
마이페이지 카카오 콜백 처리 - 카카오 연동(머지) + 마이페이지 이동
|
||||
|
||||
@@ -816,6 +882,9 @@ def _handle_mypage_kakao_callback(code, kakao_client):
|
||||
B) 미연결 + 카카오 전화번호로 기존 유저 발견 → 카카오 연동 후 이동
|
||||
C) 미연결 + 기존 유저 없음 → 신규 생성 + 카카오 연동
|
||||
D) 전화번호 없음 → 에러 안내
|
||||
|
||||
Args:
|
||||
redirect_to: 리다이렉트할 URL (None이면 기본 /my-page?phone=xxx)
|
||||
"""
|
||||
success, token_data = kakao_client.get_access_token(code)
|
||||
if not success:
|
||||
@@ -843,9 +912,15 @@ def _handle_mypage_kakao_callback(code, kakao_client):
|
||||
"UPDATE users SET nickname = ? WHERE id = ? AND nickname = '고객'",
|
||||
(kakao_name, existing_user_id))
|
||||
conn.commit()
|
||||
cursor.execute("SELECT phone FROM users WHERE id = ?", (existing_user_id,))
|
||||
cursor.execute("SELECT phone, nickname FROM users WHERE id = ?", (existing_user_id,))
|
||||
row = cursor.fetchone()
|
||||
if row and row['phone']:
|
||||
# 세션에 로그인 정보 저장
|
||||
session['logged_in_user_id'] = existing_user_id
|
||||
session['logged_in_phone'] = row['phone']
|
||||
session['logged_in_name'] = row['nickname'] or kakao_name
|
||||
if redirect_to:
|
||||
return redirect(redirect_to)
|
||||
return redirect(f"/my-page?phone={row['phone']}")
|
||||
|
||||
# 전화번호 없으면 연동 불가
|
||||
@@ -860,7 +935,7 @@ def _handle_mypage_kakao_callback(code, kakao_client):
|
||||
if phone_user:
|
||||
# Case B: 기존 전화번호 유저 → 카카오 연동 (머지)
|
||||
user_id = phone_user['id']
|
||||
link_kakao_identity(user_id, kakao_id, user_info)
|
||||
link_kakao_identity(user_id, kakao_id, user_info, token_data)
|
||||
# "고객" 이름이면 카카오 실명으로 업데이트
|
||||
if phone_user['nickname'] == '고객' and kakao_name and kakao_name != '고객':
|
||||
cursor.execute("UPDATE users SET nickname = ? WHERE id = ?",
|
||||
@@ -870,9 +945,17 @@ def _handle_mypage_kakao_callback(code, kakao_client):
|
||||
else:
|
||||
# Case C: 신규 유저 생성 + 카카오 연동
|
||||
user_id, _ = get_or_create_user(kakao_phone, kakao_name)
|
||||
link_kakao_identity(user_id, kakao_id, user_info)
|
||||
link_kakao_identity(user_id, kakao_id, user_info, token_data)
|
||||
logging.info(f"마이페이지 카카오 신규: user_id={user_id}, kakao_id={kakao_id}")
|
||||
|
||||
# 세션에 로그인 정보 저장 (mypage_v2용)
|
||||
session['logged_in_user_id'] = user_id
|
||||
session['logged_in_phone'] = kakao_phone
|
||||
session['logged_in_name'] = kakao_name
|
||||
|
||||
# 지정된 리다이렉트 URL이 있으면 그쪽으로
|
||||
if redirect_to:
|
||||
return redirect(redirect_to)
|
||||
return redirect(f"/my-page?phone={kakao_phone}")
|
||||
|
||||
|
||||
@@ -904,8 +987,9 @@ def claim_kakao_callback():
|
||||
return render_template('error.html', message="보안 검증에 실패했습니다. 다시 시도해주세요.")
|
||||
|
||||
# 2.5 마이페이지 조회 목적이면 별도 처리
|
||||
if state_data.get('purpose') == 'mypage':
|
||||
return _handle_mypage_kakao_callback(code, get_kakao_client())
|
||||
if state_data.get('purpose') in ('mypage', 'mypage_v2'):
|
||||
redirect_to = '/mypage' if state_data.get('purpose') == 'mypage_v2' else None
|
||||
return _handle_mypage_kakao_callback(code, get_kakao_client(), redirect_to=redirect_to)
|
||||
|
||||
# 3. claim 컨텍스트 복원
|
||||
token_param = state_data.get('t', '')
|
||||
@@ -942,6 +1026,8 @@ def claim_kakao_callback():
|
||||
# 카카오에서 받은 생년월일 조합
|
||||
kakao_birthday = None
|
||||
kakao_bday = user_info.get('birthday') # MMDD 형식
|
||||
print(f"[KAKAO DEBUG] user_info keys: {list(user_info.keys())}")
|
||||
print(f"[KAKAO DEBUG] birthday={kakao_bday}, birthyear={user_info.get('birthyear')}")
|
||||
if kakao_bday and len(kakao_bday) == 4:
|
||||
if user_info.get('birthyear'):
|
||||
kakao_birthday = f"{user_info['birthyear']}-{kakao_bday[:2]}-{kakao_bday[2:]}" # YYYY-MM-DD
|
||||
@@ -963,7 +1049,7 @@ def claim_kakao_callback():
|
||||
else:
|
||||
user_id, is_new = get_or_create_user(kakao_phone, kakao_name, birthday=kakao_birthday)
|
||||
|
||||
link_kakao_identity(user_id, kakao_id, user_info)
|
||||
link_kakao_identity(user_id, kakao_id, user_info, token_data)
|
||||
|
||||
success, msg, new_balance = claim_mileage(user_id, token_info)
|
||||
if not success:
|
||||
@@ -1077,6 +1163,67 @@ def mypage_kakao_start():
|
||||
return redirect(auth_url)
|
||||
|
||||
|
||||
@app.route('/mypage')
|
||||
def mypage_v2():
|
||||
"""확장 마이페이지 (카카오 로그인 필수)"""
|
||||
user_id = session.get('logged_in_user_id')
|
||||
|
||||
if not user_id:
|
||||
# 로그인 필요 - 카카오 로그인으로 리다이렉트
|
||||
csrf_token = secrets.token_hex(16)
|
||||
state_data = {'purpose': 'mypage_v2', 'csrf': csrf_token}
|
||||
kakao_state = base64.urlsafe_b64encode(
|
||||
json.dumps(state_data).encode()
|
||||
).decode()
|
||||
session['kakao_csrf'] = csrf_token
|
||||
return render_template('my_page_login.html', kakao_state=kakao_state, redirect_to='/mypage')
|
||||
|
||||
# 사용자 정보 조회
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, nickname, phone, profile_image_url, mileage_balance, created_at
|
||||
FROM users WHERE id = ?
|
||||
""", (user_id,))
|
||||
user_raw = cursor.fetchone()
|
||||
|
||||
if not user_raw:
|
||||
session.pop('logged_in_user_id', None)
|
||||
return redirect('/mypage')
|
||||
|
||||
user = dict(user_raw)
|
||||
|
||||
# 반려동물 목록 조회
|
||||
cursor.execute("""
|
||||
SELECT id, name, species, breed, gender, photo_url, created_at
|
||||
FROM pets WHERE user_id = ? AND is_active = TRUE
|
||||
ORDER BY created_at DESC
|
||||
""", (user_id,))
|
||||
|
||||
pets = []
|
||||
for row in cursor.fetchall():
|
||||
species_label = '강아지 🐕' if row['species'] == 'dog' else ('고양이 🐈' if row['species'] == 'cat' else '기타')
|
||||
pets.append({
|
||||
'id': row['id'],
|
||||
'name': row['name'],
|
||||
'species': row['species'],
|
||||
'species_label': species_label,
|
||||
'breed': row['breed'],
|
||||
'gender': row['gender'],
|
||||
'photo_url': row['photo_url']
|
||||
})
|
||||
|
||||
# 구매 횟수 (적립 내역 수)
|
||||
cursor.execute("SELECT COUNT(*) FROM mileage_ledger WHERE user_id = ?", (user_id,))
|
||||
purchase_count = cursor.fetchone()[0]
|
||||
|
||||
return render_template('mypage_v2.html',
|
||||
user=user,
|
||||
pets=pets,
|
||||
purchase_count=purchase_count)
|
||||
|
||||
|
||||
@app.route('/my-page')
|
||||
def my_page():
|
||||
"""마이페이지 (전화번호로 조회)"""
|
||||
@@ -4412,6 +4559,266 @@ def api_kims_interaction_check():
|
||||
}), 500
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 반려동물 API
|
||||
# ==============================================================================
|
||||
|
||||
# 견종/묘종 데이터
|
||||
DOG_BREEDS = [
|
||||
'말티즈', '푸들', '포메라니안', '치와와', '시츄', '요크셔테리어',
|
||||
'비숑프리제', '골든리트리버', '래브라도리트리버', '진돗개', '시바견',
|
||||
'웰시코기', '닥스훈트', '비글', '보더콜리', '프렌치불독', '불독',
|
||||
'슈나우저', '사모예드', '허스키', '믹스견', '기타'
|
||||
]
|
||||
|
||||
CAT_BREEDS = [
|
||||
'코리안숏헤어', '페르시안', '러시안블루', '샴', '먼치킨', '랙돌',
|
||||
'브리티쉬숏헤어', '아메리칸숏헤어', '스코티쉬폴드', '노르웨이숲',
|
||||
'메인쿤', '뱅갈', '아비시니안', '터키쉬앙고라', '믹스묘', '기타'
|
||||
]
|
||||
|
||||
@app.route('/api/pets', methods=['GET'])
|
||||
def get_pets():
|
||||
"""사용자의 반려동물 목록 조회"""
|
||||
# 세션에서 로그인 유저 확인
|
||||
user_id = session.get('logged_in_user_id')
|
||||
if not user_id:
|
||||
return jsonify({'success': False, 'error': '로그인이 필요합니다.'}), 401
|
||||
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT id, name, species, breed, gender, birth_date,
|
||||
age_months, weight, photo_url, notes, created_at
|
||||
FROM pets
|
||||
WHERE user_id = ? AND is_active = TRUE
|
||||
ORDER BY created_at DESC
|
||||
""", (user_id,))
|
||||
|
||||
pets = []
|
||||
for row in cursor.fetchall():
|
||||
pets.append({
|
||||
'id': row['id'],
|
||||
'name': row['name'],
|
||||
'species': row['species'],
|
||||
'species_label': '강아지 🐕' if row['species'] == 'dog' else ('고양이 🐈' if row['species'] == 'cat' else '기타'),
|
||||
'breed': row['breed'],
|
||||
'gender': row['gender'],
|
||||
'birth_date': row['birth_date'],
|
||||
'age_months': row['age_months'],
|
||||
'weight': float(row['weight']) if row['weight'] else None,
|
||||
'photo_url': row['photo_url'],
|
||||
'notes': row['notes'],
|
||||
'created_at': utc_to_kst_str(row['created_at'])
|
||||
})
|
||||
|
||||
return jsonify({'success': True, 'pets': pets, 'count': len(pets)})
|
||||
except Exception as e:
|
||||
logging.error(f"반려동물 조회 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/pets', methods=['POST'])
|
||||
def create_pet():
|
||||
"""반려동물 등록"""
|
||||
user_id = session.get('logged_in_user_id')
|
||||
if not user_id:
|
||||
return jsonify({'success': False, 'error': '로그인이 필요합니다.'}), 401
|
||||
|
||||
try:
|
||||
data = request.get_json()
|
||||
name = data.get('name', '').strip()
|
||||
species = data.get('species', '').strip() # dog, cat, other
|
||||
breed = data.get('breed', '').strip()
|
||||
gender = data.get('gender') # male, female, unknown
|
||||
|
||||
if not name:
|
||||
return jsonify({'success': False, 'error': '이름을 입력해주세요.'}), 400
|
||||
if species not in ['dog', 'cat', 'other']:
|
||||
return jsonify({'success': False, 'error': '종류를 선택해주세요.'}), 400
|
||||
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO pets (user_id, name, species, breed, gender)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (user_id, name, species, breed, gender))
|
||||
|
||||
pet_id = cursor.lastrowid
|
||||
conn.commit()
|
||||
|
||||
logging.info(f"반려동물 등록: user_id={user_id}, pet_id={pet_id}, name={name}, species={species}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'pet_id': pet_id,
|
||||
'message': f'{name}이(가) 등록되었습니다!'
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"반려동물 등록 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/pets/<int:pet_id>', methods=['PUT'])
|
||||
def update_pet(pet_id):
|
||||
"""반려동물 정보 수정"""
|
||||
user_id = session.get('logged_in_user_id')
|
||||
if not user_id:
|
||||
return jsonify({'success': False, 'error': '로그인이 필요합니다.'}), 401
|
||||
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 소유권 확인
|
||||
cursor.execute("SELECT id FROM pets WHERE id = ? AND user_id = ?", (pet_id, user_id))
|
||||
if not cursor.fetchone():
|
||||
return jsonify({'success': False, 'error': '반려동물을 찾을 수 없습니다.'}), 404
|
||||
|
||||
# 업데이트 필드 구성
|
||||
updates = []
|
||||
params = []
|
||||
|
||||
if 'name' in data:
|
||||
updates.append("name = ?")
|
||||
params.append(data['name'].strip())
|
||||
if 'species' in data:
|
||||
updates.append("species = ?")
|
||||
params.append(data['species'])
|
||||
if 'breed' in data:
|
||||
updates.append("breed = ?")
|
||||
params.append(data['breed'])
|
||||
if 'gender' in data:
|
||||
updates.append("gender = ?")
|
||||
params.append(data['gender'])
|
||||
if 'birth_date' in data:
|
||||
updates.append("birth_date = ?")
|
||||
params.append(data['birth_date'])
|
||||
if 'age_months' in data:
|
||||
updates.append("age_months = ?")
|
||||
params.append(data['age_months'])
|
||||
if 'weight' in data:
|
||||
updates.append("weight = ?")
|
||||
params.append(data['weight'])
|
||||
if 'notes' in data:
|
||||
updates.append("notes = ?")
|
||||
params.append(data['notes'])
|
||||
|
||||
if updates:
|
||||
updates.append("updated_at = CURRENT_TIMESTAMP")
|
||||
params.append(pet_id)
|
||||
|
||||
cursor.execute(f"""
|
||||
UPDATE pets SET {', '.join(updates)} WHERE id = ?
|
||||
""", params)
|
||||
conn.commit()
|
||||
|
||||
return jsonify({'success': True, 'message': '수정되었습니다.'})
|
||||
except Exception as e:
|
||||
logging.error(f"반려동물 수정 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/pets/<int:pet_id>', methods=['DELETE'])
|
||||
def delete_pet(pet_id):
|
||||
"""반려동물 삭제 (soft delete)"""
|
||||
user_id = session.get('logged_in_user_id')
|
||||
if not user_id:
|
||||
return jsonify({'success': False, 'error': '로그인이 필요합니다.'}), 401
|
||||
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 소유권 확인 및 삭제
|
||||
cursor.execute("""
|
||||
UPDATE pets SET is_active = FALSE, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
""", (pet_id, user_id))
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
return jsonify({'success': False, 'error': '반려동물을 찾을 수 없습니다.'}), 404
|
||||
|
||||
conn.commit()
|
||||
return jsonify({'success': True, 'message': '삭제되었습니다.'})
|
||||
except Exception as e:
|
||||
logging.error(f"반려동물 삭제 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/pets/<int:pet_id>/photo', methods=['POST'])
|
||||
def upload_pet_photo(pet_id):
|
||||
"""반려동물 사진 업로드"""
|
||||
user_id = session.get('logged_in_user_id')
|
||||
if not user_id:
|
||||
return jsonify({'success': False, 'error': '로그인이 필요합니다.'}), 401
|
||||
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 소유권 확인
|
||||
cursor.execute("SELECT id, name FROM pets WHERE id = ? AND user_id = ?", (pet_id, user_id))
|
||||
pet = cursor.fetchone()
|
||||
if not pet:
|
||||
return jsonify({'success': False, 'error': '반려동물을 찾을 수 없습니다.'}), 404
|
||||
|
||||
if 'photo' not in request.files:
|
||||
return jsonify({'success': False, 'error': '사진 파일이 없습니다.'}), 400
|
||||
|
||||
file = request.files['photo']
|
||||
if file.filename == '':
|
||||
return jsonify({'success': False, 'error': '파일을 선택해주세요.'}), 400
|
||||
|
||||
# 파일 확장자 체크
|
||||
allowed = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
ext = file.filename.rsplit('.', 1)[-1].lower() if '.' in file.filename else ''
|
||||
if ext not in allowed:
|
||||
return jsonify({'success': False, 'error': '지원하지 않는 이미지 형식입니다.'}), 400
|
||||
|
||||
# 파일 저장
|
||||
import uuid
|
||||
filename = f"pet_{pet_id}_{uuid.uuid4().hex[:8]}.{ext}"
|
||||
upload_dir = Path(app.root_path) / 'static' / 'uploads' / 'pets'
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
filepath = upload_dir / filename
|
||||
file.save(str(filepath))
|
||||
|
||||
# DB 업데이트
|
||||
photo_url = f"/static/uploads/pets/{filename}"
|
||||
cursor.execute("""
|
||||
UPDATE pets SET photo_url = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
""", (photo_url, pet_id))
|
||||
conn.commit()
|
||||
|
||||
logging.info(f"반려동물 사진 업로드: pet_id={pet_id}, filename={filename}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'photo_url': photo_url,
|
||||
'message': f'{pet["name"]} 사진이 등록되었습니다!'
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"반려동물 사진 업로드 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/pets/breeds/<species>')
|
||||
def get_breeds(species):
|
||||
"""종류별 품종 목록 조회"""
|
||||
if species == 'dog':
|
||||
return jsonify({'success': True, 'breeds': DOG_BREEDS})
|
||||
elif species == 'cat':
|
||||
return jsonify({'success': True, 'breeds': CAT_BREEDS})
|
||||
else:
|
||||
return jsonify({'success': True, 'breeds': ['기타']})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
|
||||
|
||||
Reference in New Issue
Block a user