diff --git a/README.md b/README.md index 64b2e56..5e405d9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,55 @@ 광주은행 엔터프라이즈 API 포털 관리 시스템 +## 프로젝트 클론 (Git Clone) + +아래 스크립트를 복사하여 한 번에 실행하면 모든 프로젝트를 클론합니다: + +```bash +# 변수 설정 (환경에 맞게 수정하세요) +GIT_URL="ssh://git@192.168.240.178:18081/eapim" +PROJECT_HOME="/c/eactive/workspaces/kjb-eapim-devPortal" +DEFAULT_BRANCH="jenkins_with_weblogic" + +# 프로젝트 홈 디렉토리 생성 및 이동 +mkdir -p "$PROJECT_HOME" +cd "$PROJECT_HOME" + +# 메인 프로젝트 클론 +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/kjb-eapim.git" kjb-eapim + +# 의존 모듈 클론 (kjb-eapim 폴더와 동일 레벨) +mkdir -p eapim-online +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/elink-online-core-jpa.git" eapim-online/elink-online-core-jpa +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/elink-portal-common.git" elink-portal-common +git clone -b "$DEFAULT_BRANCH" "$GIT_URL/kjb-safedb.git" kjb-safedb + +# 클론 후 폴더 구조 확인 +echo "=== 프로젝트 구조 ===" +ls -la "$PROJECT_HOME" +``` + +### 클론 후 폴더 구조 + +``` +$PROJECT_HOME/ +├── kjb-eapim/ # 메인 프로젝트 +│ └── eapim-portal/ # 이 프로젝트 (Portal) +├── eapim-online/ # Gateway 데이터 모델 +│ └── elink-online-core-jpa/ # JPA 엔티티 모듈 +├── elink-portal-common/ # 공통 유틸리티 +└── kjb-safedb/ # SafeDB 암호화 라이브러리 +``` + +### 의존 관계 (settings.gradle 참조) + +``` +eapim-portal +├── elink-online-core-jpa → ../eapim-online/elink-online-core-jpa +├── elink-portal-common → ../elink-portal-common +└── kjb-safedb → ../kjb-safedb +``` + ## 프로젝트 개요 **EAPIM Portal**은 광주은행의 API 서비스를 관리하고, 사용자 등록, API 키 발급, 문서화, 테스트 기능을 제공하는 웹 기반 플랫폼입니다. @@ -136,7 +185,3 @@ docker run -p 8080:8080 eapim-portal:latest - **프로젝트 상세 지침**: `CLAUDE.md` (한글) - **사용자 가이드**: `개발자포탈.md` (한글) - **빌드 스크립트**: `build-gf63.sh`, `deploy_portal.sh` - -## 라이선스 - -© 광주은행 (Kwangju Bank) diff --git a/kjb-docs/mermaid/invite-flow-integrated.mermaid b/kjb-docs/mermaid/invite-flow-integrated.mermaid index aa267a6..c5d5f1d 100644 --- a/kjb-docs/mermaid/invite-flow-integrated.mermaid +++ b/kjb-docs/mermaid/invite-flow-integrated.mermaid @@ -1,99 +1,105 @@ flowchart TD - Start([법인 관리자가 사용자 초대]) --> GenerateToken[8글자 토큰 생성
예: K7MNPQ2R
UserInvitation 저장] + Start([법인 관리자가 사용자 초대]) --> GenerateToken[8글자 토큰 생성
예: K7MNPQ2R
UserInvitation 저장
상태: PENDING] GenerateToken --> SendEmail[이메일 발송
- 링크 포함
- 8글자 토큰 표시] SendEmail --> UserType{사용자 유형} + %% ======================================== %% 신규 회원 플로우 + %% ======================================== UserType -->|신규 회원
미가입자| NewUserPath{초대 확인 방법} - NewUserPath -->|폐쇄망 환경| LoginPage1[로그인 페이지 접근] - LoginPage1 --> InviteLink1["초대를 받으셨나요?" 링크 클릭] - InviteLink1 --> InputCodePage1[초대코드 입력 페이지] - InputCodePage1 --> InputCode1[8자리 코드 입력
예: K7MNPQ2R] - InputCode1 --> ValidateCode1{코드 유효성 검증} - - ValidateCode1 -->|유효하지 않음| Error1[에러: 유효하지 않은 초대코드] - ValidateCode1 -->|유효| SignupPage1[회원가입 페이지
/signup/portalUser?invitation=CODE] - NewUserPath -->|이메일 링크| EmailLink1[이메일 링크 클릭
/signup/portalUser?invitation=CODE] - EmailLink1 --> SignupPage1 + NewUserPath -->|일반 회원가입| DirectSignup[일반 회원가입 진행
ROLE_USER로 생성] - SignupPage1 --> RegisterForm[가입 정보 입력
- 이메일 자동 입력됨
- 법인명 표시
- 약관 동의] - RegisterForm --> RegisterComplete[회원가입 완료
ROLE_CORP_USER로 생성] + EmailLink1 --> SignupPage1[회원가입 페이지
- 이메일 자동 입력됨
- 법인명 표시] + SignupPage1 --> RegisterForm[가입 정보 입력
약관 동의] + RegisterForm --> RegisterComplete[회원가입 완료
ROLE_CORP_USER로 생성
상태: COMPLETED] RegisterComplete --> NewUserEnd([가입 완료 안내]) + DirectSignup --> Login4[로그인] + Login4 --> AfterLogin4[로그인 성공] + AfterLogin4 --> CheckInvitation + + %% ======================================== %% 기존 회원 플로우 + %% ======================================== UserType -->|기존 회원
ROLE_USER| ExistingUserPath{초대 확인 방법} - ExistingUserPath -->|폐쇄망 환경| LoginPage2[로그인 페이지 접근] - LoginPage2 --> InviteLink2["초대를 받으셨나요?" 링크 클릭] - InviteLink2 --> InputCodePage2[초대코드 입력 페이지] - InputCodePage2 --> CheckLogin1{로그인 여부} + ExistingUserPath -->|이메일 링크
비로그인 상태| EmailLink2[이메일 링크 클릭
/signup/decision?invitation=CODE] + ExistingUserPath -->|이메일 링크
로그인 상태| EmailLinkLoggedIn[이메일 링크 클릭
/signup/decision?invitation=CODE] + ExistingUserPath -->|직접 로그인| DirectLogin[로그인 페이지에서 로그인] - CheckLogin1 -->|미로그인| RedirectLogin1[로그인 페이지로 리다이렉트
코드 저장] - RedirectLogin1 --> Login1[로그인] - Login1 --> AfterLogin1[로그인 성공] - - CheckLogin1 -->|로그인됨| InputCode2[8자리 코드 입력] - InputCode2 --> ValidateCode2{코드 유효성 검증} - ValidateCode2 -->|유효하지 않음| Error2[에러: 유효하지 않은 초대코드] - ValidateCode2 -->|유효| DecisionPage1[초대 수락 페이지
/signup/decision_process] - - ExistingUserPath -->|이메일 링크| EmailLink2[이메일 링크 클릭
/signup/decision?invitation=CODE] + %% 비로그인 상태에서 이메일 링크 클릭 EmailLink2 --> DecisionPagePublic[초대 수락 페이지
비로그인 상태] DecisionPagePublic --> RedirectLogin2[로그인 유도] RedirectLogin2 --> Login2[로그인] Login2 --> AfterLogin2[로그인 성공] - ExistingUserPath -->|직접 로그인| DirectLogin[로그인 페이지에서 로그인] + %% 로그인 상태에서 이메일 링크 클릭 → 바로 수락 페이지 + EmailLinkLoggedIn --> DecisionPageDirect[초대 수락 페이지
/signup/decision_process
약관 동의] + DecisionPageDirect --> UserDecision + + %% 직접 로그인 DirectLogin --> Login3[로그인] Login3 --> AfterLogin3[로그인 성공] - %% 로그인 후 자동 초대 확인 - AfterLogin1 --> CheckInvitation[초대 코드 자동 확인
findFirstByInvitationEmailAndStatus] - AfterLogin2 --> CheckInvitation + %% ======================================== + %% 로그인 후 초대 확인 (PortalAuthenticationSuccessHandler) + %% ======================================== + AfterLogin2 --> CheckInvitation[PortalAuthenticationSuccessHandler
PENDING 초대 확인
findFirstByInvitationEmailAndStatus] AfterLogin3 --> CheckInvitation - CheckInvitation --> HasInvitation{초대 코드 존재?} + CheckInvitation --> HasInvitation{PENDING 초대 존재?} - HasInvitation -->|없음| NormalHome[일반 홈페이지] - HasInvitation -->|있음| AutoRedirect[자동으로 초대 수락 페이지로 이동
/signup/decision_process] + HasInvitation -->|없음| NormalHome[메인 페이지 이동] + HasInvitation -->|있음| SaveToSession[세션에 초대 정보 저장
pendingInvitation=true
pendingInvitationToken
pendingInvitationOrgName] - AutoRedirect --> DecisionPage1 - DecisionPage1 --> UserDecision{사용자 선택} + SaveToSession --> RedirectHome[메인 페이지로 이동] - UserDecision -->|수락| AcceptProcess[초대 수락 처리
1. PortalOrg 변경
2. ROLE_CORP_USER 전환
3. 약관 재동의] - UserDecision -->|거절| RejectProcess[초대 거절 처리
Invitation.status = REJECTED] + %% ======================================== + %% 메인 페이지 팝업 (index.html) + %% ======================================== + RedirectHome --> ShowPopup[팝업 표시
OOO에서 초대가 도착했습니다
초대를 확인하시겠습니까?] - AcceptProcess --> CompleteInvitation[Invitation.status = COMPLETED] - CompleteInvitation --> LogoutPrompt[로그아웃 후 재로그인 안내] + ShowPopup --> PopupChoice{사용자 선택} + + PopupChoice -->|확인| GoDecisionPage[초대 수락 페이지 이동
/signup/decision?invitation=TOKEN] + PopupChoice -->|취소| StayHome[메인 페이지 유지
다음 로그인 시 팝업 재표시] + + StayHome --> NextLogin[다음 로그인] + NextLogin --> CheckInvitation + + GoDecisionPage --> DecisionPage[초대 수락 페이지
/signup/decision_process
약관 동의] + + DecisionPage --> UserDecision{사용자 선택} + + UserDecision -->|수락| AcceptProcess[초대 수락 처리
1. PortalOrg 변경
2. ROLE_CORP_USER 전환
3. 약관 재동의
상태: COMPLETED] + UserDecision -->|거절| RejectProcess[초대 거절 처리
상태: REJECTED] + + AcceptProcess --> LogoutPrompt[로그아웃 후 재로그인 안내] LogoutPrompt --> ExistingUserEnd([초대 수락 완료]) - RejectProcess --> RejectEnd([초대 거절 완료]) - - %% 에러 처리 - Error1 --> ErrorEnd([처리 종료]) - Error2 --> ErrorEnd + RejectProcess --> RejectEnd([초대 거절 완료
다음 로그인 시 팝업 안 뜸]) + %% ======================================== %% 스타일링 + %% ======================================== style Start fill:#e1f5e1 style NewUserEnd fill:#c8e6c9 style ExistingUserEnd fill:#c8e6c9 style RejectEnd fill:#fff9c4 - style ErrorEnd fill:#ffcdd2 - style Error1 fill:#ffcdd2 - style Error2 fill:#ffcdd2 + style NormalHome fill:#e3f2fd style GenerateToken fill:#bbdefb style AcceptProcess fill:#c5cae9 - style CheckInvitation fill:#ffe0b2 - style AutoRedirect fill:#ffe0b2 + style RejectProcess fill:#ffecb3 - style LoginPage1 fill:#e1bee7 - style LoginPage2 fill:#e1bee7 - style InviteLink1 fill:#f8bbd0 - style InviteLink2 fill:#f8bbd0 - style InputCodePage1 fill:#f8bbd0 - style InputCodePage2 fill:#f8bbd0 + style CheckInvitation fill:#ffe0b2 + style SaveToSession fill:#ffe0b2 + style ShowPopup fill:#f8bbd0 + style PopupChoice fill:#f8bbd0 + + style StayHome fill:#e1bee7 + style NextLogin fill:#e1bee7 diff --git a/kjb-docs/mermaid/invite-sequence.mermaid b/kjb-docs/mermaid/invite-sequence.mermaid index 5d3214c..5204e6e 100644 --- a/kjb-docs/mermaid/invite-sequence.mermaid +++ b/kjb-docs/mermaid/invite-sequence.mermaid @@ -6,10 +6,17 @@ sequenceDiagram participant MessageService as Message Handler participant Email as Email System actor 초대받은사용자 + participant AuthHandler as PortalAuthenticationSuccessHandler + participant IndexController + participant MainPage as 메인 페이지 (index.html) participant UserRegisterController participant UserRegisterFacade participant PortalUserRepo as PortalUser Repository + participant PortalOrgRepo as PortalOrg Repository + %% ======================================== + %% 1. 초대 발송 + %% ======================================== 법인관리자->>Portal: 사용자 이메일 입력
초대 요청 Portal->>UserManFacade: sendInvitation(sender, emailAddr) @@ -17,7 +24,7 @@ sequenceDiagram alt 사용자가 없거나 이미 법인 사용자 UserManFacade-->>Portal: 에러 반환 Portal-->>법인관리자: 초대 불가 알림 - else 일반 사용자 ROLE_USER + else 일반 사용자 ROLE_USER 또는 비회원 UserManFacade->>UserManFacade: generateEightCharToken()
예: K7MNPQ2R UserManFacade->>UserInvitationRepo: save(UserInvitation)
status: PENDING
expiresOn: 7일 후 UserManFacade->>MessageService: publishEvent(UserInvitationEvent) @@ -28,25 +35,74 @@ sequenceDiagram Email->>초대받은사용자: 초대 이메일 수신 - alt 폐쇄망 환경 - 초대받은사용자->>Portal: 8글자 토큰 직접 입력
/signup/decision?invitation=K7MNPQ2R - else 이메일 링크 - 초대받은사용자->>Portal: 링크 클릭
/signup/decision?invitation=BASE64TOKEN + %% ======================================== + %% 2-A. 로그인 상태에서 이메일 링크 클릭 (바로 수락 페이지) + %% ======================================== + alt 로그인 상태에서 이메일 링크 클릭 + 초대받은사용자->>Portal: 이메일 링크 클릭
/signup/decision?invitation=TOKEN + Portal->>UserRegisterController: showDecisionPage(invitationToken) + UserRegisterController->>UserInvitationRepo: findByToken(decodedToken) + UserRegisterController-->>Portal: 약관 동의 페이지 + Portal-->>초대받은사용자: 초대 수락/거절 화면
(바로 표시, 팝업 없음) + Note over 초대받은사용자: 6. 초대 수락/거절 처리로 이동 end - Portal->>UserRegisterController: showDecisionPage(invitationToken) - UserRegisterController->>UserRegisterController: decodeInvitationToken()
(Base64 or 직접입력 처리) - UserRegisterController->>UserInvitationRepo: findByToken(decodedToken) - - alt 토큰 무효 or 상태가 PENDING 아님 - UserRegisterController-->>Portal: 에러: 유효하지 않은 초대 - Portal-->>초대받은사용자: 에러 페이지 - else 토큰 유효 - UserRegisterController->>PortalUserRepo: findByEmailAddr() - UserRegisterController-->>Portal: 약관 동의 페이지
(법인명, 사용자명 표시) - Portal-->>초대받은사용자: 수락/거절 선택 화면 + %% ======================================== + %% 2-B. 로그인 (기존 회원 또는 신규 가입 후) + %% ======================================== + alt 기존 회원 (비로그인 상태) + 초대받은사용자->>Portal: 로그인 + else 비회원이 일반 회원가입 후 + 초대받은사용자->>Portal: 회원가입 (ROLE_USER) + 초대받은사용자->>Portal: 로그인 end + %% ======================================== + %% 3. 로그인 성공 처리 (AuthenticationSuccessHandler) + %% ======================================== + Portal->>AuthHandler: onAuthenticationSuccess() + AuthHandler->>AuthHandler: 이메일 인증, 휴면계정,
비밀번호 만료 체크 + + AuthHandler->>UserInvitationRepo: findFirstByInvitationEmailAndStatus
(loginId, PENDING) + + alt PENDING 초대 없음 + AuthHandler->>Portal: redirect to / + Portal-->>초대받은사용자: 메인 페이지 (팝업 없음) + else PENDING 초대 있음 & 만료 안됨 + AuthHandler->>PortalOrgRepo: findById(invitation.orgId) + PortalOrgRepo-->>AuthHandler: PortalOrg (기관명) + AuthHandler->>AuthHandler: session.setAttribute
("pendingInvitation", true)
("pendingInvitationToken", token)
("pendingInvitationOrgName", orgName) + AuthHandler->>Portal: redirect to / + end + + %% ======================================== + %% 4. 메인 페이지 팝업 표시 + %% ======================================== + Portal->>IndexController: GET / + IndexController->>MainPage: render index.html + + MainPage->>MainPage: 세션에서 pendingInvitation 확인 + + alt pendingInvitation == true + MainPage-->>초대받은사용자: 팝업 표시
"[기관명]에서 초대가 도착했습니다.
초대를 확인하시겠습니까?" + end + + %% ======================================== + %% 5. 사용자 팝업 선택 + %% ======================================== + alt 팝업에서 "확인" 클릭 + 초대받은사용자->>Portal: /signup/decision?invitation=TOKEN + Portal->>UserRegisterController: showDecisionPage(invitationToken) + UserRegisterController->>UserInvitationRepo: findByToken(decodedToken) + UserRegisterController-->>Portal: 약관 동의 페이지 + Portal-->>초대받은사용자: 초대 수락/거절 화면 + else 팝업에서 "취소" 클릭 + Note over 초대받은사용자,MainPage: 메인 페이지 유지
다음 로그인 시 팝업 재표시 + end + + %% ======================================== + %% 6. 초대 수락/거절 처리 + %% ======================================== 초대받은사용자->>Portal: 수락 or 거절 선택 Portal->>UserRegisterController: processInvitation(action) UserRegisterController->>UserRegisterFacade: processInvitation(action, invitation) @@ -57,8 +113,10 @@ sequenceDiagram UserRegisterFacade->>UserInvitationRepo: save(invitation)
status: COMPLETED
completeDate: now() UserRegisterFacade-->>Portal: 수락 성공 Portal-->>초대받은사용자: 성공 메시지
로그아웃 후 재로그인 안내 + Note over 초대받은사용자: 다음 로그인 시
PENDING 초대 없음 → 팝업 안 뜸 else 거절 UserRegisterFacade->>UserInvitationRepo: save(invitation)
status: REJECTED
completeDate: now() UserRegisterFacade-->>Portal: 거절 처리 완료 Portal-->>초대받은사용자: 거절 완료 메시지 + Note over 초대받은사용자: 다음 로그인 시
REJECTED 상태 → 팝업 안 뜸 end diff --git a/kjb-docs/mermaid/invite-state.mermaid b/kjb-docs/mermaid/invite-state.mermaid index 79bba32..1699fec 100644 --- a/kjb-docs/mermaid/invite-state.mermaid +++ b/kjb-docs/mermaid/invite-state.mermaid @@ -1,20 +1,44 @@ stateDiagram-v2 [*] --> 초대생성: 법인관리자가 초대 시작 - 초대생성 --> PENDING: 8글자 토큰 생성
UserInvitation 저장 + 초대생성 --> PENDING: 토큰 생성 및 저장 - PENDING --> 토큰전달: 이메일 발송 + PENDING --> 이메일발송: 초대 이메일 발송 - 토큰전달 --> 사용자접근: 폐쇄망 직접입력 or
이메일 링크 클릭 + state 로그인후팝업 { + 이메일발송 --> 로그인: 사용자 로그인 + 로그인 --> 초대확인: AuthSuccessHandler + 초대확인 --> 세션저장: 세션에 초대 정보 저장 + 세션저장 --> 팝업표시: 메인 페이지 팝업 + 팝업표시 --> 팝업확인: 확인 클릭 + 팝업표시 --> 팝업취소: 취소 클릭 + 팝업취소 --> 로그인: 다음 로그인 시 재표시 + } - 사용자접근 --> 약관동의: 토큰 유효성 검증 성공 - 사용자접근 --> 에러: 토큰 무효 + 팝업확인 --> 약관동의: 수락 페이지 이동 - 약관동의 --> COMPLETED: 사용자 수락
ROLE_CORP_USER 전환
PortalOrg 변경 - 약관동의 --> REJECTED: 사용자 거절 + 약관동의 --> COMPLETED: 수락 + 약관동의 --> REJECTED: 거절 COMPLETED --> [*]: 초대 완료 REJECTED --> [*]: 초대 거절 - PENDING --> CANCELED: 관리자가 초대 취소 + PENDING --> CANCELED: 관리자 취소 + PENDING --> EXPIRED: 만료일 경과 CANCELED --> [*]: 초대 취소 - 에러 --> [*]: 처리 종료 + EXPIRED --> [*]: 초대 만료 + + note right of PENDING + PENDING 상태일 때만 + 로그인 시 팝업 표시 + end note + + note right of REJECTED + REJECTED 상태가 되면 + 더 이상 팝업 표시 안 함 + end note + + note right of COMPLETED + COMPLETED 상태가 되면 + 더 이상 팝업 표시 안 함 + ROLE_CORP_USER로 전환됨 + end note diff --git a/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java b/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java index 2a51fab..2b70e66 100644 --- a/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java +++ b/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java @@ -19,6 +19,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; + @Controller @RequiredArgsConstructor public class IndexController { diff --git a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java index 6fa089f..736e4f8 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java @@ -1,9 +1,11 @@ package com.eactive.apim.portal.config; +import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; import com.eactive.apim.portal.apps.user.service.PortalUserLogService; import com.eactive.apim.portal.invitation.entity.UserInvitation; import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.entity.UserPasswordHistory; @@ -38,6 +40,7 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess private final UserPasswordHistoryRepository passwordHistoryRepository; private final MessageRequestRepository messageRequestRepository; private final UserInvitationRepository userInvitationRepository; + private final PortalOrgRepository portalOrgRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, @@ -78,18 +81,23 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess session.setAttribute("redirectUrl", contextPath + "/new_password"); } - // 초대 코드 확인 - ROLE_USER만 확인 + // 초대 코드 확인 - ROLE_USER만 확인 (세션에 저장하여 메인 페이지에서 팝업으로 표시) if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_USER) { Optional pendingInvitation = userInvitationRepository.findFirstByInvitationEmailAndStatus( user.getLoginId(), InvitationStatus.PENDING); if (pendingInvitation.isPresent()) { - // 만료되지 않은 초대가 있으면 자동으로 초대 수락 페이지로 이동 UserInvitation invitation = pendingInvitation.get(); if (invitation.getExpiresOn().isAfter(LocalDateTime.now())) { - response.sendRedirect(contextPath + "/signup/decision?invitation=" + invitation.getToken()); - return; + // 세션에 초대 정보 저장 (메인 페이지에서 팝업으로 표시) + session.setAttribute("pendingInvitation", true); + session.setAttribute("pendingInvitationToken", invitation.getToken()); + // orgId로 기관명 조회 + String orgName = portalOrgRepository.findById(invitation.getOrgId()) + .map(PortalOrg::getOrgName) + .orElse("알 수 없는 기관"); + session.setAttribute("pendingInvitationOrgName", orgName); } } } diff --git a/src/main/resources/templates/views/apps/login/login.html b/src/main/resources/templates/views/apps/login/login.html index 9b6ccbc..fa154a7 100644 --- a/src/main/resources/templates/views/apps/login/login.html +++ b/src/main/resources/templates/views/apps/login/login.html @@ -70,13 +70,6 @@ 회원가입 -
  • - | -
  • -
  • - - 초대를 받으셨나요? -
  • diff --git a/src/main/resources/templates/views/apps/main/index.html b/src/main/resources/templates/views/apps/main/index.html index 30036cd..e78150d 100644 --- a/src/main/resources/templates/views/apps/main/index.html +++ b/src/main/resources/templates/views/apps/main/index.html @@ -299,6 +299,20 @@ customPopups.hideAlert('#customAlert'); window.location.href = redirectUrl; }); + return; // 다른 체크 중단 + } + + // 초대 대기 확인 (로그인할 때마다 표시) + const pendingInvitation = [[${session.pendingInvitation}]]; + if (pendingInvitation) { + const invitationToken = /*[[${session.pendingInvitationToken}]]*/ ''; + const orgName = /*[[${session.pendingInvitationOrgName}]]*/ ''; + + customPopups.showConfirm(`${orgName}에서 초대가 도착했습니다.
    초대를 확인하시겠습니까?`, function (selection) { + if (selection) { + window.location.href = '/signup/decision?invitation=' + invitationToken; + } + }); } });