from abc import ABC, abstractmethod from typing import Optional, Tuple from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import supplier_users, supplier_user_tokens from common.enums import ErrorType from common.logger import LOG from common.utils.gtime import GTime # 채팅(협상) 로그인 계정 CRUD. supplier.supplier_users 에는 company_id 가 없으므로 # 회사 스코프(협력사 소유권) 확인은 호출측(서비스)이 마친 뒤 supplier_id 로만 접근한다. class ISupplierUserCRUD(ABC): @abstractmethod async def get_by_supplier(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, Optional[supplier_users]]: pass @abstractmethod async def login_id_exists(self, cdb: AsyncSession, login_id: str) -> Tuple[ErrorType, bool]: pass @abstractmethod async def account_map(self, cdb: AsyncSession, supplier_ids) -> Tuple[ErrorType, dict]: pass @abstractmethod async def add_account(self, cdb: AsyncSession, account: supplier_users) -> ErrorType: pass @abstractmethod async def update_account(self, cdb: AsyncSession, su_id, data: dict) -> ErrorType: pass @abstractmethod async def delete_tokens(self, cdb: AsyncSession, su_id) -> ErrorType: pass class SupplierUserCRUD(ISupplierUserCRUD): async def get_by_supplier(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, Optional[supplier_users]]: """협력사의 대표 계정 1건(발급순 첫 계정). 미발급이면 (SUCCESS, None) — 에러가 아니다.""" try: query = ( select(supplier_users) .where(supplier_users.supplier_id == supplier_id, supplier_users.deleted == False) # noqa: E712 .order_by(supplier_users.created_at.asc()) .limit(1) ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, None return ErrorType.SUCCESS, rows[0] if rows else None except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, None async def login_id_exists(self, cdb: AsyncSession, login_id: str) -> Tuple[ErrorType, bool]: """로그인 ID 는 채팅 로그인 전역 유일(회사 스코프 아님) — 루트 backend 계정생성과 같은 기준.""" try: query = select(supplier_users.su_id).where( supplier_users.id == login_id, supplier_users.deleted == False, # noqa: E712 ).limit(1) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, False return ErrorType.SUCCESS, len(rows) > 0 except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, False async def account_map(self, cdb: AsyncSession, supplier_ids) -> Tuple[ErrorType, dict]: """supplier_id 목록 → {supplier_id: (login_id, status)}. 목록/상세의 채팅 계정 표기용 배치 조인. 협력사당 여러 계정(시드 등)이 있으면 발급순 첫 계정만 대표로 남긴다.""" try: if not supplier_ids: return ErrorType.SUCCESS, {} query = ( select(supplier_users.supplier_id, supplier_users.id, supplier_users.status) .where(supplier_users.supplier_id.in_(supplier_ids), supplier_users.deleted == False) # noqa: E712 .order_by(supplier_users.created_at.asc()) ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, {} result = {} for sid, login_id, status in rows: if sid not in result: result[sid] = (login_id, status) return ErrorType.SUCCESS, result except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, {} async def add_account(self, cdb: AsyncSession, account: supplier_users) -> ErrorType: try: return await DB_SESSION_MNG.insert(cdb, account) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED async def update_account(self, cdb: AsyncSession, su_id, data: dict) -> ErrorType: try: if not data: return ErrorType.SUCCESS query = update(supplier_users).where(supplier_users.su_id == su_id).values(**data, updated_at=GTime.UTC()) return await DB_SESSION_MNG.add(cdb, query) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED async def delete_tokens(self, cdb: AsyncSession, su_id) -> ErrorType: """하드 삭제 — 루트 backend 가 로그인/로그아웃 때 하드 삭제하는 것과 같은 방식.""" try: query = delete(supplier_user_tokens).where(supplier_user_tokens.su_id == su_id) return await DB_SESSION_MNG.add(cdb, query) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED