브라우저 컨트롤러의 ED 모드 기능 추가: ED 모드에 따라 드롭다운 및 선택된 그룹 이름 처리 로직을 개선하였으며, UI 요소의 가시성을 조정하는 기능을 추가하였습니다. 또한, 로그 메시지를 개선하여 디버깅 용이성을 높였습니다. 등록 상품 모드 관련 가시성 규칙을 적용하고, 사용자 데이터베이스 쿼리를 수정하여 사용자 소유의 금지어만 선택하도록 변경하였습니다.

This commit is contained in:
9700X_PC 2025-09-10 16:34:24 +09:00
parent 79e265ed84
commit a9311e3e80
6 changed files with 342 additions and 57 deletions

View File

@ -234,10 +234,12 @@ class BrowserController(QThread):
# self.upload_tab_locator = self.locator_manager.get_locator('BrowserControl', 'upload_tab_locator')
self.save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
self.group_dropdown_locator = self.locator_manager.get_locator('BrowserControl', 'group_dropdown_locator')
self.group_dropdown_for_ed_locator = self.locator_manager.get_locator('BrowserControl', 'group_dropdown_for_ed_locator')
self.group_index_template = self.locator_manager.get_locator('BrowserControl', 'group_index_template')
self.group_options_selector_locator = self.locator_manager.get_locator('BrowserControl', 'group_options_selector_locator')
self.dropdown_openstatus_locator = self.locator_manager.get_locator('BrowserControl', 'dropdown_openstatus_locator')
self.selected_group_name_locator = self.locator_manager.get_locator('BrowserControl', 'selected_group_name_locator')
self.selected_group_name_for_ed_locator = self.locator_manager.get_locator('BrowserControl', 'selected_group_name_for_ed_locator')
self.product_edit_buttons = self.locator_manager.get_locator('BrowserControl', 'product_edit_buttons')
self.product_memo_buttons = self.locator_manager.get_locator('BrowserControl', 'product_memo_buttons')
@ -517,6 +519,8 @@ class BrowserController(QThread):
# self.whale_translator.lens_Search(image_url="https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/6847c740ecbe0d32a37a8570/b637c29c-80eb-43eb-975f-58279bc5fde1.jpg")
# time.sleep(1000000)
try:
self.logger.log('알바생 브라우저를 실행합니다...', level=logging.DEBUG)
@ -526,6 +530,13 @@ class BrowserController(QThread):
thumb_status = self.toggle_states.get('thumb', False)
debug_mode = self.toggle_states.get('debug_mode', False)
id_ed_mode = self.toggle_states.get('ed_mode', False)
if id_ed_mode:
debug_mode = True
else:
debug_mode = False
self.logger.log(f"id_ed_mode: {id_ed_mode}", level=logging.DEBUG)
self.logger.log(f"optionIMGTrans_status: {optionIMGTrans_status}, detail_IMGTrans_status: {detail_IMGTrans_status}, thumb_status: {thumb_status}", level=logging.DEBUG)
# Playwright 시작 및 브라우저 실행
@ -661,7 +672,6 @@ class BrowserController(QThread):
self.browser_ad1_close_error.emit(str(e))
return
id_ed_mode = self.toggle_states.get('ed_mode', False)
if id_ed_mode:
# await self.go_to_registered_product_page()
self.logger.log('등록 상품 관리 페이지로 이동 중...', level=logging.INFO)
@ -1469,9 +1479,17 @@ class BrowserController(QThread):
group_option_locator = self.group_index_template.format(index=group_index+1)
# 드롭다운 열기
await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
if self.toggle_states.get('ed_mode', False):
await self.page.wait_for_selector(self.group_dropdown_for_ed_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_for_ed_locator, timeout=3000, force=True)
self.logger.log("ED모드 드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
else:
await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
# await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
# await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
# 드롭다운 열림 상태 확인
await self.page.wait_for_selector(self.dropdown_openstatus_locator, timeout=3000)
@ -1485,7 +1503,10 @@ class BrowserController(QThread):
self.curr_group_idx = group_index
selected_group_name = await self.page.inner_text(self.selected_group_name_locator)
if self.toggle_states.get('ed_mode', False):
selected_group_name = await self.page.inner_text(self.selected_group_name_for_ed_locator)
else:
selected_group_name = await self.page.inner_text(self.selected_group_name_locator)
self.logger.log(f"선택된 그룹 이름 : [{selected_group_name}]", level=logging.INFO)
# 총 상품 개수 가져오기
@ -1529,9 +1550,18 @@ class BrowserController(QThread):
await asyncio.sleep(1)
# 드롭다운 열기
await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
if self.toggle_states.get('ed_mode', False):
await self.page.wait_for_selector(self.group_dropdown_for_ed_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_for_ed_locator, timeout=3000, force=True)
self.logger.log("ED모드 드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
else:
await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
# await self.page.wait_for_selector(self.group_dropdown_locator, timeout=3000, state='visible')
# await self.page.click(self.group_dropdown_locator, timeout=3000, force=True)
# self.logger.log("드롭다운을 성공적으로 클릭했습니다.", level=logging.DEBUG)
# 드롭다운 열림 상태 확인
await self.page.wait_for_selector(self.dropdown_openstatus_locator, timeout=3000)

View File

@ -2949,6 +2949,7 @@ class MAIN_GUI(QMainWindow):
# 등록상품모드 버튼 추가
if not hasattr(self, 'is_register_product_mode'):
self.is_register_product_mode = False
self.toggle_states['ed_mode'] = self.is_register_product_mode
self.register_mode_button = QPushButton('등록상품모드', self)
self.register_mode_button.setCheckable(True)
self.register_mode_button.clicked.connect(self.on_register_mode_button_clicked)
@ -3013,6 +3014,7 @@ class MAIN_GUI(QMainWindow):
def on_register_mode_button_clicked(self, checked):
"""등록상품 모드 버튼 클릭 핸들러"""
self.is_register_product_mode = bool(checked)
self.toggle_states['ed_mode'] = self.is_register_product_mode
# 버튼 텍스트/상태 동기화
self.register_mode_button.setChecked(self.is_register_product_mode)
@ -3065,6 +3067,23 @@ class MAIN_GUI(QMainWindow):
# 디버깅: 탭 상태 확인
self.debug_tab_status()
def _apply_initial_register_mode_visibility(self):
"""앱 최초 실행 시 기본모드로 가시성 규칙을 1회 강제 적용"""
try:
# 기본모드로 강제 초기화
self.is_register_product_mode = False
self.toggle_states['ed_mode'] = self.is_register_product_mode
if hasattr(self, 'register_mode_button') and self.register_mode_button is not None:
self.register_mode_button.setChecked(False)
self.register_mode_button.setText("등록상품모드")
self.register_mode_button.setStyleSheet("")
# 탭/위젯 가시성 동기화
self.apply_register_mode_tab_visibility()
self.apply_register_mode_widget_visibility()
except Exception as e:
self.logger.log(f"_apply_initial_register_mode_visibility 오류: {e}", level=logging.WARNING)
def apply_register_mode_tab_visibility(self):
"""등록상품 모드일 때 멤버십에 따른 탭 가시성 제어"""
try:
@ -3082,10 +3101,22 @@ class MAIN_GUI(QMainWindow):
show_global = show_name = show_option = show_tag = show_price = show_thumb = show_detail = show_etc = True
if self.is_register_product_mode:
# 등록상품모드에서는 멤버십에 상관없이 필수 탭만 노출
# 보임: 상품명, 옵션, 썸네일 / 숨김: 글로벌, 태그, 가격, 상세페이지, 기타 설정
self.logger.log("등록상품모드 활성화 - 고정 탭 노출 규칙 적용 (상품명/옵션/썸네일만 보임)", level=logging.INFO)
show_global, show_name, show_option, show_tag, show_price, show_thumb, show_detail, show_etc = False, True, True, False, False, True, False, False
# 등록상품모드: 등급별 노출 규칙
# basic → 상품명
# premium→ 상품명, 옵션
# vip/admin → 상품명, 옵션, 썸네일, 가격
level = (self.user_membership_level or 'basic').lower()
show_global = False
show_tag = False
show_detail = False
show_etc = False
if level == 'basic':
show_name, show_option, show_thumb, show_price = True, False, False, False
elif level == 'premium':
show_name, show_option, show_thumb, show_price = True, True, False, False
else: # vip, admin, 기타 미분류는 vip로 취급
show_name, show_option, show_thumb, show_price = True, True, True, True
self.logger.log(f"등록상품모드 활성화 - 등급:{level} → 탭 노출(상품명:{show_name}, 옵션:{show_option}, 썸네일:{show_thumb}, 가격:{show_price})", level=logging.INFO)
else:
self.logger.log("등록상품모드 비활성화 - 모든 탭 표시", level=logging.INFO)
@ -3240,31 +3271,165 @@ class MAIN_GUI(QMainWindow):
return self.toggle_tab_widget.count() # 기본값: 맨 끝
def apply_register_mode_widget_visibility(self):
"""등록상품 모드일 때 각 탭 내 위젯 가시성 제어"""
"""등록상품/기본 모드별로 위젯 맵 기반 가시성 제어"""
try:
# 등록모드일 때: 상품명 탭은 모두 표시, 옵션/썸네일 탭은 화이트리스트만 표시
if self.is_register_product_mode:
# 상품명 탭: 카테추천, 번역타입 토글 숨김
if hasattr(self, 'cat_rec_widget'):
self.cat_rec_widget.setVisible(False)
if hasattr(self, 'title_trans_type_widget'):
self.title_trans_type_widget.setVisible(False)
# 옵션 탭: 옵션명 셔플만 보이게 (다른 위젯들 숨김)
self._hide_option_widgets_except_shuffle()
# 썸네일 탭: 대표썸네일 변경만 보이게 (다른 위젯들 숨김)
self._hide_thumbnail_widgets_except_represent()
option_container_layout = getattr(self, 'option_toggle_container_layout', None)
if option_container_layout is None:
option_container_layout = getattr(self, 'option_toggle_layout', None)
if option_container_layout is not None:
self._apply_whitelist_to_layout(option_container_layout, ['option_shuffle_widget'])
if hasattr(self, 'thumbnail_toggle_layout') and self.thumbnail_toggle_layout is not None:
self._apply_whitelist_to_layout(self.thumbnail_toggle_layout, ['thumb_represent_widget'])
# 가격 탭: 등록모드에서는 '가격범위변경'만 표시
if hasattr(self, 'prices_toggle_layout') and self.prices_toggle_layout is not None:
self._apply_whitelist_to_layout(self.prices_toggle_layout, ['price_range_widget'])
else:
# 등록상품모드가 아닐 때는 모든 위젯 보이게
if hasattr(self, 'cat_rec_widget'):
self.cat_rec_widget.setVisible(True)
if hasattr(self, 'title_trans_type_widget'):
self.title_trans_type_widget.setVisible(True)
self._show_all_option_widgets()
self._show_all_thumbnail_widgets()
# 기본모드: 옵션/썸네일 탭의 모든 위젯 표시
option_container_layout = getattr(self, 'option_toggle_container_layout', None)
if option_container_layout is None:
option_container_layout = getattr(self, 'option_toggle_layout', None)
if option_container_layout is not None:
self._show_all_in_layout(option_container_layout)
if hasattr(self, 'thumbnail_toggle_layout') and self.thumbnail_toggle_layout is not None:
self._show_all_in_layout(self.thumbnail_toggle_layout)
# 기본모드: 가격 탭의 모든 위젯 표시 후, 등록모드 전용만 숨김
if hasattr(self, 'prices_toggle_layout') and self.prices_toggle_layout is not None:
self._show_all_in_layout(self.prices_toggle_layout)
self._hide_widgets_in_layout_by_names(self.prices_toggle_layout, ['price_range_widget'])
# 추가적으로 맵 기반으로 특수 위젯 가시성 보정 (필요한 경우에만)
widget_visibility_map = self._build_widget_visibility_map()
if self.is_register_product_mode:
# 등록모드에서만 표시할 위젯
for wname in widget_visibility_map.get('register', []):
self._set_widget_visible(wname, True)
# 항상 표시할 위젯
for wname in widget_visibility_map.get('both', []):
self._set_widget_visible(wname, True)
# 등록모드에서는 normal 전용 위젯 숨김
for wname in widget_visibility_map.get('normal', []):
self._set_widget_visible(wname, False)
else:
# 기본모드에서만 표시할 위젯 + 항상 표시
for wname in widget_visibility_map.get('normal', []):
self._set_widget_visible(wname, True)
for wname in widget_visibility_map.get('both', []):
self._set_widget_visible(wname, True)
# 기본모드에서는 register 전용 위젯 숨김
for wname in widget_visibility_map.get('register', []):
self._set_widget_visible(wname, False)
except Exception as e:
self.logger.log(f"apply_register_mode_widget_visibility 오류: {e}", level=logging.WARNING)
def _set_widget_visible(self, widget_name, visible):
try:
if hasattr(self, widget_name):
w = getattr(self, widget_name)
if w is not None:
w.setVisible(visible)
except Exception as e:
self.logger.log(f"_set_widget_visible 오류 - {widget_name}: {e}", level=logging.WARNING)
def _apply_whitelist_to_layout(self, layout, allowed_widget_names):
try:
allowed_widgets = []
for name in allowed_widget_names:
if hasattr(self, name):
allowed_widgets.append(getattr(self, name))
for i in range(layout.count()):
item = layout.itemAt(i)
if item is None:
continue
widget = item.widget()
if widget is not None:
widget.setVisible(widget in allowed_widgets)
except Exception as e:
self.logger.log(f"_apply_whitelist_to_layout 오류: {e}", level=logging.WARNING)
def _show_all_in_layout(self, layout):
try:
for i in range(layout.count()):
item = layout.itemAt(i)
if item is None:
continue
widget = item.widget()
if widget is not None:
widget.setVisible(True)
except Exception as e:
self.logger.log(f"_show_all_in_layout 오류: {e}", level=logging.WARNING)
def _hide_widgets_in_layout_by_names(self, layout, widget_names):
try:
targets = []
for name in widget_names:
if hasattr(self, name):
targets.append(getattr(self, name))
for i in range(layout.count()):
item = layout.itemAt(i)
if item is None:
continue
widget = item.widget()
if widget is not None and widget in targets:
widget.setVisible(False)
except Exception as e:
self.logger.log(f"_hide_widgets_in_layout_by_names 오류: {e}", level=logging.WARNING)
def _build_widget_visibility_map(self):
"""모드별 위젯 가시성 맵 구성
- register: 등록모드에서만 표시
- normal: 기본모드에서만 표시
- both: 양쪽 모두 표시
위젯 이름은 실제 self.<name> 속성명과 일치해야 .
"""
try:
register_only = [
# 옵션 탭: 옵션명 셔플만
'option_shuffle_widget',
# 썸네일 탭: 대표썸네일 변경만
'thumb_represent_widget',
]
# 상품명 탭: 모두 표시 → 여기서는 known 위젯들을 both로 둠
product_name_widgets = [
'title_widget',
'title_trans_type_widget',
'cat_rec_widget',
# 필요 시 추가: 상품명 탭 내 위젯 객체명들
]
# 옵션 탭의 기타 위젯들 (등록모드에선 숨김, 기본모드에서는 표시 원하면 normal에 배치)
option_other_widgets = [
'option_name_toggle_widget', 'option_name_shuffle_widget',
'option_price_toggle_widget', 'option_price_shuffle_widget',
'option_img_toggle_widget', 'option_img_trans_widget',
'option_img_remove_bg_widget', 'option_img_inpaint_widget'
]
# 썸네일 탭의 기타 위젯들
thumb_other_widgets = [
'thumb_toggle_widget', 'thumb_trans_widget', 'thumb_remove_bg_widget',
'thumb_inpaint_widget', 'thumb_resize_widget', 'thumb_quality_widget'
]
both = [] + product_name_widgets
normal_only = option_other_widgets + thumb_other_widgets
return {
'register': register_only,
'normal': normal_only,
'both': both,
}
except Exception as e:
self.logger.log(f"_build_widget_visibility_map 오류: {e}", level=logging.WARNING)
return {'register': [], 'normal': [], 'both': []}
def _hide_option_widgets_except_shuffle(self):
"""옵션 탭에서 옵션명 셔플 외 모든 위젯 숨김"""
@ -5002,7 +5167,8 @@ class MAIN_GUI(QMainWindow):
self.price_range_spin_label = QLabel("변경범위(±%)", self)
self.price_range_spin = QSpinBox(self)
self.price_range_spin.setObjectName("price_range_spin")
self.price_range_spin.setRange(0, 10)
self.price_range_spin.setMinimum(-10)
self.price_range_spin.setMaximum(10)
self.price_range_spin.setSuffix("%")
self.price_range_spin.setValue(int(self.settings_manager.get_value("price/range_percent", 0)))
self.price_range_spin.valueChanged.connect(lambda value: self.universal_input_handler(self.price_range_spin, value))
@ -6100,6 +6266,12 @@ class MAIN_GUI(QMainWindow):
toggle_main_widget.setVisible(False)
self.main_layout.addWidget(toggle_main_widget)
# 초기 실행 시 기본모드 가시성 강제 적용
try:
QTimer.singleShot(0, self._apply_initial_register_mode_visibility)
except Exception:
pass
# 프로그레스 레이아웃 생성
self.progress_layout = self.create_progress_layout()
self.main_layout.addLayout(self.progress_layout)
@ -7269,6 +7441,20 @@ class MAIN_GUI(QMainWindow):
"""
활성 세션 목록을 참고해, 작업 중인 그룹이면 (작업중) 표시를 붙이고 선택도 비활성화.
"""
# ed_mode(등록모드)가 켜져 있으면 활성 세션 검사를 건너뛴다
try:
if getattr(self, 'toggle_states', {}).get('ed_mode', False):
self.group_selector.blockSignals(True)
self.group_selector.clear()
for group in group_names_list:
self.group_selector.addItem(group)
self.group_selector.blockSignals(False)
self.group_selector.setEnabled(True)
if hasattr(self, 'group_change_button') and self.group_change_button is not None:
self.group_change_button.setEnabled(True)
return
except Exception:
pass
# 시그널 일시 차단 → ComboBox 업데이트 중 의도하지 않은 선택 변경 방지
self.group_selector.blockSignals(True)
@ -7368,23 +7554,32 @@ class MAIN_GUI(QMainWindow):
QMessageBox.warning(self, "오류", "그룹을 선택해주세요.")
return
result = self.supabase_manager.check_group_conflict(
target_group=self.selected_group.text().strip(),
admin_id=self.admin_id_input.text().strip()
)
has_conflict = result["has_conflict"]
conflicting_sessions = result["conflicting_sessions"]
message = result["message"]
if not self.toggle_states.get('ed_mode', False):
result = self.supabase_manager.check_group_conflict(
target_group=self.selected_group.text().strip(),
admin_id=self.admin_id_input.text().strip()
)
has_conflict = result["has_conflict"]
conflicting_sessions = result["conflicting_sessions"]
message = result["message"]
self.logger.log(f"has_conflict: {has_conflict}", level=logging.DEBUG)
self.logger.log(f"conflicting_sessions: {conflicting_sessions}", level=logging.DEBUG)
self.logger.log(f"message: {message}", level=logging.DEBUG)
self.logger.log(f"has_conflict: {has_conflict}", level=logging.DEBUG)
self.logger.log(f"conflicting_sessions: {conflicting_sessions}", level=logging.DEBUG)
self.logger.log(f"message: {message}", level=logging.DEBUG)
if has_conflict:
QMessageBox.warning(self, "오류", "선택한 그룹이 작업중입니다. 다른 그룹을 선택하세요.")
return
if has_conflict:
QMessageBox.warning(self, "오류", "선택한 그룹이 작업중입니다. 다른 그룹을 선택하세요.")
return
self.supabase_manager.update_session_selected_group(selected_group=self.selected_group.text())
# ed_mode면 그룹명에 [등록모드] 태그 추가하여 세션에 기록
try:
group_name_to_store = self.selected_group.text()
if self.toggle_states.get('ed_mode', False) and "[등록모드]" not in group_name_to_store:
group_name_to_store = f"{group_name_to_store} [등록모드]"
except Exception:
group_name_to_store = self.selected_group.text()
self.supabase_manager.update_session_selected_group(selected_group=group_name_to_store)
self.logger.log(f"그룹세션 업데이트", level=logging.INFO)
self.supabase_manager.update_session_selected_group_userid(admin_id=self.admin_id_input.text().strip())

View File

@ -1250,17 +1250,19 @@ class DBManager:
# 2. 마지막 푸시 이후 변경된 로컬 데이터 가져오기
last_push = self._get_last_push_time()
if last_push is None:
query_cbw = "SELECT word_id, banned_word, grade, status, created_at, updated_at FROM user_banned_words"
# 현재 사용자 소유 금지어만 선택 (공통 금지어 제외)
query_cbw = "SELECT word_id, banned_word, grade, status, created_at, updated_at FROM user_banned_words WHERE user_id = ?"
query_cbw_k = """
SELECT id, banned_word_id, application_status, registration_date, applicant_name,
classification_code, category_description, drawing, bigDrawing, created_at, updated_at
FROM user_banned_words_kipris
"""
else:
# 현재 사용자 소유이면서 마지막 푸시 이후 갱신된 금지어만 선택
query_cbw = """
SELECT word_id, banned_word, grade, status, created_at, updated_at
FROM user_banned_words
WHERE updated_at > ?
WHERE user_id = ? AND updated_at > ?
"""
query_cbw_k = """
SELECT id, banned_word_id, application_status, registration_date, applicant_name,
@ -1269,7 +1271,11 @@ class DBManager:
WHERE updated_at > ?
"""
self.cursor.execute(query_cbw, (last_push,) if last_push else ())
# 사용자 금지어는 반드시 현재 사용자만 조회
if last_push is None:
self.cursor.execute(query_cbw, (self.user_id,))
else:
self.cursor.execute(query_cbw, (self.user_id, last_push))
rows_cbw = self.cursor.fetchall()
self.cursor.execute(query_cbw_k, (last_push,) if last_push else ())
rows_cbw_k = self.cursor.fetchall()
@ -1289,6 +1295,24 @@ class DBManager:
self.logger.log(f"빈 word_id 값 발견, 항목 건너뜀: {row}", level=logging.WARNING)
continue
# UUID 형식 검증 (Supabase의 uuid 타입 컬럼과 맞추기)
try:
uuid.UUID(str(word_id))
except Exception:
self.logger.log(
f"유효하지 않은 word_id(UUID 아님), 건너뜀: {word_id} (banned_word={banned_word})",
level=logging.WARNING,
)
continue
# 타임스탬프 ISO 문자열로 정규화
created_at = row[4]
updated_at = row[5]
if created_at and not isinstance(created_at, str):
created_at = created_at.isoformat() if hasattr(created_at, 'isoformat') else str(created_at)
if updated_at and not isinstance(updated_at, str):
updated_at = updated_at.isoformat() if hasattr(updated_at, 'isoformat') else str(updated_at)
# 중복 검사
is_new = True
if banned_word in existing_words:
@ -1318,13 +1342,13 @@ class DBManager:
# 딕셔너리 구성
cbw_list.append({
"word_id": word_id,
"word_id": str(word_id),
"banned_word": banned_word or "",
"grade": row[2] or "",
"status": row[3] or "",
"created_at": row[4],
"updated_at": row[5],
"user_id": self.user_id
"created_at": created_at,
"updated_at": updated_at,
"user_id": self.user_id
})
# 5. 키프리스 결과 처리 (user_banned_words_kipris)
@ -1337,11 +1361,31 @@ class DBManager:
if result_id is None or result_id == "":
result_id = str(uuid.uuid4())
self.logger.log(f"빈 ID 값 발견, 새 UUID 생성: {result_id}", level=logging.WARNING)
# UUID 형식 정규화/검증
result_id_str = str(result_id)
try:
uuid.UUID(result_id_str)
except Exception:
new_id = str(uuid.uuid4())
self.logger.log(
f"유효하지 않은 결과 ID(UUID 아님) 발견, 새 UUID로 대체: {result_id} -> {new_id}",
level=logging.WARNING,
)
result_id_str = new_id
# banned_word_id 필드 검증
if banned_word_id is None or banned_word_id == "":
self.logger.log(f"빈 banned_word_id 값 발견, 항목 건너뜀: {row}", level=logging.WARNING)
continue
# UUID 형식 검증 (공통 금지어와 같은 비-UUID 값은 제외)
try:
uuid.UUID(str(banned_word_id))
except Exception:
self.logger.log(
f"유효하지 않은 banned_word_id(UUID 아님) 발견, 항목 건너뜀: {banned_word_id}",
level=logging.WARNING,
)
continue
# RLS 정책 준수: 사용자 소유의 banned_word만 Supabase로 업로드
self.cursor.execute("""
@ -1362,8 +1406,8 @@ class DBManager:
updated_at = updated_at.isoformat() if hasattr(updated_at, 'isoformat') else str(updated_at)
cbw_k_list.append({
"id": result_id,
"banned_word_id": banned_word_id,
"id": result_id_str,
"banned_word_id": str(banned_word_id),
"application_status": row[2] or "",
"registration_date": row[3] or "",
"applicant_name": row[4] or "",

View File

@ -6,6 +6,7 @@ from PySide6.QtCore import Qt, Signal, Slot, QDateTime, QSize
from PySide6.QtGui import QColor, QTextCharFormat, QTextCursor, QFont, QPalette
import os
import re
import logging
from datetime import datetime, timedelta
import glob
@ -424,6 +425,17 @@ class LogDialog(QDialog):
"""이메일에서 @ 앞의 id 추출 (한글 포함 가능)"""
return email.split('@')[0]
def _sanitize_filename(self, name: str) -> str:
"""스토리지 키로 안전한 파일명으로 정규화 (한글 등 비 ASCII 문자 대체)"""
# 공백을 밑줄로, 나머지 비허용 문자는 '_'로 치환
name = name.replace(' ', '_')
name = re.sub(r"[^A-Za-z0-9._-]", "_", name)
# 너무 긴 파일명은 잘라냄 (스토리지/경로 제약 대비)
if len(name) > 180:
root, ext = os.path.splitext(name)
name = root[:180 - len(ext)] + ext
return name
def send_logs(self):
try:
@ -441,7 +453,8 @@ class LogDialog(QDialog):
# 로그 파일 수집
for file_path in glob.glob(os.path.join(self.log_paths['logs_dir'], "*.log")):
storage_path = f"{folder}/{date_str}/logs/{os.path.basename(file_path)}"
safe_name = self._sanitize_filename(os.path.basename(file_path))
storage_path = f"{folder}/{date_str}/logs/{safe_name}"
files_to_upload.append((file_path, storage_path))
# 스크린샷 파일 수집
@ -463,7 +476,8 @@ class LogDialog(QDialog):
if target_screenshot_dir and os.path.exists(target_screenshot_dir):
for file_path in glob.glob(os.path.join(target_screenshot_dir, "*.png")):
storage_path = f"{folder}/{date_str}/screenshots/{os.path.basename(file_path)}"
safe_name = self._sanitize_filename(os.path.basename(file_path))
storage_path = f"{folder}/{date_str}/screenshots/{safe_name}"
files_to_upload.append((file_path, storage_path))
if not files_to_upload:

View File

@ -10,6 +10,8 @@
- 이미지처리 모듈 통합
- 썸네일 누끼 순환오류 수정
- 원본이미지가 정리되지 않던 문제 수정
- 로그 업로드시 한글처리 문제 수정
- 공통금지어 관련 ID변환 오류 수정
### 패치 1 기능변경
- 상품편집 시작시 편집설정 재반영 적용