feat: 반려동물 등록 기능 및 확장 마이페이지 추가
- pets 테이블 추가 (이름, 종류, 품종, 사진 등) - 반려동물 CRUD API (/api/pets) - 확장 마이페이지 (/mypage) - 카카오 로그인 기반 - 기존 마이페이지에 퀵 메뉴 추가 (반려동물/쿠폰/구매내역/내정보) - 카카오 로그인 시 세션에 user_id 저장 - 동물약 APC 매핑 가이드 문서 추가
This commit is contained in:
@@ -154,11 +154,46 @@ class DatabaseManager:
|
||||
return self.engines[database]
|
||||
|
||||
def get_session(self, database='PM_BASE'):
|
||||
"""특정 데이터베이스 세션 반환"""
|
||||
"""특정 데이터베이스 세션 반환 (자동 복구 포함)"""
|
||||
if database not in self.sessions:
|
||||
engine = self.get_engine(database)
|
||||
Session = sessionmaker(bind=engine)
|
||||
self.sessions[database] = Session()
|
||||
else:
|
||||
# 🔥 기존 세션 상태 체크 및 자동 복구
|
||||
session = self.sessions[database]
|
||||
try:
|
||||
# 세션이 유효한지 간단한 쿼리로 테스트
|
||||
session.execute(text("SELECT 1"))
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
# 연결 끊김 또는 트랜잭션 에러 감지
|
||||
if any(keyword in error_msg for keyword in [
|
||||
'invalid transaction', 'rollback', 'connection',
|
||||
'closed', 'lost', 'timeout', 'network', 'disconnect'
|
||||
]):
|
||||
print(f"[DB Manager] {database} 세션 복구 시도: {e}")
|
||||
try:
|
||||
session.rollback()
|
||||
print(f"[DB Manager] {database} 롤백 성공, 세션 재사용")
|
||||
except Exception as rollback_err:
|
||||
print(f"[DB Manager] {database} 롤백 실패, 세션 재생성: {rollback_err}")
|
||||
try:
|
||||
session.close()
|
||||
except:
|
||||
pass
|
||||
del self.sessions[database]
|
||||
# 새 세션 생성
|
||||
engine = self.get_engine(database)
|
||||
Session = sessionmaker(bind=engine)
|
||||
self.sessions[database] = Session()
|
||||
print(f"[DB Manager] {database} 새 세션 생성 완료")
|
||||
else:
|
||||
# 다른 종류의 에러면 롤백만 시도
|
||||
try:
|
||||
session.rollback()
|
||||
except:
|
||||
pass
|
||||
return self.sessions[database]
|
||||
|
||||
def rollback_session(self, database='PM_BASE'):
|
||||
@@ -237,7 +272,13 @@ class DatabaseManager:
|
||||
self.init_sqlite_schema()
|
||||
self.sqlite_conn = old_conn
|
||||
print(f"[DB Manager] SQLite 신규 DB 생성 완료: {self.sqlite_db_path}")
|
||||
|
||||
else:
|
||||
# 기존 DB: 마이그레이션 실행
|
||||
old_conn = self.sqlite_conn
|
||||
self.sqlite_conn = conn
|
||||
self._migrate_sqlite()
|
||||
self.sqlite_conn = old_conn
|
||||
|
||||
return conn
|
||||
|
||||
def init_sqlite_schema(self):
|
||||
@@ -319,6 +360,43 @@ class DatabaseManager:
|
||||
self.sqlite_conn.commit()
|
||||
print("[DB Manager] SQLite 마이그레이션: ai_recommendations 테이블 생성")
|
||||
|
||||
# customer_identities 토큰 저장 컬럼 추가
|
||||
cursor.execute("PRAGMA table_info(customer_identities)")
|
||||
ci_columns = [row[1] for row in cursor.fetchall()]
|
||||
if 'access_token' not in ci_columns:
|
||||
cursor.execute("ALTER TABLE customer_identities ADD COLUMN access_token TEXT")
|
||||
cursor.execute("ALTER TABLE customer_identities ADD COLUMN refresh_token TEXT")
|
||||
cursor.execute("ALTER TABLE customer_identities ADD COLUMN token_expires_at DATETIME")
|
||||
self.sqlite_conn.commit()
|
||||
print("[DB Manager] SQLite 마이그레이션: customer_identities 토큰 컬럼 추가")
|
||||
|
||||
# pets 테이블 생성 (반려동물)
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='pets'")
|
||||
if not cursor.fetchone():
|
||||
cursor.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS pets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
species VARCHAR(20) NOT NULL,
|
||||
breed VARCHAR(50),
|
||||
gender VARCHAR(10),
|
||||
birth_date DATE,
|
||||
age_months INTEGER,
|
||||
weight DECIMAL(5,2),
|
||||
photo_url TEXT,
|
||||
notes TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pets_user ON pets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pets_species ON pets(species);
|
||||
""")
|
||||
self.sqlite_conn.commit()
|
||||
print("[DB Manager] SQLite 마이그레이션: pets 테이블 생성")
|
||||
|
||||
def test_connection(self, database='PM_BASE'):
|
||||
"""연결 테스트"""
|
||||
try:
|
||||
|
||||
@@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS customer_identities (
|
||||
provider VARCHAR(20) NOT NULL,
|
||||
provider_user_id VARCHAR(100) NOT NULL,
|
||||
provider_data TEXT,
|
||||
access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
token_expires_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
UNIQUE(provider, provider_user_id)
|
||||
@@ -120,3 +123,25 @@ CREATE TABLE IF NOT EXISTS ai_recommendations (
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rec_user_status ON ai_recommendations(user_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_rec_expires ON ai_recommendations(expires_at);
|
||||
|
||||
-- 8. 반려동물 테이블
|
||||
CREATE TABLE IF NOT EXISTS pets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name VARCHAR(50) NOT NULL, -- 이름 (예: 뽀삐, 나비)
|
||||
species VARCHAR(20) NOT NULL, -- 종류: dog, cat, other
|
||||
breed VARCHAR(50), -- 품종 (말티즈, 페르시안 등)
|
||||
gender VARCHAR(10), -- male, female, unknown
|
||||
birth_date DATE, -- 생년월일 (나중에 사용)
|
||||
age_months INTEGER, -- 월령 (나중에 사용)
|
||||
weight DECIMAL(5,2), -- 체중 kg (나중에 사용)
|
||||
photo_url TEXT, -- 사진 URL
|
||||
notes TEXT, -- 특이사항/메모
|
||||
is_active BOOLEAN DEFAULT TRUE, -- 활성 상태
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pets_user ON pets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pets_species ON pets(species);
|
||||
|
||||
Reference in New Issue
Block a user