Files
Aislo/.agent/plan_register_split.md
T

9.1 KiB

회원가입 플로우 개편: 계정 생성과 회사 연결 분리

Context

현재 A07_Register 회원가입 폼은 계정 생성과 동시에 회사 생성/연결(신규 회사명 또는 기존 company_id)을 강제로 요구한다. 사용자는 이 결합이 부적절하다고 판단했다 — 회사 관련 정보(사업자등록번호, 전화번호, 주소 등)는 계정을 만든 사람이 로그인한 이후에 "회사 생성 또는 검색·연결" 단계로 넘겨야 자연스럽다. 또한:

  • 회원가입 폼 필드를 이름/직급/이메일/전화번호/비밀번호/비밀번호 확인으로 재정의 (department는 여기서 제외, 회사 연결 이후 설정)
  • 약관/개인정보처리방침/마케팅 동의는 필수 항목이지만 실제 내용이 어디에도 없었다 — 체크박스 옆에 이름을 두고 펼치면 내용이 열리는 UI로 바꾸고, 실제 텍스트는 나중에 쉽게 고칠 수 있도록 리소스 텍스트 파일로 분리
  • 이메일 인증은 회원가입 제출 즉시 폼 하단에 OTP 입력 칸이 나타나는 현재 방식을 유지

목표: 계정 생성(A07) → 이메일 인증 → 로그인 가능(company 없이) → 로그인 후 B01_AccountDetail에서 회사 생성/연결 → 연결 완료 후에만 B02~B11 워크플로우 접근 가능.

현재 구조 파악 결과

  • users 테이블: company_id nullable, role ENUM('SYSTEM_ADMIN','MASTER','MEMBER') 기본 MEMBER, is_master BOOLEAN, status ENUM('PENDING_EMAIL','PENDING','ACTIVE','INACTIVE','REJECTED')
  • create_registration() (common_util/common_util_auth_repository.py:30): 현재 회원가입 시 계정+회사(or join_request)+user_consents를 한 트랜잭션에서 같이 생성
  • complete_registration() (common_util/common_util_auth_repository.py:134): 이메일 인증 완료 시 MASTER→ACTIVE, MEMBER→PENDING(마스터 승인 대기)로 status 변경
  • verify_session() (common_util/common_util_auth.py:74): 세션 조회 시 company_id, role, is_master, status를 함께 반환 — 이미 dependency에서 활용 가능한 형태
  • main.py:236-240: B03B06 라우터에 protected = [Depends(verify_session)]만 걸려 있음 (company_id 미검사, B07B11은 라우터 자체가 아직 없음)
  • A00_Common/router.ts: PROTECTED_ROUTES 목록으로 인증 가드만 수행, company 연결 여부는 검사하지 않음
  • A09_Security_Terms.ts + A09_Security_UI_Page.ts: 이미 "보안정책 안내 페이지" 용도로 존재하지만 세션/보안 정책 설명이지 회원가입 약관 동의가 아님 — 이 구조(약관 텍스트 분리 + 언어별 배열)를 참고해 새 리소스를 만든다
  • B01_AccountDetail: 현재 UI_Page.ts, UI_Style.css만 있고 백엔드/API 없음 — 여기에 계정 상세 정보 표시 + 회사 연결 섹션을 새로 구현해야 함
  • resources/ 폴더(프로젝트 루트)는 로고/배너 등 에셋용으로 이미 정의돼 있음 (structure.md:41) — 약관 텍스트는 이 폴더 하위에 신설 서브폴더로 정리

DB 변경

db_management/003_register_split.sql 신규 마이그레이션:

  1. users.status ENUM에 'NO_COMPANY' 추가 (이메일 인증 완료, 회사 미연결 상태)
  2. users.company_id, users.role, users.is_master 는 구조 그대로 유지하되 회원가입 시점에는 전부 NULL/기본값으로 시작
  3. 기존 join_requests 테이블 그대로 재사용 (MEMBER가 기존 회사 연결 신청 시)
  4. department 컬럼은 그대로 두되 회원가입 INSERT에서 제외 (NULL 허용이라 문제 없음)

백엔드 변경

A07_Register (회원가입 축소)

  • A07_Register_Schema.py: RegisterRequest에서 account_type, company_id, company_name, business_number, phone_number(→ 개인 전화번호로 의미 변경), address, representative_name 제거. 필드: email, password, password_confirm, name, position, phone, terms_agreed, privacy_agreed, marketing_agreed, terms_version. password_confirm == password 검증을 model_validator에 추가.
  • A07_Register_Router.pyrequest_registration(): create_registration() 호출을 단순화된 데이터로 변경.
  • common_util_auth_repository.pycreate_registration(): 회사/join_request 생성 로직 제거. users INSERT만 수행 (role 기본값 MEMBER, company_id NULL, is_master FALSE). user_consents INSERT는 유지.
  • complete_registration(): 이메일 인증 완료 시 무조건 status = 'NO_COMPANY'로 변경 (MASTER/MEMBER 분기 제거).
  • A06_Login_Router.pyrequest_login(): user["status"] != "ACTIVE" 체크를 status not in ("ACTIVE", "NO_COMPANY")로 완화 — company 미연결 상태에서도 로그인은 허용.

회사 생성/연결 API (신규, B01_AccountDetail 백엔드)

B01_AccountDetail/B01_AccountDetail_Router.py, _Schema.py, _Repository.py 신규 생성 (기존 패턴 준수, common_util_auth_repository.pysearch_companies/_company_code 재사용):

  • GET /api/account/me — 현재 계정 상세 (session 기반)
  • PATCH /api/account/me — 이름/직급/전화번호 수정
  • GET /api/account/companies?q= — 기존 search_companies() 재사용
  • POST /api/account/company/create — 신규 회사 생성 + 본인을 MASTER/is_master=TRUE로 연결, role='MASTER', status='ACTIVE'로 전환. 회사 필드(사업자등록번호/주소/전화번호/대표자명)를 여기서 받음.
  • POST /api/account/company/join — 기존 회사에 join_requests 생성 + users.company_id 세팅(단, status는 PENDING으로, 마스터 승인 전까지 유지)

접근 제어 (company 연결 강제)

  • common_util/common_util_auth.pyrequire_company() dependency 추가: session["company_id"] is None 이면 403.
  • main.py: B03~B06 라우터의 protected 리스트에 Depends(require_company) 추가.

프론트엔드 변경

리소스 텍스트 분리

resources/legal/ 신규 폴더:

  • terms_of_service.txt — 이용약관 (Aislo/임도설계 자동화 서비스에 맞는 내용 초안 작성)
  • privacy_policy.txt — 개인정보처리방침
  • marketing_consent.txt — 마케팅 정보 수신 동의

이 텍스트는 빌드 시 정적 자산으로 fetch하거나, 간단히 A07_Register에서 TS 상수 모듈로 다시 export하는 두 방식 중 — Vite가 ?raw import를 지원하므로 import termsText from "../../resources/legal/terms_of_service.txt?raw" 형태로 로드 (외부 자산 폴더를 벗어나지 않고 사용자가 텍스트 파일만 고치면 되도록).

A07_Register (폼 축소 + 약관 아코디언 + OTP 유지)

  • A07_Register_UI_Auth_Page.ts: accountType, company, companyId 필드 및 관련 로직 제거. position(직급), phone(전화번호), passwordConfirm 필드 추가.
  • 약관 체크박스 영역: 현재 check() 헬퍼(단순 체크박스+라벨)를 확장한 checkWithAccordion()로 교체 — 라벨 클릭 시 하단에 resources/legal/*.txt 내용이 펼쳐지는 <details>/<summary> 또는 토글 버튼 구조.
  • OTP 입력 칸: 기존처럼 제출 후 폼 하단에 노출되는 현재 로직 그대로 유지 (verifyStep 플래그 방식 재사용).
  • A07_Register_Api_Fetch.tsRegisterPayload: 축소된 필드로 갱신, searchCompanies export는 B01로 이동(또는 공용 유지 — B01에서 재사용하도록 export 유지).

B01_AccountDetail (계정 상세 + 회사 연결)

  • B01_AccountDetail_UI_Page.ts: 계정 기본정보 표시/수정 폼 + "회사 생성" / "회사 검색·연결" 두 개의 섹션(회사 미연결 시에만 노출, A07_Register_Api_Fetch.searchCompanies 재사용 가능).
  • B01_AccountDetail_Api_Fetch.ts 신규: 위 백엔드 API 클라이언트.

라우팅 가드 (company 없으면 B02+ 차단)

  • A00_Common/router.tsrenderCurrentRoute(): PROTECTED_ROUTES 검사 이후, B02~B11 라우트에 한해 세션의 company_id 확인 로직 추가 (session 조회 API에 company_id 이미 포함돼 있음 — A06_Login_Router.py/session 응답에 company_id 필드만 추가하면 됨). 없으면 ROUTES.B01_ACCOUNT로 리다이렉트.
  • A06_Login_Router.pycurrent_session(): 응답에 company_id, status 추가.

검증 방법

  1. python main.py 실행 후 브라우저에서 /#/a07-register 접속 → 축소된 필드로 가입, 약관 아코디언 펼쳐서 텍스트 노출 확인, 제출 후 OTP 칸 노출 확인
  2. OTP 인증 완료 → DB에서 users.status = 'NO_COMPANY', company_id IS NULL 확인
  3. 해당 계정으로 로그인 성공 확인 (기존엔 status != ACTIVE면 403이었음)
  4. 로그인 상태에서 /#/b02-proj-register 접속 시도 → /#/b01-account로 리다이렉트되는지 확인
  5. B01에서 "회사 생성" 실행 → role='MASTER', status='ACTIVE', company_id 세팅 확인 후 B02 접근 가능한지 재확인
  6. 두 번째 테스트 계정으로 "회사 검색·연결"(join) 플로우 → join_requests 생성 확인, 기존 마스터 승인 API(A09_Security_Router.py/master/join-requests)로 승인 후 status ACTIVE 전환 확인