import uuid from fastapi import Depends from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import users from common.enums import DBWRType, ErrorType, UserRole, UserStatus from common.utils.gtime import GTime from crud.user_crud import IUserCRUD, UserCRUD from router.v1.company.protocol import ( CompanyUserData, Req_CreateCompanyUser, Req_UpdateCompanyUser, Res_CompanyUser, Res_CompanyUserList, Res_DeleteCompanyUser, ) from router.v1.validator.dependencies import GetHashedPW class CompanyUserService: """최고관리자(OWNER)의 자기 회사 유저 관리 로직. - 라우터에서 RequireOwner 로 1차 권한을 거른 뒤 호출된다. - company_id 는 토큰값만 쓴다(요청 body 무시) → 남의 회사 데이터 불가. - 변경 대상이 OWNER 면 거부한다(최고관리자는 앱에서 수정·삭제 불가). """ def __init__(self, user_crud: IUserCRUD = Depends(UserCRUD)): self.user_crud = user_crud async def _fetch_managed(self, company_uuid: uuid.UUID, user_id: uuid.UUID): """대상 유저 조회 + 같은 회사 + 비-OWNER 확인. (ErrorType, user|None) 반환.""" err_type, user = await DB_SESSION_MNG.execute_lambda( users.DBType(), DBWRType.DB_READ.value, lambda s: self.user_crud.get_by_user_id(s, user_id), ) if err_type != ErrorType.SUCCESS or user is None: return ErrorType.ACCOUNT_NOT_FOUND, None if user.company_id != company_uuid: return ErrorType.ACCOUNT_NOT_FOUND, None if user.role == UserRole.OWNER.value: return ErrorType.ACCOUNT_FORBIDDEN, None return ErrorType.SUCCESS, user async def list_users(self, company_id: str, search, pg, hide_dev: bool = False) -> Res_CompanyUserList: res = Res_CompanyUserList(page=pg.page, size=pg.size) company_uuid = uuid.UUID(company_id) err_type, rows, total = await DB_SESSION_MNG.execute_lambda( users.DBType(), DBWRType.DB_READ.value, lambda s: self.user_crud.list_by_company(s, company_uuid, search, pg.skip, pg.size, hide_dev), ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res res.users = [CompanyUserData.model_validate(r) for r in rows] res.total = total return res async def get_user(self, company_id: str, user_id: str) -> Res_CompanyUser: res = Res_CompanyUser() err_type, user = await self._fetch_managed(uuid.UUID(company_id), uuid.UUID(user_id)) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res res.user = CompanyUserData.model_validate(user) return res async def create_user(self, company_id: str, req: Req_CreateCompanyUser) -> Res_CompanyUser: res = Res_CompanyUser() company_uuid = uuid.UUID(company_id) # 1) 로그인 ID 중복 확인 (id 는 전역 unique) err_type = await DB_SESSION_MNG.execute_lambda( users.DBType(), DBWRType.DB_READ.value, lambda s: self.user_crud.is_user(s, req.id), ) if err_type == ErrorType.DB_ALREADY_SAME_KEY: res.result.SetResult(ErrorType.ACCOUNT_ALREADY_EXIST) return res if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res # 2) 생성 — 회사는 토큰값. 권한은 일반·관리자만 허용(최고관리자 승격은 앱에서 불가). user = users( company_id=company_uuid, id=req.id, password=await GetHashedPW(req.password), name=req.name or None, email=req.email or None, contact_number=req.contact_number or None, role=req.role if req.role in (UserRole.USER.value, UserRole.OWNER.value) else UserRole.USER.value, ) err_type = await DB_SESSION_MNG.execute_lambda_run( [users.DBType()], [lambda s: self.user_crud.add_user(s, user)], ) if err_type != ErrorType.SUCCESS: if err_type == ErrorType.DB_ALREADY_SAME_KEY: res.result.SetResult(ErrorType.ACCOUNT_ALREADY_EXIST) else: res.result.SetResult(err_type) return res # 서버 기본값(created_at 등)은 insert 후 객체에 안 실리므로 재조회. return await self.get_user(company_id, str(user.user_id)) async def update_user(self, company_id: str, user_id: str, req: Req_UpdateCompanyUser) -> Res_CompanyUser: res = Res_CompanyUser() company_uuid = uuid.UUID(company_id) user_uuid = uuid.UUID(user_id) err_type, _ = await self._fetch_managed(company_uuid, user_uuid) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res data = req.model_dump(exclude_unset=True) if data.get("status") is not None: s = data["status"] # pydantic 은 enum 멤버로 돌려준다 → SMALLINT 값으로 환원 data["status"] = s.value if isinstance(s, UserStatus) else int(s) if data.get("password"): data["password"] = await GetHashedPW(data["password"]) else: data.pop("password", None) # 빈 비밀번호는 변경하지 않음 err_type = await DB_SESSION_MNG.execute_lambda_run( [users.DBType()], [lambda s: self.user_crud.update_user(s, user_uuid, data)], ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res return await self.get_user(company_id, user_id) async def delete_user(self, company_id: str, user_id: str) -> Res_DeleteCompanyUser: res = Res_DeleteCompanyUser() company_uuid = uuid.UUID(company_id) user_uuid = uuid.UUID(user_id) err_type, _ = await self._fetch_managed(company_uuid, user_uuid) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res err_type = await DB_SESSION_MNG.execute_lambda_run( [users.DBType()], [lambda s: self.user_crud.update_user(s, user_uuid, {"deleted": True, "updated_at": GTime.UTC()})], ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res