Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67ec6d2103 | |||
| b024f8e86f | |||
| fd16300553 | |||
| 815c329e3c | |||
| 7fdd70dbc7 | |||
| 3dd9edc849 | |||
| 6cff96f40a | |||
| a97609d533 | |||
| 4482f6f807 | |||
| 34f5a89f78 | |||
| 9238584cb9 | |||
| 23235bb854 | |||
| 2d67647c8f | |||
| 4824149ba9 | |||
| 4927c301f3 | |||
| d9830fb47d | |||
| 6a1fade68d | |||
| 357d609552 | |||
| cce9753e36 | |||
| 06fdcb8bb6 | |||
| efb88caab5 | |||
| fb28a31da1 | |||
| b38ca6fe0b | |||
| 86336d52f0 | |||
| 53ca4a2a26 | |||
| 36a59b2b64 | |||
| cdf57c130c | |||
| 7e123c088f | |||
| daafbb596a | |||
| 0ffee7a101 | |||
| b47f76a783 | |||
| f591c5c81d | |||
| a0ea62f8cc | |||
| 157c66ca9d | |||
| ba5b2e61a5 | |||
| 5deda2a004 |
@@ -0,0 +1,180 @@
|
||||
name: eapim-portal CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- stage
|
||||
- feats/ci-test
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, linux, eapim-portal]
|
||||
env:
|
||||
JAVA_HOME: /apps/opts/jdk8
|
||||
JAVA_HOME_TOMCAT: /apps/opts/jdk17
|
||||
CATALINA_BASE: /prod/eapim/devportal
|
||||
CATALINA_HOME: /prod/eapim/apache-tomcat-9.0.116
|
||||
CATALINA_PID: /prod/eapim/devportal/temp/catalina.pid
|
||||
DEPLOY_HTTP_PORT: "39130"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: eapim-portal
|
||||
|
||||
steps:
|
||||
- name: Checkout eapim-portal (this branch)
|
||||
# ref 미지정 — 트리거된 브랜치/commit(github.sha) 그대로 빌드.
|
||||
uses: http://172.30.1.50:3000/actions/checkout@v4
|
||||
with:
|
||||
path: eapim-portal
|
||||
|
||||
- name: Checkout elink-portal-common (master, dependency)
|
||||
# 의존 모듈은 항상 master 고정.
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
rm -rf elink-portal-common
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
|
||||
- name: Checkout elink-online-core-jpa (master, dependency)
|
||||
# 의존 모듈은 항상 master 고정.
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
mkdir -p eapim-online
|
||||
rm -rf eapim-online/elink-online-core-jpa
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
|
||||
- name: Verify toolchain
|
||||
run: |
|
||||
java -version
|
||||
gradle --version
|
||||
|
||||
- name: Gradle build (no tests)
|
||||
run: gradle clean build -x test --no-daemon
|
||||
|
||||
- name: Upload WAR artifacts
|
||||
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
|
||||
with:
|
||||
name: eapim-portal-wars-${{ github.sha }}
|
||||
path: |
|
||||
eapim-portal/build/libs/eapim-portal.war
|
||||
eapim-portal/build/libs/eapim-portal-boot.war
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish WAR to Gitea Generic Package
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
GITEA_URL: http://172.30.1.50:3000
|
||||
PKG_OWNER: djb-eapim
|
||||
PKG_NAME: eapim-portal
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
VERSION="$(TZ=Asia/Seoul date +%Y-%m-%d-%H%M)-${SHORT_SHA}"
|
||||
BASE="$GITEA_URL/api/packages/$PKG_OWNER/generic/$PKG_NAME/$VERSION"
|
||||
|
||||
echo "package version: $VERSION"
|
||||
echo "endpoint base : $BASE"
|
||||
|
||||
for WAR in eapim-portal.war eapim-portal-boot.war; do
|
||||
SRC="eapim-portal/build/libs/$WAR"
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "::error::missing artifact: $SRC"
|
||||
exit 1
|
||||
fi
|
||||
HTTP=$(curl -sS -o "/tmp/pkg-upload-$WAR.log" -w '%{http_code}' \
|
||||
--user "oauth2:${{ secrets.CI_TOKEN }}" \
|
||||
--upload-file "$SRC" \
|
||||
"$BASE/$WAR")
|
||||
if [ "$HTTP" != "201" ] && [ "$HTTP" != "200" ]; then
|
||||
echo "::error::upload failed for $WAR (HTTP $HTTP)"
|
||||
cat "/tmp/pkg-upload-$WAR.log" || true
|
||||
exit 1
|
||||
fi
|
||||
echo " uploaded: $WAR (HTTP $HTTP)"
|
||||
done
|
||||
|
||||
echo "published: $GITEA_URL/$PKG_OWNER/-/packages/generic/$PKG_NAME/$VERSION"
|
||||
|
||||
- name: Stop tomcat (pre-clean)
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
# 1) systemd 관리 인스턴스 정지 (이번 워크플로가 띄운 것)
|
||||
systemctl --user stop eapim-portal 2>/dev/null || true
|
||||
|
||||
# 2) systemd 외부 detached 인스턴스 (eapim-boot-start.sh 등) 정리
|
||||
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null; then
|
||||
JAVA_HOME=$JAVA_HOME_TOMCAT "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
|
||||
for i in $(seq 1 30); do
|
||||
[ -f "$CATALINA_PID" ] && kill -0 "$(cat $CATALINA_PID)" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
rm -f "$CATALINA_PID"
|
||||
|
||||
- name: Deploy war as ROOT
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
DEPLOY_DIR="$CATALINA_BASE/webapps"
|
||||
rm -rf "$DEPLOY_DIR/ROOT" "$DEPLOY_DIR/ROOT.war"
|
||||
cp eapim-portal/build/libs/eapim-portal.war "$DEPLOY_DIR/ROOT.war"
|
||||
ls -la "$DEPLOY_DIR"
|
||||
|
||||
- name: Start tomcat and readiness probe (timeout 120s)
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||
BEFORE=0
|
||||
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
|
||||
|
||||
# systemd 가 Tomcat 을 새 cgroup 으로 띄움 — runner step 종료와 무관하게 살아남음
|
||||
systemctl --user start eapim-portal
|
||||
|
||||
DEADLINE=$(($(date +%s) + 120))
|
||||
LIVE_OK=0
|
||||
STATUS=000
|
||||
BODY=""
|
||||
while [ $(date +%s) -lt $DEADLINE ]; do
|
||||
# Stage 1: liveness — servlet 응답 가능?
|
||||
if [ "$LIVE_OK" = "0" ]; then
|
||||
LIVE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
http://localhost:$DEPLOY_HTTP_PORT/health 2>/dev/null || echo "000")
|
||||
if [ "$LIVE" = "200" ]; then
|
||||
echo "Liveness OK (/health)"
|
||||
LIVE_OK=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Stage 2: readiness — DB(EMS, Gateway)까지 검증
|
||||
if [ "$LIVE_OK" = "1" ]; then
|
||||
BODY=$(curl -sS -o /tmp/ready-body.json -w '%{http_code}' --max-time 3 \
|
||||
http://localhost:$DEPLOY_HTTP_PORT/health/ready 2>/dev/null || echo "000")
|
||||
STATUS=$BODY
|
||||
if [ "$STATUS" = "200" ]; then
|
||||
echo "Readiness OK (/health/ready)"
|
||||
cat /tmp/ready-body.json
|
||||
echo
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
|
||||
echo "::error::Startup error detected in log"
|
||||
tail -c +$((BEFORE+1)) "$LOG" | tail -80
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "::error::Readiness probe failed within 120s (last HTTP status: $STATUS)"
|
||||
[ -f /tmp/ready-body.json ] && echo "--- last readiness body ---" && cat /tmp/ready-body.json && echo
|
||||
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- key boot log lines ---"
|
||||
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
|
||||
@@ -0,0 +1,60 @@
|
||||
name: eapim-portal E2E Test
|
||||
|
||||
# 수동 트리거 전용. e2e 태그가 붙은 Selenium/통합 테스트만 실행.
|
||||
# 요구 인프라:
|
||||
# - Chrome + libgbm/libxkbcommon-x11 (Selenium 4종)
|
||||
# - Oracle JDBC 또는 testcontainers (SignupFileTest)
|
||||
# 미설치 환경에서는 SessionNotCreated/DB 에러로 일부 케이스가 실패할 수 있음.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: [self-hosted, linux, eapim-portal]
|
||||
env:
|
||||
JAVA_HOME: /apps/opts/jdk8
|
||||
defaults:
|
||||
run:
|
||||
working-directory: eapim-portal
|
||||
|
||||
steps:
|
||||
- name: Checkout eapim-portal (this branch)
|
||||
uses: http://172.30.1.50:3000/actions/checkout@v4
|
||||
with:
|
||||
path: eapim-portal
|
||||
|
||||
- name: Checkout elink-portal-common (master, dependency)
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
rm -rf elink-portal-common
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
|
||||
- name: Checkout elink-online-core-jpa (master, dependency)
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
mkdir -p eapim-online
|
||||
rm -rf eapim-online/elink-online-core-jpa
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
|
||||
- name: Verify toolchain
|
||||
run: |
|
||||
java -version
|
||||
gradle --version
|
||||
|
||||
- name: Gradle e2e test
|
||||
run: gradle test --no-daemon -PincludeE2E=true
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
|
||||
with:
|
||||
name: eapim-portal-e2e-${{ github.sha }}
|
||||
path: |
|
||||
eapim-portal/build/reports/tests/test/
|
||||
eapim-portal/build/test-results/test/
|
||||
if-no-files-found: warn
|
||||
@@ -0,0 +1,63 @@
|
||||
name: eapim-portal Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- stage
|
||||
- feats/ci-test
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: [self-hosted, linux, eapim-portal]
|
||||
env:
|
||||
JAVA_HOME: /apps/opts/jdk8
|
||||
defaults:
|
||||
run:
|
||||
working-directory: eapim-portal
|
||||
|
||||
steps:
|
||||
- name: Checkout eapim-portal (this branch)
|
||||
# ref 미지정 — 트리거된 브랜치/commit(github.sha) 그대로 빌드.
|
||||
uses: http://172.30.1.50:3000/actions/checkout@v4
|
||||
with:
|
||||
path: eapim-portal
|
||||
|
||||
- name: Checkout elink-portal-common (master, dependency)
|
||||
# 의존 모듈은 항상 master 고정.
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
rm -rf elink-portal-common
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
|
||||
- name: Checkout elink-online-core-jpa (master, dependency)
|
||||
# 의존 모듈은 항상 master 고정.
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
mkdir -p eapim-online
|
||||
rm -rf eapim-online/elink-online-core-jpa
|
||||
git clone --depth=1 --branch master \
|
||||
http://oauth2:${{ secrets.CI_TOKEN }}@172.30.1.50:3000/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
|
||||
- name: Verify toolchain
|
||||
run: |
|
||||
java -version
|
||||
gradle --version
|
||||
|
||||
- name: Gradle test
|
||||
run: gradle test --no-daemon
|
||||
|
||||
- name: Upload test results
|
||||
# 실패해도 결과 리포트는 올려서 원인 분석 가능하게.
|
||||
if: always()
|
||||
uses: http://172.30.1.50:3000/actions/upload-artifact@v3
|
||||
with:
|
||||
name: eapim-portal-test-${{ github.sha }}
|
||||
path: |
|
||||
eapim-portal/build/reports/tests/test/
|
||||
eapim-portal/build/test-results/test/
|
||||
if-no-files-found: warn
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
**기술 스택:**
|
||||
- Spring Boot 2.7.18 with Spring MVC and Thymeleaf
|
||||
- Java 8 (targetCompatibility: 1.8)
|
||||
- Java 8 (sourceCompatibility/targetCompatibility: 1.8)
|
||||
- Gradle 8.7 빌드 시스템
|
||||
- Oracle 19c 데이터베이스 (JNDI를 통한 이중 데이터소스)
|
||||
- Spring Security (커스텀 인증)
|
||||
@@ -59,7 +59,7 @@ git push origin jenkins_with_weblogic
|
||||
|
||||
### 애플리케이션 실행
|
||||
- gradlew 사용 금지 - offline gradle 단독 실행
|
||||
- msi-gf63 컴퓨터에서는 /c/eactive/workspaces/shell-scripts/kjb-gradle.sh 로 gradle 실행 (jdk, gradle 등 환경 변수 세팅 후 gradle 실행하는 스크립트)
|
||||
- JDK/Gradle 환경 변수는 저장소 루트의 `.envrc`(direnv)로 자동 설정됨 (JAVA_HOME → Zulu JDK 8, GRADLE_USER_HOME → 격리 디렉터리)
|
||||
|
||||
```bash
|
||||
# dev 프로파일로 실행 (기본값)
|
||||
@@ -173,13 +173,13 @@ com.eactive.apim.portal/
|
||||
|
||||
1. **EMS Database** (Portal/Admin)
|
||||
- JNDI: `jdbc/dsOBP_EMS`
|
||||
- Schema: `EMSADM`
|
||||
- Schema: `EMSAPP`
|
||||
- Entities: `com.eactive.apim.portal.apps.*`
|
||||
- 목적: 포털 사용자, 조직, API 키, 승인
|
||||
|
||||
2. **Gateway Database** (API Specs)
|
||||
- JNDI: `jdbc/dsOBP_AGW`
|
||||
- Schema: `AGWADM`
|
||||
- Schema: `AGWAPP`
|
||||
- Entities: `com.eactive.eai.data.entity.onl.*`
|
||||
- 목적: API 명세, 서비스, 메시지
|
||||
|
||||
@@ -265,6 +265,41 @@ API 키는 승인 워크플로우와 함께 관리됩니다:
|
||||
8. `src/main/resources/templates/views/newfeature/`에 Thymeleaf 템플릿 추가
|
||||
9. `application.yml`의 `pages` 섹션에 라우트 설정 (또는 `@GetMapping`/`@PostMapping`으로 직접 매핑)
|
||||
|
||||
### 메뉴/네비게이션 항목 추가하기
|
||||
|
||||
**중요:** 메뉴 정의가 **두 곳으로 분리**되어 있다. yml 한 곳만 고치면 헤더 드롭다운에 안 보이고, 헤더만 고치면 브레드크럼이 비어 보인다. 메뉴 추가/이름 변경/순서 변경 시 **반드시 두 파일을 같이 수정**한다.
|
||||
|
||||
1. **`application.yml`의 `page:` 트리** (브레드크럼·페이지 메타용)
|
||||
- `PageService`가 URL → breadcrumb 매핑에 사용
|
||||
- 부모 메뉴(`service`, `apis`, `community` 등) 아래 `children:`에 새 키를 추가
|
||||
```yaml
|
||||
page:
|
||||
home:
|
||||
children:
|
||||
service:
|
||||
children:
|
||||
oauth2_guide:
|
||||
name: "OAuth2 인증가이드"
|
||||
path: "/service/oauth2-guide"
|
||||
```
|
||||
|
||||
2. **`templates/views/fragment/djbank/header_container.html`** (상단 글로벌 네비)
|
||||
- `<nav class="header-center"><ul class="nav-menu">` 블록에 정적 `<li>`로 하드코딩되어 있다 — yml 트리를 iterate 하지 **않음**
|
||||
- 부모 메뉴의 `<ul class="sub-menu">`에 `<li><a th:href="@{/...}">메뉴명</a></li>` 직접 추가
|
||||
|
||||
3. **라우트도 같이 등록** (`application.yml`의 `portal.pages` 매핑 섹션, 또는 `@GetMapping`)
|
||||
```yaml
|
||||
portal:
|
||||
pages:
|
||||
- path-pattern: /service/oauth2-guide
|
||||
method: GET
|
||||
view-name: apps/service/oauth2-guide
|
||||
```
|
||||
|
||||
4. **검증**: 변경 후 (a) 헤더 드롭다운에 항목이 보이는지, (b) 새 페이지 진입 시 브레드크럼에 메뉴명이 보이는지 둘 다 확인.
|
||||
|
||||
5. **재시작**: `application.yml` 변경은 `PortalPageHandlerMapper`가 `@PostConstruct`에서 한 번만 등록하므로 **자동 라이브 리로드 안 됨** — 수동 재시작 필요. HTML/CSS만 라이브 반영됨.
|
||||
|
||||
### MapStruct DTO 매핑
|
||||
|
||||
타입 안전한 DTO 변환을 위해 MapStruct 사용:
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
pipeline {
|
||||
agent { label 'djb-vm' }
|
||||
|
||||
parameters {
|
||||
string(name: 'BRANCH', defaultValue: 'master', description: 'Branch to deploy')
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||||
}
|
||||
|
||||
environment {
|
||||
JAVA_HOME = '/apps/opts/jdk8'
|
||||
JAVA_HOME_TOMCAT = '/apps/opts/jdk17'
|
||||
GRADLE_HOME = '/apps/opts/gradle-8.7'
|
||||
GRADLE_USER_HOME = '/apps/opts/gradle-home'
|
||||
NODE_HOME = '/apps/opts/node-v24'
|
||||
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}"
|
||||
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||
CATALINA_BASE = '/prod/eapim/devportal'
|
||||
CATALINA_HOME = '/prod/eapim/apache-tomcat-9.0.116'
|
||||
CATALINA_PID = '/prod/eapim/devportal/temp/catalina.pid'
|
||||
DEPLOY_HTTP_PORT = '39130'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: "*/${params.BRANCH}"]],
|
||||
userRemoteConfigs: [[url: 'ssh://git@172.30.1.50:2222/djb-eapim/eapim-portal.git']]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
stage('Checkout dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
cd "$WORKSPACE/.."
|
||||
|
||||
if [ ! -d elink-portal-common/.git ]; then
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
else
|
||||
git -C elink-portal-common fetch --depth=1 origin master
|
||||
git -C elink-portal-common reset --hard origin/master
|
||||
git -C elink-portal-common clean -fdx
|
||||
fi
|
||||
|
||||
mkdir -p eapim-online
|
||||
if [ ! -d eapim-online/elink-online-core-jpa/.git ]; then
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
else
|
||||
git -C eapim-online/elink-online-core-jpa fetch --depth=1 origin master
|
||||
git -C eapim-online/elink-online-core-jpa reset --hard origin/master
|
||||
git -C eapim-online/elink-online-core-jpa clean -fdx
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
java -version
|
||||
gradle --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build WAR') {
|
||||
steps {
|
||||
sh 'gradle clean build -x test --no-daemon'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
sh '''
|
||||
set -eu
|
||||
cd build/libs
|
||||
sha1sum eapim-portal.war eapim-portal-boot.war > SHA1SUMS
|
||||
sha256sum eapim-portal.war eapim-portal-boot.war > SHA256SUMS
|
||||
'''
|
||||
archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/SHA1SUMS,build/libs/SHA256SUMS', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Stop Tomcat') {
|
||||
steps {
|
||||
sh '''
|
||||
set +e
|
||||
systemctl --user stop eapim-portal 2>/dev/null
|
||||
|
||||
if [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null; then
|
||||
JAVA_HOME="$JAVA_HOME_TOMCAT" "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true
|
||||
for i in $(seq 1 30); do
|
||||
[ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
rm -f "$CATALINA_PID"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy ROOT.war') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
DEPLOY_DIR="$CATALINA_BASE/webapps"
|
||||
rm -rf "$DEPLOY_DIR/ROOT" "$DEPLOY_DIR/ROOT.war"
|
||||
cp build/libs/eapim-portal.war "$DEPLOY_DIR/ROOT.war"
|
||||
ls -la "$DEPLOY_DIR"
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Start Tomcat and readiness') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log"
|
||||
BEFORE=0
|
||||
[ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG")
|
||||
|
||||
systemctl --user start eapim-portal
|
||||
|
||||
DEADLINE=$(($(date +%s) + 120))
|
||||
LIVE_OK=0
|
||||
STATUS=000
|
||||
|
||||
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||
if [ "$LIVE_OK" = "0" ]; then
|
||||
LIVE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \
|
||||
"http://localhost:$DEPLOY_HTTP_PORT/health" 2>/dev/null || echo "000")
|
||||
if [ "$LIVE" = "200" ]; then
|
||||
echo "Liveness OK (/health)"
|
||||
LIVE_OK=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$LIVE_OK" = "1" ]; then
|
||||
STATUS=$(curl -sS -o /tmp/eapim-ready-body.json -w '%{http_code}' --max-time 3 \
|
||||
"http://localhost:$DEPLOY_HTTP_PORT/health/ready" 2>/dev/null || echo "000")
|
||||
if [ "$STATUS" = "200" ]; then
|
||||
echo "Readiness OK (/health/ready)"
|
||||
cat /tmp/eapim-ready-body.json
|
||||
echo
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then
|
||||
echo "Startup error detected in log"
|
||||
tail -c +$((BEFORE+1)) "$LOG" | tail -80
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "Readiness probe failed within 120s, last HTTP status: $STATUS"
|
||||
[ -f /tmp/eapim-ready-body.json ] && cat /tmp/eapim-ready-body.json && echo
|
||||
[ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- key boot log lines ---"
|
||||
tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
pipeline {
|
||||
agent { label 'djb-vm' }
|
||||
|
||||
triggers {
|
||||
pollSCM('H/10 * * * *')
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20'))
|
||||
}
|
||||
|
||||
environment {
|
||||
JAVA_HOME = '/apps/opts/jdk8'
|
||||
JAVA_HOME_TOMCAT = '/apps/opts/jdk17'
|
||||
GRADLE_HOME = '/apps/opts/gradle-8.7'
|
||||
GRADLE_USER_HOME = '/apps/opts/gradle-home'
|
||||
NODE_HOME = '/apps/opts/node-v24'
|
||||
PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}"
|
||||
GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
|
||||
stage('Checkout dependencies') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
cd "$WORKSPACE/.."
|
||||
|
||||
if [ ! -d elink-portal-common/.git ]; then
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-portal-common.git \
|
||||
elink-portal-common
|
||||
else
|
||||
git -C elink-portal-common fetch --depth=1 origin master
|
||||
git -C elink-portal-common reset --hard origin/master
|
||||
git -C elink-portal-common clean -fdx
|
||||
fi
|
||||
|
||||
mkdir -p eapim-online
|
||||
if [ ! -d eapim-online/elink-online-core-jpa/.git ]; then
|
||||
git clone --depth=1 --branch master \
|
||||
ssh://git@172.30.1.50:2222/djb-eapim/elink-online-core-jpa.git \
|
||||
eapim-online/elink-online-core-jpa
|
||||
else
|
||||
git -C eapim-online/elink-online-core-jpa fetch --depth=1 origin master
|
||||
git -C eapim-online/elink-online-core-jpa reset --hard origin/master
|
||||
git -C eapim-online/elink-online-core-jpa clean -fdx
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify toolchain') {
|
||||
steps {
|
||||
sh '''
|
||||
set -eu
|
||||
java -version
|
||||
gradle --version
|
||||
node --version || true
|
||||
npm --version || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'gradle clean test --no-daemon'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit allowEmptyResults: true, testResults: 'build/test-results/test/*.xml'
|
||||
archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/tests/test/**,build/test-results/test/**'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build WAR') {
|
||||
steps {
|
||||
sh 'gradle build -x test --no-daemon'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
sh '''
|
||||
set -eu
|
||||
cd build/libs
|
||||
sha1sum eapim-portal.war eapim-portal-boot.war > SHA1SUMS
|
||||
sha256sum eapim-portal.war eapim-portal-boot.war > SHA256SUMS
|
||||
'''
|
||||
archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/SHA1SUMS,build/libs/SHA256SUMS', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,10 +69,17 @@ eapim-portal
|
||||
|
||||
### 사전 요구사항
|
||||
|
||||
- JDK 8 이상
|
||||
- Gradle 8.7
|
||||
아래 도구 설치 및 검증 절차는 별도 문서로 분리되어 있습니다. 신규 개발자는 먼저 이 문서를 끝까지 진행하세요.
|
||||
|
||||
📄 **[개발환경 준비 사항](djb-docs/개발환경-준비-사항.md)** — JDK 8 · Gradle 8.7 · Git · Oracle 19c · Node.js & SASS · direnv · IDE 설정
|
||||
|
||||
요약:
|
||||
|
||||
- JDK 8 (Zulu 권장)
|
||||
- Gradle 8.7 (gradlew 사용 금지)
|
||||
- Git
|
||||
- Oracle 19c (또는 개발 환경에 따라 접근 가능한 DB)
|
||||
- Oracle 19c 접근 (개발: `192.168.240.177:1599`)
|
||||
- Node.js 18 LTS 이상 (SCSS 컴파일용)
|
||||
|
||||
### Git Bash에서 초기 세팅
|
||||
|
||||
@@ -90,10 +97,7 @@ export GIT_REPO_BASE="ssh://git@192.168.240.178:18081/eapim"
|
||||
# export GIT_REPO_BASE="https://git.eactive.synology.me:8090/kjb-eapim"
|
||||
|
||||
# Gradle 명령어 설정
|
||||
# 일반 환경: gradle
|
||||
# msi-gf63 환경: /c/eactive/workspaces/shell-scripts/kjb-gradle.sh
|
||||
export GRADLE_CMD="gradle"
|
||||
# export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh"
|
||||
|
||||
# ====================================
|
||||
# 2. 작업 디렉토리 생성
|
||||
@@ -128,12 +132,6 @@ git clone ${GIT_REPO_BASE}/elink-portal-common.git
|
||||
cd elink-portal-common
|
||||
git checkout master # 또는 필요한 브랜치
|
||||
|
||||
# kjb-safedb 클론
|
||||
cd $WORKSPACE_DIR
|
||||
git clone ${GIT_REPO_BASE}/kjb-safedb.git
|
||||
cd kjb-safedb
|
||||
git checkout master # 또는 필요한 브랜치
|
||||
|
||||
# ====================================
|
||||
# 6. 최종 폴더 구조 확인
|
||||
# ====================================
|
||||
@@ -142,7 +140,6 @@ git checkout master # 또는 필요한 브랜치
|
||||
# ├── eapim-online/
|
||||
# │ └── elink-online-core-jpa/
|
||||
# ├── elink-portal-common/
|
||||
# └── kjb-safedb/
|
||||
|
||||
# ====================================
|
||||
# 7. eapim-portal 프로젝트로 이동 및 디펜던시 다운로드
|
||||
@@ -169,10 +166,7 @@ set GIT_REPO_BASE=ssh://git@192.168.240.178:18081/eapim
|
||||
REM set GIT_REPO_BASE=https://git.eactive.synology.me:8090/kjb-eapim
|
||||
|
||||
REM Gradle 명령어 설정
|
||||
REM 일반 환경: gradle
|
||||
REM msi-gf63 환경: C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
|
||||
set GRADLE_CMD=gradle
|
||||
REM set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
|
||||
|
||||
REM ====================================
|
||||
REM 2. 작업 디렉토리 생성
|
||||
@@ -207,12 +201,6 @@ git clone %GIT_REPO_BASE%/elink-portal-common.git
|
||||
cd elink-portal-common
|
||||
git checkout master
|
||||
|
||||
REM kjb-safedb 클론
|
||||
cd /d "%WORKSPACE_DIR%"
|
||||
git clone %GIT_REPO_BASE%/kjb-safedb.git
|
||||
cd kjb-safedb
|
||||
git checkout master
|
||||
|
||||
REM ====================================
|
||||
REM 6. 최종 폴더 구조 확인
|
||||
REM ====================================
|
||||
@@ -221,7 +209,6 @@ REM ├── eapim-portal\
|
||||
REM ├── eapim-online\
|
||||
REM │ └── elink-online-core-jpa\
|
||||
REM ├── elink-portal-common\
|
||||
REM └── kjb-safedb\
|
||||
|
||||
REM ====================================
|
||||
REM 7. eapim-portal 프로젝트로 이동 및 디펜던시 다운로드
|
||||
@@ -253,30 +240,19 @@ REM Gradle 디펜던시 다운로드
|
||||
|
||||
- **gradlew 사용 금지**: 반드시 시스템에 설치된 gradle을 직접 사용하거나 환경에 맞는 스크립트를 사용하세요
|
||||
- **Gradle 명령어 변수화**: 위의 `GRADLE_CMD` 변수를 설정하면 환경별로 다른 gradle 명령어 사용 가능
|
||||
- **SafeDB**: 암호화 기능이 필요한 경우 kjb-safedb 모듈 설정 확인
|
||||
|
||||
## 빌드 및 실행
|
||||
|
||||
### Gradle 명령어 설정
|
||||
|
||||
사용 환경에 따라 적절한 gradle 명령어를 설정하세요:
|
||||
|
||||
**Git Bash:**
|
||||
```bash
|
||||
# 일반 환경
|
||||
export GRADLE_CMD="gradle"
|
||||
|
||||
# msi-gf63 환경
|
||||
export GRADLE_CMD="/c/eactive/workspaces/shell-scripts/kjb-gradle.sh"
|
||||
```
|
||||
|
||||
**Windows CMD:**
|
||||
```cmd
|
||||
REM 일반 환경
|
||||
set GRADLE_CMD=gradle
|
||||
|
||||
REM msi-gf63 환경
|
||||
set GRADLE_CMD=C:\eactive\workspaces\shell-scripts\kjb-gradle.sh
|
||||
```
|
||||
|
||||
### 빌드 및 실행 명령어
|
||||
@@ -310,7 +286,6 @@ kjb-eapim/
|
||||
├── eapim-online/ # Online 프로젝트
|
||||
│ └── elink-online-core-jpa/ # Gateway 데이터 모델
|
||||
├── elink-portal-common/ # 공통 유틸리티
|
||||
└── kjb-safedb/ # SafeDB 암호화 라이브러리
|
||||
```
|
||||
|
||||
### eapim-portal 내부 구조
|
||||
@@ -327,7 +302,6 @@ src/main/java/com/eactive/apim/portal/
|
||||
이 프로젝트는 다음 모듈에 의존합니다 (settings.gradle 참조):
|
||||
- `eapim-online/elink-online-core-jpa`: Gateway 데이터 모델 (../eapim-online/elink-online-core-jpa)
|
||||
- `elink-portal-common`: 공통 유틸리티 (../elink-portal-common)
|
||||
- `kjb-safedb`: SafeDB 암호화 라이브러리 (../kjb-safedb)
|
||||
|
||||
## SASS / CSS 스타일 개발
|
||||
|
||||
@@ -339,8 +313,11 @@ src/main/java/com/eactive/apim/portal/
|
||||
|
||||
### 설치
|
||||
|
||||
Node.js 및 SASS 설치 절차는 [개발환경 준비 사항 § 6. Node.js & SASS 빌드 환경](djb-docs/개발환경-준비-사항.md#6-nodejs--sass-빌드-환경) 을 참고하세요.
|
||||
|
||||
Node.js가 이미 설치되어 있다면 프로젝트 의존성만 설치하면 됩니다:
|
||||
|
||||
```bash
|
||||
# Node.js가 설치된 상태에서
|
||||
npm install
|
||||
```
|
||||
|
||||
@@ -532,8 +509,6 @@ ls -lh src/main/resources/static/css/main.min.css # minified
|
||||
|
||||
- 사용자 관리 (내부/기업)
|
||||
- API 키 관리 및 승인 워크플로우
|
||||
- API 카탈로그 및 문서화
|
||||
- API 테스트 프록시
|
||||
- 감사 추적 (Hibernate Envers)
|
||||
|
||||
## 배포
|
||||
@@ -543,24 +518,9 @@ ls -lh src/main/resources/static/css/main.min.css # minified
|
||||
- WebLogic
|
||||
- Tomcat (내장)
|
||||
|
||||
### Docker
|
||||
```bash
|
||||
./build_docker.sh
|
||||
docker run -p 8080:8080 eapim-portal:latest
|
||||
```
|
||||
|
||||
## 관련 프로젝트
|
||||
|
||||
프로젝트 구조 (kjb-eapim 디렉토리 기준):
|
||||
- `eapim-portal/` - Portal 웹 애플리케이션 (현재 프로젝트)
|
||||
- `eapim-online/` - Online 프로젝트 (elink-online-core-jpa 포함)
|
||||
- `elink-portal-common/` - 공통 유틸리티 라이브러리
|
||||
- `kjb-safedb/` - SafeDB 암호화 라이브러리
|
||||
- `eapim-admin/` - Admin 포털 (선택사항)
|
||||
- `kjb-eapim-sql/` - SQL 스크립트 (선택사항)
|
||||
|
||||
## 문서
|
||||
|
||||
- **개발환경 준비 사항**: [`djb-docs/개발환경-준비-사항.md`](djb-docs/개발환경-준비-사항.md) — JDK·Gradle·Node.js·SASS 설치 가이드
|
||||
- **프로젝트 상세 지침**: `CLAUDE.md` (한글)
|
||||
- **사용자 가이드**: `개발자포탈.md` (한글)
|
||||
- **빌드 스크립트**: `build-gf63.sh`, `deploy_portal.sh`
|
||||
|
||||
@@ -128,11 +128,13 @@ bootRun {
|
||||
}
|
||||
|
||||
|
||||
// annotation processor 출력 디렉토리는 IntelliJ/Gradle 통합이 자동 등록함.
|
||||
// 명시적으로 srcDir 추가하면 IntelliJ idea 모드에서 MapStruct가 같은 파일을
|
||||
// 중복 생성하려다 javax.annotation.processing.FilerException 발생함.
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir 'src/main/java'
|
||||
srcDir 'build/generated/sources/annotationProcessor/java/main'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +158,13 @@ processResources {
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform {
|
||||
if (project.hasProperty('includeE2E') && project.includeE2E.toBoolean()) {
|
||||
includeTags 'e2e'
|
||||
} else {
|
||||
excludeTags 'e2e'
|
||||
}
|
||||
}
|
||||
enabled = true
|
||||
}
|
||||
|
||||
@@ -181,20 +189,4 @@ task printSourceSets {
|
||||
println " Output dir : ${srcSet.output.classesDirs.asPath}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoCli 실행 task (bcrypt 지원)
|
||||
task cryptoCli(type: JavaExec) {
|
||||
mainClass = 'com.eactive.ext.kjb.safedb.CryptoCli'
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
standardInput = System.in
|
||||
|
||||
// SafeDB 라이브러리 경로 추가 (리눅스)
|
||||
classpath += files("/safedb/JavaAPI/config")
|
||||
classpath += fileTree(dir: "/safedb/JavaAPI/lib", include: ["*.jar"])
|
||||
|
||||
// CLI 인자 전달: ./gradlew cryptoCli --args="bcrypt hash password123"
|
||||
if (project.hasProperty('args')) {
|
||||
args project.args.split('\\s+')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
# 개발환경 준비 사항
|
||||
|
||||
EAPIM Portal 개발에 필요한 도구와 런타임을 설치하고 검증하는 절차입니다.
|
||||
처음 환경을 세팅하는 신규 개발자는 이 문서를 끝까지 따라 진행한 뒤 [README.md](../README.md)의 "초기 세팅 가이드" 단계를 이어서 수행하세요.
|
||||
|
||||
---
|
||||
|
||||
## 0. 한눈에 보는 요구사항
|
||||
|
||||
| 구분 | 도구 | 권장 버전 | 비고 |
|
||||
|------|------|-----------|----------------------------------|
|
||||
| 백엔드 | JDK | 1.8 (Zulu 8 권장) | `sourceCompatibility=1.8` |
|
||||
| 빌드 | Gradle | 8.7 | gradlew 사용 금지 — 시스템 gradle 직접 사용 |
|
||||
| 형상관리 | Git | 2.30 이상 | SSH 키 등록 권장 |
|
||||
| DB | Oracle Client/접근 | 19c | 개발은 `192.168.240.177:1598` |
|
||||
| 프론트엔드 빌드 | Node.js | 18 LTS 이상 | SCSS 컴파일 전용 |
|
||||
| 프론트엔드 빌드 | npm | Node.js 함께 설치 | `sass` 패키지 사용 |
|
||||
| (선택) 환경 변수 | direnv | 최신 | `.envrc` 자동 로드 |
|
||||
| (선택) IDE | IntelliJ IDEA | 2023.x 이상 | Lombok / MapStruct 플러그인 |
|
||||
|
||||
---
|
||||
|
||||
## 1. JDK 8 설치
|
||||
|
||||
Java 8 (1.8) 호환이 필요합니다. Azul Zulu JDK 8 사용을 권장합니다.
|
||||
|
||||
### macOS (Homebrew)
|
||||
|
||||
```bash
|
||||
brew install --cask zulu@8
|
||||
# 설치 후 경로 확인
|
||||
/usr/libexec/java_home -v 1.8
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
1. [Azul Zulu JDK 8 다운로드](https://www.azul.com/downloads/?version=java-8-lts&package=jdk)
|
||||
2. MSI 설치 후 `JAVA_HOME` 환경변수 등록
|
||||
3. `PATH`에 `%JAVA_HOME%\bin` 추가
|
||||
|
||||
### 검증
|
||||
|
||||
```bash
|
||||
java -version # 1.8.x
|
||||
javac -version # 1.8.x
|
||||
echo $JAVA_HOME # macOS/Linux
|
||||
echo %JAVA_HOME% # Windows
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Gradle 8.7 설치
|
||||
|
||||
이 프로젝트는 **gradlew 사용을 금지**합니다. 시스템에 설치된 gradle 8.7을 직접 사용합니다.
|
||||
|
||||
### macOS (Homebrew + SDKMAN 권장)
|
||||
|
||||
```bash
|
||||
# SDKMAN 설치 (이미 있으면 생략)
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
|
||||
# Gradle 8.7 설치
|
||||
sdk install gradle 8.7
|
||||
sdk default gradle 8.7
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
1. [Gradle 8.7 Binary-only 다운로드](https://gradle.org/releases/)
|
||||
2. `C:\gradle\gradle-8.7` 경로에 압축 해제
|
||||
3. `GRADLE_HOME=C:\gradle\gradle-8.7` 환경변수 등록
|
||||
4. `PATH`에 `%GRADLE_HOME%\bin` 추가
|
||||
|
||||
### 검증
|
||||
|
||||
```bash
|
||||
gradle -v
|
||||
# Gradle 8.7
|
||||
# JVM: 1.8.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. (권장) direnv 사용
|
||||
|
||||
저장소 루트의 `.envrc` 가 `JAVA_HOME`, `GRADLE_USER_HOME` 등을 자동 주입합니다.
|
||||
디렉터리 진입만으로 환경이 격리되므로 다른 프로젝트와 JDK가 섞이지 않습니다.
|
||||
|
||||
### 설치
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install direnv
|
||||
|
||||
# Windows (scoop)
|
||||
scoop install direnv
|
||||
```
|
||||
|
||||
### 셸 훅 등록 (한 번만)
|
||||
|
||||
```bash
|
||||
# zsh
|
||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
|
||||
|
||||
# bash
|
||||
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
|
||||
```
|
||||
|
||||
### 프로젝트에서 허용
|
||||
|
||||
```bash
|
||||
cd /path/to/eapim-portal
|
||||
direnv allow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Git 설치
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
brew install git
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
[Git for Windows](https://git-scm.com/download/win) 설치. Git Bash가 함께 설치되어 README의 Bash 스니펫을 그대로 사용할 수 있습니다.
|
||||
|
||||
### SSH 키 등록 (사내 Git 서버용)
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "your.email@djbank.co.kr"
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
# 출력된 공개키를 사내 Git 서버(192.168.240.178:18081)에 등록
|
||||
```
|
||||
|
||||
### 검증
|
||||
|
||||
```bash
|
||||
git --version
|
||||
ssh -T git@192.168.240.178 -p 18081
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Oracle 19c 접근 정보
|
||||
|
||||
개발 환경에서는 별도 Oracle 설치 없이 사내 개발 DB에 접속합니다.
|
||||
|
||||
- **Host**: `192.168.240.177`
|
||||
- **Port**: `1599`
|
||||
- **Schemas**: `EMSADM` (Portal), `AGWADM` (Gateway)
|
||||
- 상세 계정/패스워드는 사내 비밀번호 관리자에게 문의
|
||||
|
||||
스테이징/운영은 JNDI를 통해 접근합니다 — `jdbc/dsOBP_EMS`, `jdbc/dsOBP_AGW`.
|
||||
|
||||
DB 클라이언트 도구는 자유 선택 (DBeaver, DataGrip, SQL Developer 등).
|
||||
|
||||
---
|
||||
|
||||
## 6. Node.js & SASS 빌드 환경
|
||||
|
||||
SCSS 파일을 CSS로 컴파일하기 위해 Node.js 환경이 필요합니다.
|
||||
`src/main/resources/static/css/main.css` 는 SASS 컴파일 결과물이므로 **반드시 SCSS 파일을 수정한 뒤 컴파일**해야 합니다.
|
||||
|
||||
### 6.1 Node.js 설치 (LTS 18 이상)
|
||||
|
||||
#### macOS (Homebrew)
|
||||
|
||||
```bash
|
||||
brew install node
|
||||
```
|
||||
|
||||
#### macOS / Linux (nvm 권장 — 버전 관리 용이)
|
||||
|
||||
```bash
|
||||
# nvm 설치
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
||||
exec $SHELL -l
|
||||
|
||||
# Node.js LTS 설치
|
||||
nvm install --lts
|
||||
nvm use --lts
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
1. [Node.js LTS 다운로드](https://nodejs.org/en/download) 후 MSI 설치
|
||||
2. 또는 [nvm-windows](https://github.com/coreybutler/nvm-windows/releases) 사용
|
||||
|
||||
#### 검증
|
||||
|
||||
```bash
|
||||
node -v # v18.x 이상
|
||||
npm -v # 9.x 이상
|
||||
```
|
||||
|
||||
### 6.2 프로젝트 SASS 의존성 설치
|
||||
|
||||
```bash
|
||||
cd /path/to/eapim-portal
|
||||
npm install
|
||||
```
|
||||
|
||||
`package.json` 의 `devDependencies.sass` (Dart Sass) 가 `node_modules/` 에 설치됩니다.
|
||||
|
||||
### 6.3 SASS 빌드 명령어
|
||||
|
||||
```bash
|
||||
# 1회 컴파일 (개발용, 들여쓰기 유지)
|
||||
npm run sass:build
|
||||
|
||||
# 1회 컴파일 (배포용, minified)
|
||||
npm run sass:build:minified
|
||||
|
||||
# 파일 변경 감지 후 자동 컴파일 (개발 중 권장)
|
||||
npm run sass:watch
|
||||
|
||||
# 빌드 + minified 동시 생성
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 6.4 SASS 컴파일 검증
|
||||
|
||||
```bash
|
||||
ls -lh src/main/resources/static/css/main.css
|
||||
ls -lh src/main/resources/static/css/main.min.css
|
||||
```
|
||||
|
||||
파일이 갱신되었고 브라우저에서 스타일이 정상 적용되면 성공입니다.
|
||||
|
||||
### 6.5 SASS 작업 시 주의사항
|
||||
|
||||
- `src/main/resources/static/css/main.css` **직접 수정 금지** — 재컴파일 시 덮어씌워짐
|
||||
- SCSS 파일은 `src/main/resources/static/sass/` 하위에서만 작성
|
||||
- 새 파일 추가 시 파일명 앞에 언더스코어(`_`) 필수 (`_filename.scss`)
|
||||
- `@import` 대신 `@use` 사용 (Dart Sass 3.0 호환)
|
||||
|
||||
---
|
||||
|
||||
## 7. (선택) IDE 설정
|
||||
|
||||
### IntelliJ IDEA
|
||||
|
||||
1. **Lombok 플러그인** 설치 → `Settings → Plugins → Lombok`
|
||||
2. **Annotation Processing 활성화** → `Settings → Build → Compiler → Annotation Processors → Enable annotation processing`
|
||||
3. **Gradle JVM** 설정 → `Settings → Build → Build Tools → Gradle → Gradle JVM` 을 JDK 1.8 (Zulu) 로 지정
|
||||
4. **MapStruct** 는 별도 플러그인 불필요 (annotation processor 만 활성화되어 있으면 됨)
|
||||
|
||||
### VS Code
|
||||
|
||||
- `Extension Pack for Java`
|
||||
- `Lombok Annotations Support`
|
||||
- `Sass` (Syler) — SCSS 문법 하이라이팅
|
||||
|
||||
---
|
||||
|
||||
## 8. 최종 체크리스트
|
||||
|
||||
아래 명령이 모두 정상 동작하면 개발환경 준비가 완료된 것입니다.
|
||||
|
||||
```bash
|
||||
java -version # 1.8.x
|
||||
gradle -v # 8.7 / JVM 1.8
|
||||
git --version
|
||||
node -v # v18 이상
|
||||
npm -v
|
||||
direnv version # (선택)
|
||||
```
|
||||
|
||||
이후 [README.md](../README.md) 의 "초기 세팅 가이드" 로 이동해 프로젝트 클론과 의존 모듈 설치를 진행하세요.
|
||||
@@ -1,91 +0,0 @@
|
||||
pipeline {
|
||||
// 25.10.01 : 기본 브랜치 staging -> jenkins_with_weblogic 로 변경
|
||||
|
||||
agent { label 'apipod01' }
|
||||
|
||||
triggers {
|
||||
pollSCM('H/5 * * * *')
|
||||
}
|
||||
|
||||
environment {
|
||||
// Java 환경 설정
|
||||
JAVA_HOME = '/App/jenkins/jdks/jdk1.8.0_441'
|
||||
PATH = "${JAVA_HOME}/bin:${env.PATH}"
|
||||
|
||||
// Gradle 환경 설정
|
||||
GRADLE_HOME = '/App/jenkins/gradle-8.7'
|
||||
GRADLE_USER_HOME = '/App/jenkins/gradle-home'
|
||||
// GRADLE_OPTS = '--offline'
|
||||
|
||||
// Git 저장소 정보
|
||||
GIT_SERVER = '192.168.240.178:18081'
|
||||
GIT_CREDENTIALS_ID = 'git-ssh-credentials'
|
||||
|
||||
// 프로젝트 디렉토리 구조
|
||||
PROJECT_HOME = '/Data/jenkins/eapim'
|
||||
|
||||
BRANCH = 'jenkins_with_weblogic'
|
||||
}
|
||||
|
||||
|
||||
stages {
|
||||
stage('Git pull') {
|
||||
steps {
|
||||
echo "[portal] Git pull from branch: ${BRANCH}"
|
||||
dir("${PROJECT_HOME}/eapim-portal") {
|
||||
git url: "ssh://git@${GIT_SERVER}/eapim/eapim-portal", branch: "${BRANCH}", credentialsId: "${GIT_CREDENTIALS_ID}"
|
||||
}
|
||||
|
||||
|
||||
echo "[online-core-jpa] Git pull from branch: ${BRANCH}"
|
||||
dir("${PROJECT_HOME}/eapim-online/elink-online-core-jpa") {
|
||||
git url: "ssh://git@${GIT_SERVER}/eapim/elink-online-core-jpa", branch: "${BRANCH}", credentialsId: "${GIT_CREDENTIALS_ID}"
|
||||
}
|
||||
|
||||
|
||||
echo "[portal-common] Git pull from branch: ${BRANCH}"
|
||||
dir("${PROJECT_HOME}/elink-portal-common") {
|
||||
git url: "ssh://git@${GIT_SERVER}/eapim/elink-portal-common", branch: "${BRANCH}", credentialsId: "${GIT_CREDENTIALS_ID}"
|
||||
}
|
||||
|
||||
echo "[kjb-safedb] Git pull from branch: ${BRANCH}"
|
||||
dir("${PROJECT_HOME}/kjb-safedb") {
|
||||
git url: "ssh://git@${GIT_SERVER}/eapim/kjb-safedb", branch: "${BRANCH}", credentialsId: "${GIT_CREDENTIALS_ID}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
tool name: 'Oracle-JDK-1.8', type: 'hudson.model.JDK'
|
||||
// tool name: 'Gradle-8.7', type: 'hudson.tasks.Gradle'
|
||||
|
||||
// echo "[online-core-jpa] Build project"
|
||||
// dir("${PROJECT_HOME}/elink-online-multi/elink-online-core-jpa") {
|
||||
// sh '${GRADLE_HOME}/bin/gradle clean build -x test'
|
||||
// }
|
||||
|
||||
// echo "[portal-common] Build project"
|
||||
// dir("${PROJECT_HOME}/elink-portal-common") {
|
||||
// sh '${GRADLE_HOME}/bin/gradle clean build -x test'
|
||||
// }
|
||||
|
||||
echo "[portal] Build project"
|
||||
dir("${PROJECT_HOME}/eapim-portal") {
|
||||
sh '${GRADLE_HOME}/bin/gradle clean build -x test'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stage('Archive WAR') {
|
||||
steps {
|
||||
echo "[portal] Archive WAR file"
|
||||
dir("${PROJECT_HOME}/eapim-portal/build/libs") {
|
||||
archiveArtifacts artifacts: '*.war', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# 광주은행 eAPIM
|
||||
|
||||
## 커스텀 항목
|
||||
### 포탈 Property (EMS 프로퍼티)
|
||||
- `disable_features.user_email_verify' == true
|
||||
이메일 인증 기능 비활성화(회원 가입시 이메일 인증은 한걸로 처리)
|
||||
|
||||
### 디비 접속 방법 Direct -> JNDI 로 변경
|
||||
- 은행 내부 환경에선 모두 JNDI 사용하기 때문에 서버환경에서 동작하는 profile 내 application.yaml 에서 디비 접속 정보 삭제
|
||||
|
||||
## 서버 운영 중 긴급 로깅이 필요할 경우 JVM 파라미터 추가 하여 재기동
|
||||
- DEBUG 레벨 root 로그 / debug.log 로 파일 작성
|
||||
```
|
||||
-Dlogging.config=classpath:logback-debug.xml
|
||||
```
|
||||
|
||||
## 참고자료
|
||||
### 개발용 파라미터
|
||||
```
|
||||
-Dlogging.config=classpath:logback-rinjae.xml
|
||||
-Dinst.Name=devSvr98
|
||||
-Duser.language=en
|
||||
-Duser.country=US
|
||||
-Dkjb_safedb.mode=fake
|
||||
```
|
||||
@@ -1,113 +0,0 @@
|
||||
# 사업자등록번호 중복 체크 구현 작업 계획서
|
||||
|
||||
## 1. 현황 분석
|
||||
|
||||
### 문제점
|
||||
- 법인 등록 시 사업자등록번호(compRegNo) 중복 검증이 없음
|
||||
- `PortalOrgRepository.findByCompRegNo()` 메서드는 존재하나 사용되지 않음
|
||||
- 동일한 사업자등록번호로 여러 법인이 등록될 수 있는 상태
|
||||
|
||||
### 검증 시점
|
||||
1. **실시간 검증**: 사용자가 사업자등록번호 입력 시 `/check-business-number` API 호출
|
||||
2. **등록 시 검증**: 실제 법인 생성 시점 (`registerOrgFromDTOWithFile`)
|
||||
|
||||
---
|
||||
|
||||
## 2. 수정 대상 파일
|
||||
|
||||
| 파일 경로 | 수정 내용 |
|
||||
|-----------|-----------|
|
||||
| `src/main/java/.../apps/user/service/PortalOrgService.java` | 사업자등록번호 중복 체크 메서드 추가 |
|
||||
| `src/main/java/.../apps/user/facade/OrgRegisterFacadeImpl.java` | `checkBusinessNumber()` 중복 체크 로직 추가, 등록 전 중복 체크 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 구현 상세
|
||||
|
||||
### 3.1 PortalOrgService.java
|
||||
|
||||
```java
|
||||
// 추가할 메서드
|
||||
public boolean existsByCompRegNo(String compRegNo) {
|
||||
return portalOrgRepository.findByCompRegNo(compRegNo).isPresent();
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 OrgRegisterFacadeImpl.java
|
||||
|
||||
#### checkBusinessNumber 메서드 수정
|
||||
- 형식 검증 + 중복 검증 동시 수행
|
||||
- 중복 시 "이미 등록된 사업자등록번호입니다." 메시지 반환
|
||||
|
||||
```java
|
||||
@Override
|
||||
public ResponseEntity<ValidationResponse> checkBusinessNumber(String compRegNo) {
|
||||
// 1. 형식 검증
|
||||
boolean isValidFormat = validationService.isValidBusinessNumber(compRegNo);
|
||||
if (!isValidFormat) {
|
||||
return ResponseEntity.ok(new ValidationResponse(false, "유효하지 않은 사업자 등록번호입니다."));
|
||||
}
|
||||
|
||||
// 2. 중복 검증
|
||||
boolean isDuplicate = portalOrgService.existsByCompRegNo(compRegNo);
|
||||
if (isDuplicate) {
|
||||
return ResponseEntity.ok(new ValidationResponse(false, "이미 등록된 사업자등록번호입니다."));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(new ValidationResponse(true, "사용 가능한 사업자 등록번호입니다."));
|
||||
}
|
||||
```
|
||||
|
||||
#### 법인 생성 메서드들에 중복 체크 추가
|
||||
- `registerNewCorporateUser()`
|
||||
- `convertExistingUserToCorporate()`
|
||||
- `registerNewCorporateUserWithExistingData()`
|
||||
|
||||
각 메서드에서 `portalOrgService.registerOrgFromDTOWithFile()` 호출 전 중복 체크 수행
|
||||
|
||||
---
|
||||
|
||||
## 4. 검증 흐름
|
||||
|
||||
```
|
||||
[사용자 입력]
|
||||
↓
|
||||
[실시간 검증] /check-business-number API
|
||||
├── 형식 검증 (10자리 숫자)
|
||||
└── 중복 검증 (DB 조회)
|
||||
↓
|
||||
[등록 요청]
|
||||
↓
|
||||
[등록 시 검증] registerOrgFromDTOWithFile 호출 전
|
||||
└── 중복 체크 (이중 방어)
|
||||
↓
|
||||
[법인 생성]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 기존 중복 데이터 처리
|
||||
|
||||
- 기존에 중복 등록된 데이터는 그대로 유지
|
||||
- 신규 등록 시에만 중복 체크 적용
|
||||
- 필요시 관리자 화면에서 중복 데이터 조회/정리 기능 별도 개발 가능
|
||||
|
||||
---
|
||||
|
||||
## 6. 테스트 시나리오
|
||||
|
||||
| 시나리오 | 예상 결과 |
|
||||
|----------|-----------|
|
||||
| 형식이 잘못된 사업자등록번호 입력 | "유효하지 않은 사업자 등록번호입니다." |
|
||||
| 이미 등록된 사업자등록번호 입력 | "이미 등록된 사업자등록번호입니다." |
|
||||
| 사용 가능한 사업자등록번호 입력 | "사용 가능한 사업자 등록번호입니다." |
|
||||
| 중복된 번호로 법인 등록 시도 | 등록 실패, 에러 메시지 표시 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 작업 완료
|
||||
|
||||
- [x] PortalOrgService에 existsByCompRegNo 메서드 추가
|
||||
- [x] OrgRegisterFacadeImpl.checkBusinessNumber 수정
|
||||
- [x] 법인 등록 메서드들에 중복 체크 로직 추가
|
||||
- [x] 빌드 검증 완료
|
||||
@@ -1,165 +0,0 @@
|
||||
# 네비게이션 바 디자인 Survey 기능 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
직원 대상 네비게이션 바 디자인 선호도 조사를 위한 UI 기능입니다.
|
||||
사용자가 A, B, C 등의 버튼을 클릭하면 실시간으로 네비게이션 바 디자인이 변경됩니다.
|
||||
|
||||
## 기능 활성화/비활성화
|
||||
|
||||
### PortalProperty 설정
|
||||
|
||||
DB의 `ptl_property` 테이블에서 `ui.design-survey` 값을 설정합니다.
|
||||
|
||||
```sql
|
||||
-- 활성화
|
||||
UPDATE ptl_property
|
||||
SET property_value = 'true'
|
||||
WHERE property_group_name = 'Portal' AND property_name = 'ui.design-survey';
|
||||
|
||||
-- 비활성화
|
||||
UPDATE ptl_property
|
||||
SET property_value = 'false'
|
||||
WHERE property_group_name = 'Portal' AND property_name = 'ui.design-survey';
|
||||
```
|
||||
|
||||
> 최초 페이지 로드 시 속성이 자동 생성됩니다 (기본값: `false`)
|
||||
|
||||
---
|
||||
|
||||
## 디자인 옵션 추가/수정 방법
|
||||
|
||||
### 파일 위치
|
||||
`src/main/resources/templates/views/fragment/kjbank/header_container.html`
|
||||
|
||||
### DESIGN_OPTIONS 배열
|
||||
|
||||
디자인 옵션은 JavaScript의 `DESIGN_OPTIONS` 배열에서 관리됩니다:
|
||||
|
||||
```javascript
|
||||
const DESIGN_OPTIONS = [
|
||||
{
|
||||
id: 'A', // 고유 식별자 (필수)
|
||||
label: 'A (현재)', // 버튼에 표시될 텍스트 (필수)
|
||||
isDefault: true, // 기본 선택 옵션 (선택, 하나만 true)
|
||||
styles: {} // 적용할 CSS 스타일 (필수)
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
label: 'B',
|
||||
styles: {
|
||||
'.logo-text': { 'font-size': '18px' },
|
||||
'.nav-link': { 'margin': '0 20px' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
label: 'C',
|
||||
styles: {
|
||||
'.nav-link': { 'margin': '0 20px', 'font-weight': 'bold' }
|
||||
}
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
### 새 디자인 옵션 추가 예시
|
||||
|
||||
```javascript
|
||||
// D 옵션 추가
|
||||
{
|
||||
id: 'D',
|
||||
label: 'D',
|
||||
styles: {
|
||||
'.logo-text': {
|
||||
'font-size': '16px',
|
||||
'color': '#333333'
|
||||
},
|
||||
'.nav-link': {
|
||||
'padding': '10px 24px',
|
||||
'font-weight': '600'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### styles 객체 구조
|
||||
|
||||
```javascript
|
||||
styles: {
|
||||
'CSS 셀렉터': {
|
||||
'CSS 속성': '값',
|
||||
'CSS 속성2': '값2'
|
||||
},
|
||||
'다른 셀렉터': {
|
||||
'CSS 속성': '값'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 자주 사용되는 CSS 셀렉터
|
||||
|
||||
| 셀렉터 | 설명 |
|
||||
|--------|------|
|
||||
| `.logo-text` | 로고 텍스트 "API Portal" |
|
||||
| `.nav-link` | 네비게이션 메뉴 링크 |
|
||||
| `.nav-menu` | 네비게이션 메뉴 컨테이너 |
|
||||
| `.header-content` | 헤더 콘텐츠 영역 |
|
||||
| `.global-header` | 전체 헤더 |
|
||||
| `.logo-wrapper` | 로고 래퍼 (이미지 + 텍스트) |
|
||||
|
||||
## 자주 사용되는 CSS 속성
|
||||
|
||||
| 속성 | 예시 값 | 설명 |
|
||||
|------|---------|------|
|
||||
| `font-size` | `18px`, `1.2rem` | 글자 크기 |
|
||||
| `font-weight` | `400`, `500`, `600`, `bold` | 글자 두께 |
|
||||
| `margin` | `0 20px`, `10px 15px` | 바깥 여백 |
|
||||
| `padding` | `8px 16px` | 안쪽 여백 |
|
||||
| `color` | `#333333`, `#0049b4` | 글자 색상 |
|
||||
| `background` | `#ffffff`, `#f5f5f5` | 배경 색상 |
|
||||
| `gap` | `12px`, `20px` | flex 아이템 간격 |
|
||||
|
||||
---
|
||||
|
||||
## 사용자 선택 저장
|
||||
|
||||
사용자의 디자인 선택은 브라우저의 `localStorage`에 저장됩니다.
|
||||
|
||||
- Key: `design-survey-selection`
|
||||
- Value: 선택한 디자인 ID (예: `A`, `B`, `C`)
|
||||
|
||||
```javascript
|
||||
// 저장된 선택 확인 (개발자 도구 콘솔)
|
||||
localStorage.getItem('design-survey-selection')
|
||||
|
||||
// 선택 초기화
|
||||
localStorage.removeItem('design-survey-selection')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `GlobalControllerAdvice.java` | `designSurveyEnabled` ModelAttribute 제공 |
|
||||
| `header_container.html` | Survey 바 HTML 및 DESIGN_OPTIONS 설정 |
|
||||
| `_header.scss` | Survey 바 스타일 |
|
||||
|
||||
---
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **디자인 옵션 ID는 고유해야 합니다** - 중복 ID 사용 시 오동작
|
||||
2. **isDefault는 하나의 옵션에만 설정** - 여러 개 설정 시 첫 번째만 적용
|
||||
3. **CSS 속성명은 kebab-case 사용** - `fontSize` (X) → `font-size` (O)
|
||||
4. **스타일 적용 시 `!important` 자동 추가** - 기존 스타일 덮어씀
|
||||
5. **Survey 기능 종료 후** - `ui.design-survey`를 `false`로 설정하여 비활성화
|
||||
|
||||
---
|
||||
|
||||
## 문의
|
||||
|
||||
기능 관련 문의는 개발팀에 연락해주세요.
|
||||
@@ -1,105 +0,0 @@
|
||||
flowchart TD
|
||||
Start([법인 관리자가 사용자 초대]) --> GenerateToken[8글자 토큰 생성<br/>예: K7MNPQ2R<br/>UserInvitation 저장<br/>상태: PENDING]
|
||||
|
||||
GenerateToken --> SendEmail[이메일 발송<br/>- 링크 포함<br/>- 8글자 토큰 표시]
|
||||
|
||||
SendEmail --> UserType{사용자 유형}
|
||||
|
||||
%% ========================================
|
||||
%% 신규 회원 플로우
|
||||
%% ========================================
|
||||
UserType -->|신규 회원<br/>미가입자| NewUserPath{초대 확인 방법}
|
||||
|
||||
NewUserPath -->|이메일 링크| EmailLink1[이메일 링크 클릭<br/>/signup/portalUser?invitation=CODE]
|
||||
NewUserPath -->|일반 회원가입| DirectSignup[일반 회원가입 진행<br/>ROLE_USER로 생성]
|
||||
|
||||
EmailLink1 --> SignupPage1[회원가입 페이지<br/>- 이메일 자동 입력됨<br/>- 법인명 표시]
|
||||
SignupPage1 --> RegisterForm[가입 정보 입력<br/>초대 수락]
|
||||
RegisterForm --> RegisterComplete[회원가입 완료<br/>ROLE_CORP_USER로 생성<br/>상태: COMPLETED]
|
||||
RegisterComplete --> NewUserEnd([가입 완료 안내])
|
||||
|
||||
DirectSignup --> Login4[로그인]
|
||||
Login4 --> AfterLogin4[로그인 성공]
|
||||
AfterLogin4 --> CheckInvitation
|
||||
|
||||
%% ========================================
|
||||
%% 기존 회원 플로우
|
||||
%% ========================================
|
||||
UserType -->|기존 회원<br/>ROLE_USER| ExistingUserPath{초대 확인 방법}
|
||||
|
||||
ExistingUserPath -->|이메일 링크<br/>비로그인 상태| EmailLink2[이메일 링크 클릭<br/>/signup/decision?invitation=CODE]
|
||||
ExistingUserPath -->|이메일 링크<br/>로그인 상태| EmailLinkLoggedIn[이메일 링크 클릭<br/>/signup/decision?invitation=CODE]
|
||||
ExistingUserPath -->|직접 로그인| DirectLogin[로그인 페이지에서 로그인]
|
||||
|
||||
%% 비로그인 상태에서 이메일 링크 클릭
|
||||
EmailLink2 --> DecisionPagePublic[초대 수락 페이지<br/>비로그인 상태]
|
||||
DecisionPagePublic --> RedirectLogin2[로그인 유도]
|
||||
RedirectLogin2 --> Login2[로그인]
|
||||
Login2 --> AfterLogin2[로그인 성공]
|
||||
|
||||
%% 로그인 상태에서 이메일 링크 클릭 → 바로 수락 페이지
|
||||
EmailLinkLoggedIn --> DecisionPageDirect[초대 수락 페이지<br/>/signup/decision_process<br/>초대 수락]
|
||||
DecisionPageDirect --> UserDecision
|
||||
|
||||
%% 직접 로그인
|
||||
DirectLogin --> Login3[로그인]
|
||||
Login3 --> AfterLogin3[로그인 성공]
|
||||
|
||||
%% ========================================
|
||||
%% 로그인 후 초대 확인 (PortalAuthenticationSuccessHandler)
|
||||
%% ========================================
|
||||
AfterLogin2 --> CheckInvitation[PortalAuthenticationSuccessHandler<br/>PENDING 초대 확인<br/>findFirstByInvitationEmailAndStatus]
|
||||
AfterLogin3 --> CheckInvitation
|
||||
|
||||
CheckInvitation --> HasInvitation{PENDING 초대 존재?}
|
||||
|
||||
HasInvitation -->|없음| NormalHome[메인 페이지 이동]
|
||||
HasInvitation -->|있음| SaveToSession[세션에 초대 정보 저장<br/>pendingInvitation=true<br/>pendingInvitationToken<br/>pendingInvitationOrgName]
|
||||
|
||||
SaveToSession --> RedirectHome[메인 페이지로 이동]
|
||||
|
||||
%% ========================================
|
||||
%% 메인 페이지 팝업 (index.html)
|
||||
%% ========================================
|
||||
RedirectHome --> ShowPopup[팝업 표시<br/>OOO에서 초대가 도착했습니다<br/>초대를 확인하시겠습니까?]
|
||||
|
||||
ShowPopup --> PopupChoice{사용자 선택}
|
||||
|
||||
PopupChoice -->|확인| GoDecisionPage[초대 수락 페이지 이동<br/>/signup/decision?invitation=TOKEN]
|
||||
PopupChoice -->|취소| StayHome[메인 페이지 유지<br/>다음 로그인 시 팝업 재표시]
|
||||
|
||||
StayHome --> NextLogin[다음 로그인]
|
||||
NextLogin --> CheckInvitation
|
||||
|
||||
GoDecisionPage --> DecisionPage[초대 수락 페이지<br/>/signup/decision_process<br/>초대 수락]
|
||||
|
||||
DecisionPage --> UserDecision{사용자 선택}
|
||||
|
||||
UserDecision -->|수락| AcceptProcess[초대 수락 처리<br/>1. PortalOrg 변경<br/>2. ROLE_CORP_USER 전환<br/>3. 약관 재동의<br/>상태: COMPLETED]
|
||||
UserDecision -->|거절| RejectProcess[초대 거절 처리<br/>상태: REJECTED]
|
||||
|
||||
AcceptProcess --> LogoutPrompt[로그아웃 후 재로그인 안내]
|
||||
LogoutPrompt --> ExistingUserEnd([초대 수락 완료])
|
||||
|
||||
RejectProcess --> RejectEnd([초대 거절 완료<br/>다음 로그인 시 팝업 안 뜸])
|
||||
|
||||
%% ========================================
|
||||
%% 스타일링
|
||||
%% ========================================
|
||||
style Start fill:#e1f5e1
|
||||
style NewUserEnd fill:#c8e6c9
|
||||
style ExistingUserEnd fill:#c8e6c9
|
||||
style RejectEnd fill:#fff9c4
|
||||
style NormalHome fill:#e3f2fd
|
||||
|
||||
style GenerateToken fill:#bbdefb
|
||||
style AcceptProcess fill:#c5cae9
|
||||
style RejectProcess fill:#ffecb3
|
||||
|
||||
style CheckInvitation fill:#ffe0b2
|
||||
style SaveToSession fill:#ffe0b2
|
||||
style ShowPopup fill:#f8bbd0
|
||||
style PopupChoice fill:#f8bbd0
|
||||
|
||||
style StayHome fill:#e1bee7
|
||||
style NextLogin fill:#e1bee7
|
||||
@@ -1,44 +0,0 @@
|
||||
flowchart TD
|
||||
Start([법인 관리자가 사용자 초대 시작]) --> CheckUser{가입된 사용자인가?}
|
||||
|
||||
CheckUser -->|아니오| Error1[에러: 가입된 사용자만 초대 가능]
|
||||
CheckUser -->|예| CheckRole{이미 법인 사용자인가?}
|
||||
|
||||
CheckRole -->|예| Error2[에러: 이미 기관에 등록된 사용자]
|
||||
CheckRole -->|아니오 ROLE_USER| CreateInvitation[8글자 토큰 생성<br/>UserInvitation 저장<br/>상태: PENDING]
|
||||
|
||||
CreateInvitation --> SendEmail[이메일 발송<br/>UserInvitationEvent]
|
||||
|
||||
SendEmail --> UserReceive{사용자가 토큰 받음}
|
||||
|
||||
UserReceive -->|폐쇄망| ManualInput[8글자 토큰 직접 입력<br/>예: K7MNPQ2R]
|
||||
UserReceive -->|이메일 링크| EmailLink[Base64 인코딩된<br/>링크 클릭]
|
||||
|
||||
ManualInput --> DecodeToken[토큰 디코딩<br/>decodeInvitationToken]
|
||||
EmailLink --> DecodeToken
|
||||
|
||||
DecodeToken --> ValidateToken{토큰 유효성 검증}
|
||||
|
||||
ValidateToken -->|무효| Error3[에러: 유효하지 않은 초대]
|
||||
ValidateToken -->|유효 & PENDING| ShowDecision[약관 동의 페이지<br/>/signup/decision]
|
||||
|
||||
ShowDecision --> UserDecision{사용자 선택}
|
||||
|
||||
UserDecision -->|수락| ProcessAccept[processInvitation: accept]
|
||||
UserDecision -->|거절| ProcessReject[processInvitation: reject]
|
||||
|
||||
ProcessAccept --> UpdateUser[1. PortalOrg 변경<br/>2. ROLE_CORP_USER로 전환<br/>3. 약관 재동의 PRIVACY_COLLECT_ORG]
|
||||
UpdateUser --> CompleteInvitation[Invitation 상태:<br/>COMPLETED + completeDate]
|
||||
CompleteInvitation --> Success([초대 수락 완료<br/>로그아웃 후 재로그인])
|
||||
|
||||
ProcessReject --> RejectInvitation[Invitation 상태:<br/>REJECTED + completeDate]
|
||||
RejectInvitation --> End([초대 거절 완료])
|
||||
|
||||
style Start fill:#e1f5e1
|
||||
style Success fill:#c8e6c9
|
||||
style End fill:#fff9c4
|
||||
style Error1 fill:#ffcdd2
|
||||
style Error2 fill:#ffcdd2
|
||||
style Error3 fill:#ffcdd2
|
||||
style CreateInvitation fill:#bbdefb
|
||||
style UpdateUser fill:#c5cae9
|
||||
@@ -1,122 +0,0 @@
|
||||
sequenceDiagram
|
||||
actor 법인관리자
|
||||
participant Portal as Portal UI
|
||||
participant UserManFacade
|
||||
participant UserInvitationRepo as UserInvitation Repository
|
||||
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: 사용자 이메일 입력<br/>초대 요청
|
||||
Portal->>UserManFacade: sendInvitation(sender, emailAddr)
|
||||
|
||||
UserManFacade->>PortalUserRepo: findByEmailAddr(emailAddr)
|
||||
alt 사용자가 없거나 이미 법인 사용자
|
||||
UserManFacade-->>Portal: 에러 반환
|
||||
Portal-->>법인관리자: 초대 불가 알림
|
||||
else 일반 사용자 ROLE_USER 또는 비회원
|
||||
UserManFacade->>UserManFacade: generateEightCharToken()<br/>예: K7MNPQ2R
|
||||
UserManFacade->>UserInvitationRepo: save(UserInvitation)<br/>status: PENDING<br/>expiresOn: 7일 후
|
||||
UserManFacade->>MessageService: publishEvent(UserInvitationEvent)
|
||||
MessageService->>Email: 초대 이메일 발송<br/>(법인명, 관리자명, 토큰)
|
||||
UserManFacade-->>Portal: 초대 완료
|
||||
Portal-->>법인관리자: 초대 성공 알림
|
||||
end
|
||||
|
||||
Email->>초대받은사용자: 초대 이메일 수신
|
||||
|
||||
%% ========================================
|
||||
%% 2-A. 로그인 상태에서 이메일 링크 클릭 (바로 수락 페이지)
|
||||
%% ========================================
|
||||
alt 로그인 상태에서 이메일 링크 클릭
|
||||
초대받은사용자->>Portal: 이메일 링크 클릭<br/>/signup/decision?invitation=TOKEN
|
||||
Portal->>UserRegisterController: showDecisionPage(invitationToken)
|
||||
UserRegisterController->>UserInvitationRepo: findByToken(decodedToken)
|
||||
UserRegisterController-->>Portal: 약관 동의 페이지
|
||||
Portal-->>초대받은사용자: 초대 수락/거절 화면<br/>(바로 표시, 팝업 없음)
|
||||
Note over 초대받은사용자: 6. 초대 수락/거절 처리로 이동
|
||||
end
|
||||
|
||||
%% ========================================
|
||||
%% 2-B. 로그인 (기존 회원 또는 신규 가입 후)
|
||||
%% ========================================
|
||||
alt 기존 회원 (비로그인 상태)
|
||||
초대받은사용자->>Portal: 로그인
|
||||
else 비회원이 일반 회원가입 후
|
||||
초대받은사용자->>Portal: 회원가입 (ROLE_USER)
|
||||
초대받은사용자->>Portal: 로그인
|
||||
end
|
||||
|
||||
%% ========================================
|
||||
%% 3. 로그인 성공 처리 (AuthenticationSuccessHandler)
|
||||
%% ========================================
|
||||
Portal->>AuthHandler: onAuthenticationSuccess()
|
||||
AuthHandler->>AuthHandler: 이메일 인증, 휴면계정,<br/>비밀번호 만료 체크
|
||||
|
||||
AuthHandler->>UserInvitationRepo: findFirstByInvitationEmailAndStatus<br/>(loginId, PENDING)
|
||||
|
||||
alt PENDING 초대 없음
|
||||
AuthHandler->>Portal: redirect to /
|
||||
Portal-->>초대받은사용자: 메인 페이지 (팝업 없음)
|
||||
else PENDING 초대 있음 & 만료 안됨
|
||||
AuthHandler->>PortalOrgRepo: findById(invitation.orgId)
|
||||
PortalOrgRepo-->>AuthHandler: PortalOrg (기관명)
|
||||
AuthHandler->>AuthHandler: session.setAttribute<br/>("pendingInvitation", true)<br/>("pendingInvitationToken", token)<br/>("pendingInvitationOrgName", orgName)
|
||||
AuthHandler->>Portal: redirect to /
|
||||
end
|
||||
|
||||
%% ========================================
|
||||
%% 4. 메인 페이지 팝업 표시
|
||||
%% ========================================
|
||||
Portal->>IndexController: GET /
|
||||
IndexController->>MainPage: render index.html
|
||||
|
||||
MainPage->>MainPage: 세션에서 pendingInvitation 확인
|
||||
|
||||
alt pendingInvitation == true
|
||||
MainPage-->>초대받은사용자: 팝업 표시<br/>"[기관명]에서 초대가 도착했습니다.<br/>초대를 확인하시겠습니까?"
|
||||
end
|
||||
|
||||
%% ========================================
|
||||
%% 5. 사용자 팝업 선택
|
||||
%% ========================================
|
||||
alt 팝업에서 "확인" 클릭
|
||||
초대받은사용자->>Portal: /signup/decision?invitation=TOKEN
|
||||
Portal->>UserRegisterController: showDecisionPage(invitationToken)
|
||||
UserRegisterController->>UserInvitationRepo: findByToken(decodedToken)
|
||||
UserRegisterController-->>Portal: 약관 동의 페이지
|
||||
Portal-->>초대받은사용자: 초대 수락/거절 화면
|
||||
else 팝업에서 "취소" 클릭
|
||||
Note over 초대받은사용자,MainPage: 메인 페이지 유지<br/>다음 로그인 시 팝업 재표시
|
||||
end
|
||||
|
||||
%% ========================================
|
||||
%% 6. 초대 수락/거절 처리
|
||||
%% ========================================
|
||||
초대받은사용자->>Portal: 수락 or 거절 선택
|
||||
Portal->>UserRegisterController: processInvitation(action)
|
||||
UserRegisterController->>UserRegisterFacade: processInvitation(action, invitation)
|
||||
|
||||
alt 수락
|
||||
UserRegisterFacade->>PortalUserRepo: findByEmailAddr()
|
||||
UserRegisterFacade->>PortalUserRepo: updateUserToCorpUser()<br/>(PortalOrg 변경<br/>ROLE_CORP_USER 전환)
|
||||
UserRegisterFacade->>UserInvitationRepo: save(invitation)<br/>status: COMPLETED<br/>completeDate: now()
|
||||
UserRegisterFacade-->>Portal: 수락 성공
|
||||
Portal-->>초대받은사용자: 성공 메시지<br/>로그아웃 후 재로그인 안내
|
||||
Note over 초대받은사용자: 다음 로그인 시<br/>PENDING 초대 없음 → 팝업 안 뜸
|
||||
else 거절
|
||||
UserRegisterFacade->>UserInvitationRepo: save(invitation)<br/>status: REJECTED<br/>completeDate: now()
|
||||
UserRegisterFacade-->>Portal: 거절 처리 완료
|
||||
Portal-->>초대받은사용자: 거절 완료 메시지
|
||||
Note over 초대받은사용자: 다음 로그인 시<br/>REJECTED 상태 → 팝업 안 뜸
|
||||
end
|
||||
@@ -1,44 +0,0 @@
|
||||
stateDiagram-v2
|
||||
[*] --> 초대생성: 법인관리자가 초대 시작
|
||||
|
||||
초대생성 --> PENDING: 토큰 생성 및 저장
|
||||
|
||||
PENDING --> 이메일발송: 초대 이메일 발송
|
||||
|
||||
state 로그인후팝업 {
|
||||
이메일발송 --> 로그인: 사용자 로그인
|
||||
로그인 --> 초대확인: AuthSuccessHandler
|
||||
초대확인 --> 세션저장: 세션에 초대 정보 저장
|
||||
세션저장 --> 팝업표시: 메인 페이지 팝업
|
||||
팝업표시 --> 팝업확인: 확인 클릭
|
||||
팝업표시 --> 팝업취소: 취소 클릭
|
||||
팝업취소 --> 로그인: 다음 로그인 시 재표시
|
||||
}
|
||||
|
||||
팝업확인 --> 초대수락: 수락 페이지 이동
|
||||
|
||||
초대수락 --> COMPLETED: 수락
|
||||
초대수락 --> REJECTED: 거절
|
||||
|
||||
COMPLETED --> [*]: 초대 완료
|
||||
REJECTED --> [*]: 초대 거절
|
||||
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
|
||||
|
Before Width: | Height: | Size: 6.7 MiB |
|
Before Width: | Height: | Size: 574 KiB |
@@ -1,33 +1,30 @@
|
||||
{
|
||||
"name": "eapim-portal",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "eapim-portal",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"playwright": "^1.57.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.69.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
"node-addon-api": "^7.0.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
@@ -37,25 +34,25 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
"@parcel/watcher-android-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||
"@parcel/watcher-win32-x64": "2.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -74,9 +71,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -95,9 +92,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -116,9 +113,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -137,9 +134,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -158,9 +155,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -179,9 +176,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -200,9 +197,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -221,9 +218,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -242,9 +239,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -263,9 +260,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -284,9 +281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -305,9 +302,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -325,82 +322,37 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
|
||||
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -429,32 +381,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
@@ -464,57 +390,27 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
||||
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.57.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
||||
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
@@ -522,21 +418,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.97.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
|
||||
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
|
||||
"version": "1.100.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz",
|
||||
"integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"chokidar": "^5.0.0",
|
||||
"immutable": "^5.1.5",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
@@ -551,284 +447,6 @@
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1",
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"optional": true
|
||||
},
|
||||
"immutable": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
||||
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.57.0"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
||||
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.97.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
|
||||
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,5 @@
|
||||
"sass": "^1.69.5"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"playwright": "^1.57.0"
|
||||
}
|
||||
"license": "ISC"
|
||||
}
|
||||
|
||||
@@ -4,8 +4,4 @@ include 'elink-online-core-jpa'
|
||||
include 'elink-portal-common'
|
||||
|
||||
project (':elink-online-core-jpa').projectDir = new File(settingsDir, "../eapim-online/elink-online-core-jpa")
|
||||
project (':elink-portal-common').projectDir = new File(settingsDir, "../elink-portal-common")
|
||||
|
||||
|
||||
//include 'kjb-safedb'
|
||||
//project (':kjb-safedb').projectDir = new File(settingsDir, "../kjb-safedb")
|
||||
project (':elink-portal-common').projectDir = new File(settingsDir, "../elink-portal-common")
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eactive.apim.portal.apps;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Readiness probe.
|
||||
*
|
||||
* <p>Distinct from {@link HealthCheckController} (liveness — servlet alive?).
|
||||
* This endpoint validates both EMS and Gateway datasources via JDBC
|
||||
* {@code Connection.isValid(timeout)} to confirm the app is ready to serve
|
||||
* requests that depend on the database.
|
||||
*
|
||||
* <p>HTTP 200 + JSON when all checks pass.
|
||||
* <p>HTTP 503 + JSON when any check fails — body still includes the per-component
|
||||
* status so CI/CD logs the failing dependency.
|
||||
*/
|
||||
@RestController
|
||||
public class ReadinessController {
|
||||
|
||||
private static final int VALIDATION_TIMEOUT_SECONDS = 2;
|
||||
|
||||
private final DataSource portalDataSource;
|
||||
private final DataSource gatewayDataSource;
|
||||
|
||||
public ReadinessController(
|
||||
@Qualifier("portalDataSource") DataSource portalDataSource,
|
||||
@Qualifier("gatewayDataSource") DataSource gatewayDataSource) {
|
||||
this.portalDataSource = portalDataSource;
|
||||
this.gatewayDataSource = gatewayDataSource;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/health/ready", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<String> ready() {
|
||||
String ems = check(portalDataSource);
|
||||
String gw = check(gatewayDataSource);
|
||||
boolean ok = "UP".equals(ems) && "UP".equals(gw);
|
||||
|
||||
String body = "{"
|
||||
+ "\"status\":\"" + (ok ? "UP" : "DOWN") + "\","
|
||||
+ "\"ems\":\"" + ems + "\","
|
||||
+ "\"gateway\":\""+ gw + "\""
|
||||
+ "}";
|
||||
|
||||
return ResponseEntity.status(ok ? 200 : 503)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(body);
|
||||
}
|
||||
|
||||
private static String check(DataSource ds) {
|
||||
if (ds == null) return "DOWN:NO_DATASOURCE";
|
||||
try (Connection c = ds.getConnection()) {
|
||||
return c.isValid(VALIDATION_TIMEOUT_SECONDS) ? "UP" : "DOWN:INVALID";
|
||||
} catch (SQLException e) {
|
||||
String msg = e.getMessage() == null ? "" : e.getMessage();
|
||||
return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,97 +56,99 @@ public class ApiTesterFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
|
||||
if (httpServletRequest.getRequestURI().contains("/api/call-api")) {
|
||||
ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class);
|
||||
String url = httpServletRequest.getHeader("original-url");
|
||||
// "/api/call-api" 경로가 아닌 경우 필터링하지 않고 다음 필터로 넘어감
|
||||
if (!httpServletRequest.getRequestURI().contains("/api/call-api")) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.contains("/api/v1/oauth/token")){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = httpServletRequest.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String body = sb.toString();
|
||||
ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class);
|
||||
String url = httpServletRequest.getHeader("original-url");
|
||||
|
||||
// Parse request parameters
|
||||
Map<String, String> params = new HashMap<>();
|
||||
String[] pairs = body.split("&");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split("=");
|
||||
if (keyValue.length == 2) {
|
||||
params.put(keyValue[0], keyValue[1]);
|
||||
}
|
||||
}
|
||||
String scope = params.getOrDefault("scope", "default");
|
||||
if (url.contains("/api/v1/oauth/token")){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = httpServletRequest.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String body = sb.toString();
|
||||
|
||||
String token = "{\n" +
|
||||
" \"access_token\": \"kjbank_gw_sample_token\",\n" +
|
||||
" \"token_type\": \"bearer\",\n" +
|
||||
" \"expires_in\": 86400,\n" +
|
||||
" \"scope\": \""+scope +"\",\n" +
|
||||
" \"jti\": \""+ UUID.randomUUID().toString()+"\"\n" +
|
||||
"}";
|
||||
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(token);
|
||||
|
||||
} else {
|
||||
ApiSpecInfoDto apiSpecInfoDto = apiSpecInfoDtoService.selectDetailByURLAndMethod(parseUri(url), httpServletRequest.getMethod());
|
||||
|
||||
if (apiSpecInfoDto.getResponseType() == null || apiSpecInfoDto.getResponseType().equals("sample")) {
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(apiSpecInfoDto.getSampleResponse());
|
||||
} else {
|
||||
String mockUrl = apiSpecInfoDto.getMockUrl();
|
||||
String method = apiSpecInfoDto.getApiMethod();
|
||||
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String header = headerNames.nextElement();
|
||||
headers.put(header, httpServletRequest.getHeader(header));
|
||||
}
|
||||
Map<String, String[]> paramMap = new HashMap<>();
|
||||
|
||||
String originalUrl = headers.get("original-url");
|
||||
//sprlit original url with ? get the second part then split with & put into paramMap
|
||||
|
||||
String[] originalUrlArr = originalUrl.split("\\?");
|
||||
if (originalUrlArr.length > 1) {
|
||||
String[] paramArr = originalUrlArr[1].split("&");
|
||||
for (String param : paramArr) {
|
||||
String[] paramKeyValue = param.split("=");
|
||||
if (paramKeyValue.length > 1) {
|
||||
paramMap.put(paramKeyValue[0], new String[]{paramKeyValue[1]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers.remove("original-url");
|
||||
headers.remove("original-api-id");
|
||||
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
||||
|
||||
String responseStr = "";
|
||||
if (method.equalsIgnoreCase("post")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = httpServletRequest.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String body = sb.toString();
|
||||
responseStr = apiSender.requestPost(mockUrl, headers, paramMap, body);
|
||||
|
||||
} else {
|
||||
responseStr = apiSender.requestGet(mockUrl, headers, paramMap);
|
||||
}
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(responseStr);
|
||||
// Parse request parameters
|
||||
Map<String, String> params = new HashMap<>();
|
||||
String[] pairs = body.split("&");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split("=");
|
||||
if (keyValue.length == 2) {
|
||||
params.put(keyValue[0], keyValue[1]);
|
||||
}
|
||||
}
|
||||
String scope = params.getOrDefault("scope", "default");
|
||||
|
||||
String token = "{\n" +
|
||||
" \"access_token\": \"djbank_gw_sample_token\",\n" +
|
||||
" \"token_type\": \"bearer\",\n" +
|
||||
" \"expires_in\": 86400,\n" +
|
||||
" \"scope\": \""+scope +"\",\n" +
|
||||
" \"jti\": \""+ UUID.randomUUID().toString()+"\"\n" +
|
||||
"}";
|
||||
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(token);
|
||||
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
ApiSpecInfoDto apiSpecInfoDto = apiSpecInfoDtoService.selectDetailByURLAndMethod(parseUri(url), httpServletRequest.getMethod());
|
||||
|
||||
if (apiSpecInfoDto.getResponseType() == null || apiSpecInfoDto.getResponseType().equals("sample")) {
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(apiSpecInfoDto.getSampleResponse());
|
||||
} else {
|
||||
String mockUrl = apiSpecInfoDto.getMockUrl();
|
||||
String method = apiSpecInfoDto.getApiMethod();
|
||||
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String header = headerNames.nextElement();
|
||||
headers.put(header, httpServletRequest.getHeader(header));
|
||||
}
|
||||
Map<String, String[]> paramMap = new HashMap<>();
|
||||
|
||||
String originalUrl = headers.get("original-url");
|
||||
//sprlit original url with ? get the second part then split with & put into paramMap
|
||||
|
||||
String[] originalUrlArr = originalUrl.split("\\?");
|
||||
if (originalUrlArr.length > 1) {
|
||||
String[] paramArr = originalUrlArr[1].split("&");
|
||||
for (String param : paramArr) {
|
||||
String[] paramKeyValue = param.split("=");
|
||||
if (paramKeyValue.length > 1) {
|
||||
paramMap.put(paramKeyValue[0], new String[]{paramKeyValue[1]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers.remove("original-url");
|
||||
headers.remove("original-api-id");
|
||||
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
||||
|
||||
String responseStr = "";
|
||||
if (method.equalsIgnoreCase("post")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
BufferedReader reader = httpServletRequest.getReader();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
String body = sb.toString();
|
||||
responseStr = apiSender.requestPost(mockUrl, headers, paramMap, body);
|
||||
|
||||
} else {
|
||||
responseStr = apiSender.requestGet(mockUrl, headers, paramMap);
|
||||
}
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().println(responseStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package com.eactive.apim.portal.apps.auth.service;
|
||||
|
||||
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
|
||||
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
|
||||
import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO;
|
||||
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||
import lombok.var;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -18,12 +19,12 @@ public class ApiPermissionFilter {
|
||||
|
||||
if (isAuthenticated) {
|
||||
// null 체크 추가
|
||||
var user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
if (user != null && user.getRoleCode() != null) {
|
||||
roleCode = user.getRoleCode().toString();
|
||||
}
|
||||
|
||||
var userOrg = SecurityUtil.getUserOrg();
|
||||
PortalOrgDTO userOrg = SecurityUtil.getUserOrg();
|
||||
if (userOrg != null) {
|
||||
org = userOrg.getId();
|
||||
}
|
||||
@@ -44,12 +45,12 @@ public class ApiPermissionFilter {
|
||||
|
||||
if (isAuthenticated) {
|
||||
// null 체크 추가
|
||||
var user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||
if (user != null && user.getRoleCode() != null) {
|
||||
roleCode = user.getRoleCode().toString();
|
||||
}
|
||||
|
||||
var userOrg = SecurityUtil.getUserOrg();
|
||||
PortalOrgDTO userOrg = SecurityUtil.getUserOrg();
|
||||
if (userOrg != null) {
|
||||
org = userOrg.getId();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.var;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -252,7 +251,7 @@ public class AccountController {
|
||||
@GetMapping("/mypage/org-transfer")
|
||||
public String showOrgTransferPage(Model model) {
|
||||
try {
|
||||
var authenticatedUser = SecurityUtil.getPortalAuthenticatedUser();
|
||||
PortalAuthenticatedUser authenticatedUser = SecurityUtil.getPortalAuthenticatedUser();
|
||||
PortalUserDTO user = userFacade.findById(authenticatedUser.getId());
|
||||
|
||||
if (user.getRoleCode() != PortalUserEnums.RoleCode.ROLE_USER) {
|
||||
@@ -282,7 +281,7 @@ public class AccountController {
|
||||
HttpServletResponse response) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
var currentUser = SecurityUtil.getPortalAuthenticatedUser();
|
||||
PortalAuthenticatedUser currentUser = SecurityUtil.getPortalAuthenticatedUser();
|
||||
|
||||
if (currentUser.getRoleCode() != PortalUserEnums.RoleCode.ROLE_USER) {
|
||||
result.put("status", "ERROR");
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.eactive.apim.portal.apps.user.dto;
|
||||
import com.eactive.apim.portal.common.validator.AuthNumberMatch;
|
||||
import com.eactive.apim.portal.common.validator.CellPhone;
|
||||
import com.eactive.apim.portal.common.validator.PasswordMatch;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForKjbank;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForDjbank;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
@@ -12,7 +12,7 @@ import org.hibernate.validator.constraints.NotEmpty;
|
||||
@AuthNumberMatch(recipient = "loginId", authField = "authNumber")
|
||||
@PasswordMatch(input = "password", confirm = "password2")
|
||||
@Data
|
||||
@PasswordRuleForKjbank(password = "password", loginId = "loginId", mobile = "mobileNumber")
|
||||
@PasswordRuleForDjbank(password = "password", loginId = "loginId", mobile = "mobileNumber")
|
||||
public class PortalUserRegistrationDTO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.user.dto;
|
||||
|
||||
import com.eactive.apim.portal.common.validator.PasswordMatch;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForKjbank;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForDjbank;
|
||||
import com.eactive.apim.portal.common.validator.UniqueId;
|
||||
import com.eactive.apim.portal.portaluser.entity.UserStatus;
|
||||
import lombok.Data;
|
||||
@@ -13,7 +13,7 @@ import java.io.Serializable;
|
||||
|
||||
@PasswordMatch(input = "password", confirm = "password2")
|
||||
@Data
|
||||
@PasswordRuleForKjbank(loginId = "userId", password = "password", mobile = "mobilePhone")
|
||||
@PasswordRuleForDjbank(loginId = "userId", password = "password", mobile = "mobilePhone")
|
||||
public class UserRegisterDTO implements Serializable {
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.eactive.apim.portal.apps.user.validator;
|
||||
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForKbankValidator;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForKjbankValidator;
|
||||
import com.eactive.apim.portal.common.validator.PasswordRuleForDjbankValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -17,7 +17,7 @@ public class PasswordValidator {
|
||||
}
|
||||
|
||||
public boolean isValidPassword(String password, String loginId, String mobileNumber) {
|
||||
PasswordRuleForKjbankValidator validator = new PasswordRuleForKjbankValidator();
|
||||
PasswordRuleForDjbankValidator validator = new PasswordRuleForDjbankValidator();
|
||||
return validator.isValid(password, loginId, mobileNumber);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
package com.eactive.apim.portal.common.breadcrumb;
|
||||
|
||||
import java.util.HashMap;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
@Service
|
||||
public class PageService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PageService.class);
|
||||
|
||||
private final PageStructure pageStructure;
|
||||
|
||||
@Value("${portal.dev.hot-reload-pages:false}")
|
||||
private boolean hotReloadEnabled;
|
||||
|
||||
private volatile long lastReloadedMtime = 0L;
|
||||
private volatile Path watchedPath;
|
||||
|
||||
public PageService(PageStructure pageStructure) {
|
||||
this.pageStructure = pageStructure;
|
||||
}
|
||||
|
||||
public List<Map> getBreadcrumb(String currentPath) {
|
||||
// URL에서 기본 경로와 쿼리 파라미터 분리
|
||||
maybeReloadPages();
|
||||
String basePath = currentPath.contains("?") ?
|
||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||
@@ -29,12 +46,59 @@ public class PageService {
|
||||
}
|
||||
|
||||
public String getPageName(String currentPath) {
|
||||
maybeReloadPages();
|
||||
String basePath = currentPath.contains("?") ?
|
||||
currentPath.substring(0, currentPath.indexOf("?")) : currentPath;
|
||||
Map<String, String> queryParams = parseQueryParams(currentPath);
|
||||
return findPageName(pageStructure.getHome(), basePath, queryParams);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Dev-only hot reload of the `page:` tree.
|
||||
// Toggle via `portal.dev.hot-reload-pages: true` in any active profile yml.
|
||||
// Only the page tree (breadcrumb / menu names) reloads — `portal.pages` route
|
||||
// mappings are still registered at boot and require a restart.
|
||||
// -----------------------------------------------------------------------------
|
||||
private void maybeReloadPages() {
|
||||
if (!hotReloadEnabled) return;
|
||||
try {
|
||||
Path p = resolveYmlPath();
|
||||
if (p == null) return;
|
||||
long mtime = Files.getLastModifiedTime(p).toMillis();
|
||||
if (mtime == lastReloadedMtime) return;
|
||||
|
||||
try (InputStream is = Files.newInputStream(p)) {
|
||||
Yaml yaml = new Yaml();
|
||||
Map<String, Object> root = yaml.load(is);
|
||||
Object page = (root != null) ? root.get("page") : null;
|
||||
if (page instanceof Map) {
|
||||
Object home = ((Map<?, ?>) page).get("home");
|
||||
if (home instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> homeMap = (Map<String, Object>) home;
|
||||
pageStructure.setHome(homeMap);
|
||||
lastReloadedMtime = mtime;
|
||||
log.debug("[PageService] page tree reloaded from {} (mtime={})", p, mtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[PageService] page tree reload failed: {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolveYmlPath() {
|
||||
if (watchedPath != null) return watchedPath;
|
||||
try {
|
||||
ClassPathResource res = new ClassPathResource("application.yml");
|
||||
if (!res.exists() || !res.isFile()) return null;
|
||||
watchedPath = res.getFile().toPath();
|
||||
return watchedPath;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> parseQueryParams(String path) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (path.contains("?")) {
|
||||
@@ -52,7 +116,6 @@ public class PageService {
|
||||
private String findPageName(Map<String, Object> currentNode, String basePath, Map<String, String> queryParams) {
|
||||
String path = (String) currentNode.get("path");
|
||||
|
||||
// 기본 경로가 일치하는지 확인
|
||||
if (path != null) {
|
||||
String nodePath = path.contains("?") ?
|
||||
path.substring(0, path.indexOf("?")) : path;
|
||||
@@ -63,7 +126,6 @@ public class PageService {
|
||||
}
|
||||
}
|
||||
|
||||
// 자식 노드들을 재귀적으로 검색
|
||||
if (currentNode.containsKey("children")) {
|
||||
Map<String, Object> children = (Map<String, Object>) currentNode.get("children");
|
||||
for (Map.Entry<String, Object> entry : children.entrySet()) {
|
||||
@@ -116,7 +178,6 @@ public class PageService {
|
||||
}
|
||||
|
||||
private boolean matchesQueryParams(Map<String, String> nodeParams, Map<String, String> requestParams) {
|
||||
// 특별히 tab 파라미터에 대해서만 체크
|
||||
String nodeTab = nodeParams.get("tab");
|
||||
String requestTab = requestParams.get("tab");
|
||||
|
||||
@@ -125,4 +186,4 @@ public class PageService {
|
||||
}
|
||||
return Objects.equals(nodeTab, requestTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eactive.apim.portal.common.validator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Constraint(validatedBy = PasswordRuleForDjbankValidator.class)
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PasswordRuleForDjbank {
|
||||
String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~20자, 아이디, 휴대전화, 3자리 이상 연속,반복 문자 불가)";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
String password();
|
||||
|
||||
String loginId();
|
||||
|
||||
String mobile();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.eactive.apim.portal.common.validator;
|
||||
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by Sungpil Hyun
|
||||
*/
|
||||
public class PasswordRuleForDjbankValidator implements ConstraintValidator<PasswordRuleForDjbank, Object> {
|
||||
|
||||
// 최소 8자, 최대 20자 상수 선언
|
||||
private static final int MIN = 8;
|
||||
private static final int MAX = 20;
|
||||
|
||||
private String password;
|
||||
private String loginId;
|
||||
private String mobileNumber;
|
||||
|
||||
// 3자리 연속 문자 정규식
|
||||
private static final String SAMEPT = "(\\w)\\1\\1";
|
||||
// 공백 문자 정규식
|
||||
private static final String BLANKPT = "(\\s)";
|
||||
|
||||
@Override
|
||||
public void initialize(PasswordRuleForDjbank constraintAnnotation) {
|
||||
this.password = constraintAnnotation.password();
|
||||
this.loginId = constraintAnnotation.loginId();
|
||||
this.mobileNumber = constraintAnnotation.mobile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
|
||||
String passwordValue = null;
|
||||
String loginIdValue = null;
|
||||
String mobileNumberValue = null;
|
||||
|
||||
try {
|
||||
passwordValue = (String) PropertyUtils.getProperty(value, this.password);
|
||||
loginIdValue = (String) PropertyUtils.getProperty(value, this.loginId);
|
||||
mobileNumberValue = (String) PropertyUtils.getProperty(value, this.mobileNumber);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValid(passwordValue, loginIdValue, mobileNumberValue);
|
||||
}
|
||||
|
||||
public boolean isValid(String password, String loginId, String mobileNumber) {
|
||||
// 영어, 숫자, 특수문자 포함한 MIN to MAX 글자 정규식
|
||||
String REGEX = "^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "})$";
|
||||
|
||||
// 정규식 검사객체
|
||||
Matcher matcher;
|
||||
|
||||
// 공백 체크
|
||||
if (password == null || "".equals(password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ASCII 문자 비교를 위한 UpperCase
|
||||
String tmpPw = password.toUpperCase();
|
||||
// 문자열 길이
|
||||
int strLen = tmpPw.length();
|
||||
|
||||
// 글자 길이 체크
|
||||
if (strLen > 20 || strLen < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loginId != null && !loginId.isEmpty()) {
|
||||
String[] loginParts = loginId.split("@");
|
||||
if (loginParts.length > 0) {
|
||||
String username = loginParts[0].toUpperCase();
|
||||
if (tmpPw.contains(username)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile number validation
|
||||
if (mobileNumber != null && !mobileNumber.isEmpty()) {
|
||||
String[] mobileParts = mobileNumber.split("-");
|
||||
for (String part : mobileParts) {
|
||||
if (!part.isEmpty() && tmpPw.contains(part)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 공백 체크
|
||||
matcher = Pattern.compile(BLANKPT).matcher(tmpPw);
|
||||
if (matcher.find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 비밀번호 정규식 체크
|
||||
matcher = Pattern.compile(REGEX).matcher(tmpPw);
|
||||
if (!matcher.find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 동일한 문자 3개 이상 체크
|
||||
matcher = Pattern.compile(SAMEPT).matcher(tmpPw);
|
||||
if (matcher.find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 연속된 문자 / 숫자 3개 이상 체크
|
||||
// ASCII Char를 담을 배열 선언
|
||||
int[] tmpArray = new int[strLen];
|
||||
|
||||
// Make Array
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
tmpArray[i] = tmpPw.charAt(i);
|
||||
}
|
||||
|
||||
// Validation Array
|
||||
for (int i = 0; i < strLen - 2; i++) {
|
||||
if (isContinuous(tmpArray[i], tmpArray[i + 2]) && isContinuous(tmpArray[i], tmpArray[i + 1], tmpArray[i + 2])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Validation Complete
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static boolean isContinuous(int first, int third) {
|
||||
// 첫 글자 A-Z / 0-9
|
||||
return (first > 47 && third < 58) || (first > 64 && third < 91);
|
||||
}
|
||||
|
||||
static boolean isContinuous(int first, int second, int third) {
|
||||
// 배열의 연속된 수 검사
|
||||
// 3번째 글자 - 2번째 글자 = 1, 3번째 글자 - 1번째 글자 = 2
|
||||
return Math.abs(third - second) == 1 && Math.abs(third - first) == 2;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Constraint(validatedBy = PasswordRuleForKjbankValidator.class)
|
||||
@Constraint(validatedBy = PasswordRuleForDjbankValidator.class)
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
|
||||
@@ -27,6 +27,7 @@ public class BaseDatasourceConfiguration {
|
||||
protected DataSource jndiLookup(String jndiName) throws NamingException {
|
||||
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
|
||||
bean.setJndiName(jndiName);
|
||||
bean.setResourceRef(true);
|
||||
bean.setProxyInterface(DataSource.class);
|
||||
bean.afterPropertiesSet();
|
||||
return (DataSource) bean.getObject();
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
@@ -20,7 +19,6 @@ import javax.sql.DataSource;
|
||||
@Configuration
|
||||
@EntityScan(basePackages = {"com.eactive.apim.gateway"})
|
||||
@EnableJpaRepositories(basePackages = "com.eactive.apim.gateway", repositoryBaseClass = BaseRepositoryImpl.class, entityManagerFactoryRef = "gatewayEntityManagerFactory")
|
||||
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
|
||||
@Slf4j
|
||||
public class GatewayDatasourceConfiguration extends BaseDatasourceConfiguration {
|
||||
private final Environment env;
|
||||
@@ -59,6 +57,8 @@ public class GatewayDatasourceConfiguration extends BaseDatasourceConfiguration
|
||||
} catch (NamingException e) {
|
||||
log.warn("JNDI lookup failed.", e);
|
||||
}
|
||||
} else {
|
||||
log.warn("gateway.datasource.jndi-name 이 설정되어 있지 않음 - 직접 접속 모드로 진행");
|
||||
}
|
||||
|
||||
log.info("개발환경을 위해 JNDI 대신 직접 DB 접속 시도");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.eactive.apim.portal.config;
|
||||
|
||||
import com.eactive.apim.portal.custom.config.KjbPasswordEncoder;
|
||||
import com.eactive.apim.portal.custom.config.DjbPasswordEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
@@ -11,6 +11,6 @@ public class PasswordEncoderConfig {
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// return new StandardPasswordEncoder();
|
||||
return new KjbPasswordEncoder();
|
||||
return new DjbPasswordEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
@@ -31,23 +29,12 @@ public class PortalAuthenticationManager implements AuthenticationManager {
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final MessageHandlerService messageHandlerService;
|
||||
|
||||
private String decodePassword(String encodedPassword) {
|
||||
try {
|
||||
// Reverse the string and decode from Base64
|
||||
String reversed = new StringBuilder(encodedPassword).reverse().toString();
|
||||
return new String(Base64.getDecoder().decode(reversed));
|
||||
} catch (Exception e) {
|
||||
throw new BadCredentialsException("Password decoding failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(noRollbackFor = {AuthenticationException.class})
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
|
||||
String username = token.getName();
|
||||
String encodedPassword = token.getCredentials().toString();
|
||||
String decodedPassword = decodePassword(encodedPassword);
|
||||
String password = token.getCredentials().toString();
|
||||
|
||||
PortalAuthenticatedUser user = (PortalAuthenticatedUser) portalUserAuthService.loadUserByUsername(username);
|
||||
|
||||
@@ -62,9 +49,9 @@ public class PortalAuthenticationManager implements AuthenticationManager {
|
||||
|
||||
|
||||
// 비밀번호 검증
|
||||
if (!passwordEncoder.matches(decodedPassword, user.getPassword())) {
|
||||
if (!passwordEncoder.matches(password, user.getPassword())) {
|
||||
// 암호화가 되어 있지 않을 수도 있어 그대로 비교 검증
|
||||
if (!user.getPassword().equals(decodedPassword)) {
|
||||
if (!user.getPassword().equals(password)) {
|
||||
throw new BadCredentialsException("아이디 또는 비밀번호가 일치하지 않습니다.");
|
||||
}
|
||||
}
|
||||
@@ -72,7 +59,7 @@ public class PortalAuthenticationManager implements AuthenticationManager {
|
||||
// DORMANT 상태 체크
|
||||
if (user.getUserStatus().equals(PortalUserEnums.UserStatus.DORMANT)) {
|
||||
UsernamePasswordAuthenticationToken authorityToken =
|
||||
new UsernamePasswordAuthenticationToken(user, decodedPassword, user.getAuthorities());
|
||||
new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
|
||||
authorityToken.setDetails(user);
|
||||
SecurityContextHolder.getContext().setAuthentication(authorityToken);
|
||||
return authorityToken;
|
||||
@@ -101,7 +88,7 @@ public class PortalAuthenticationManager implements AuthenticationManager {
|
||||
|
||||
|
||||
UsernamePasswordAuthenticationToken authorityToken =
|
||||
new UsernamePasswordAuthenticationToken(user, decodedPassword, user.getAuthorities());
|
||||
new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
|
||||
authorityToken.setDetails(user);
|
||||
SecurityContextHolder.getContext().setAuthentication(authorityToken);
|
||||
return authorityToken;
|
||||
|
||||
@@ -70,6 +70,8 @@ public class PortalDatasourceConfiguration extends BaseDatasourceConfiguration {
|
||||
} catch (NamingException e) {
|
||||
log.warn("JNDI lookup failed.", e);
|
||||
}
|
||||
} else {
|
||||
log.warn("ems.datasource.jndi-name 이 설정되어 있지 않음 - 직접 접속 모드로 진행");
|
||||
}
|
||||
|
||||
if (dataSource == null) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eactive.apim.portal.custom.config;
|
||||
|
||||
//import com.eactive.ext.djb.safedb.DjbSafedbWrapper;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
public class DjbPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
private final BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String bcryptHash = bcryptEncoder.encode(rawPassword);
|
||||
return bcryptHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
return bcryptEncoder.matches(rawPassword, encodedPassword);
|
||||
// DjbSafedbWrapper safedb = DjbSafedbWrapper.getInstance();
|
||||
// String bcryptHash = safedb.decryptNotRnno(encodedPassword);
|
||||
// return bcryptEncoder.matches(rawPassword, bcryptHash);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ spring:
|
||||
jpa:
|
||||
properties:
|
||||
hibernate:
|
||||
show_sql: false
|
||||
show_sql: true
|
||||
format_sql: true
|
||||
use_sql_comments: true
|
||||
devtools:
|
||||
@@ -21,34 +21,26 @@ spring:
|
||||
|
||||
ems:
|
||||
datasource:
|
||||
jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM
|
||||
# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM
|
||||
# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM
|
||||
driver-class-name: oracle.jdbc.OracleDriver
|
||||
username: EMSAPP
|
||||
password: elink1234
|
||||
jndi-name: jdbc/dsOBP_EMS
|
||||
schema: EMSAPP
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy
|
||||
entity-package: com.eactive.apim.portal
|
||||
|
||||
gateway:
|
||||
datasource:
|
||||
jdbc-url: jdbc:oracle:thin:@//localhost:1599/DAPM
|
||||
# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/DAPM
|
||||
# jdbc-url: jdbc:oracle:thin:@//192.168.240.177:1599/PAPM
|
||||
driver-class-name: oracle.jdbc.OracleDriver
|
||||
username: AGWAPP
|
||||
password: elink1234
|
||||
jndi-name: jdbc/dsOBP_AGW
|
||||
schema: AGWAPP
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy
|
||||
entity-package: com.eactive.eai.data.entity.onl
|
||||
|
||||
|
||||
portal:
|
||||
auth-virtual-code: 654321
|
||||
test-auth-notice-enabled: true
|
||||
|
||||
proxy:
|
||||
targets:
|
||||
stg: http://localhost:10000/monitoring
|
||||
prod: http://localhost:10000/monitoring
|
||||
|
||||
# stg: http://inter-ndapiap01.k-bank.com:7090/monitoring
|
||||
# prod: http://inter-ndapiap01.k-bank.com:7090/monitoring
|
||||
timeout:
|
||||
connect: 5000
|
||||
read: 5000
|
||||
dev:
|
||||
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||
hot-reload-pages: true
|
||||
@@ -25,12 +25,15 @@ portal:
|
||||
test-auth-notice-enabled: true
|
||||
# 검증 단계에선 사용하지 않음
|
||||
# auth-virtual-code: 654321
|
||||
dev:
|
||||
# application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영
|
||||
hot-reload-pages: true
|
||||
|
||||
|
||||
ems:
|
||||
datasource:
|
||||
jndi-name: jdbc/dsOBP_EMS
|
||||
schema: EMSADM
|
||||
schema: EMSAPP
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy
|
||||
@@ -39,7 +42,7 @@ ems:
|
||||
gateway:
|
||||
datasource:
|
||||
jndi-name: jdbc/dsOBP_AGW
|
||||
schema: AGWADM
|
||||
schema: AGWAPP
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy
|
||||
|
||||
@@ -5,7 +5,7 @@ server:
|
||||
timeout: 15m
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
port: '8080'
|
||||
port: '39130'
|
||||
error:
|
||||
include-message: never
|
||||
include-stacktrace: never
|
||||
@@ -38,7 +38,7 @@ spring:
|
||||
enabled: true
|
||||
atomikos:
|
||||
properties:
|
||||
log-base-dir: /Log/eapim/portal
|
||||
log-base-dir: /logs/atomikos
|
||||
log-base-name: adp_tx
|
||||
|
||||
thymeleaf:
|
||||
@@ -59,7 +59,7 @@ sample-code-path: classpath:/templates/sample_code
|
||||
ems:
|
||||
datasource:
|
||||
jndi-name: jdbc/dsOBP_EMS
|
||||
schema: EMSADM
|
||||
schema: EMSAPP
|
||||
driver-class-name: oracle.jdbc.OracleDriver
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
@@ -69,7 +69,7 @@ ems:
|
||||
gateway:
|
||||
datasource:
|
||||
jndi-name: jdbc/dsOBP_AGW
|
||||
schema: AGWADM
|
||||
schema: AGWAPP
|
||||
driver-class-name: oracle.jdbc.OracleDriver
|
||||
|
||||
hibernate-dialect: org.hibernate.dialect.Oracle12cDialect
|
||||
@@ -82,7 +82,7 @@ portal:
|
||||
allowed-extensions: pdf,doc,docx,xls,xlsx,ppt,pptx,hwp,gif,jpg,jpeg,png
|
||||
|
||||
logging:
|
||||
log-path: /Log/App/eapim/
|
||||
log-path: /logs/eapim
|
||||
|
||||
auth-ttl: 300
|
||||
auth:
|
||||
@@ -175,6 +175,10 @@ portal:
|
||||
method: GET
|
||||
view-name: apps/service/guide
|
||||
|
||||
- path-pattern: /service/oauth2-guide
|
||||
method: GET
|
||||
view-name: apps/service/oauth2-guide
|
||||
|
||||
- path-pattern: /dashboard
|
||||
method: GET
|
||||
view-name: apps/mypage/dashboard
|
||||
@@ -258,6 +262,9 @@ page:
|
||||
guide:
|
||||
name: "회원가입 안내"
|
||||
path: "/service/guide"
|
||||
oauth2_guide:
|
||||
name: "OAuth2 개발가이드"
|
||||
path: "/service/oauth2-guide"
|
||||
apis:
|
||||
name: "API"
|
||||
path: "#"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<configuration scan="true" scanPeriod="5 seconds">
|
||||
<property name="LOG_PATH" value="/Log/App/eapim//${inst.Name:-devSvrUD}"/>
|
||||
<property name="LOG_PATH" value="/logs/eapim/${inst.Name:-devSvrUD}"/>
|
||||
|
||||
<property name="PATTERN" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n" />
|
||||
<property name="PATTERN_DATE" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%-15thread] %-5level %logger - %msg%n" />
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<configuration scan="true" scanPeriod="5 seconds">
|
||||
<property name="LOG_PATH" value="D:\kjb-logs\${inst.Name:-devSvr00}"/>
|
||||
|
||||
<property name="PATTERN" value="%d{HH:mm:ss.SSS} [%thread] [%-3level] %logger - %msg %n" />
|
||||
<property name="PATTERN_DATE" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-3level] %logger - %msg %n" />
|
||||
<property name="PATTERN_CONSOLE" value="%d{HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger - %msg %n" />
|
||||
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder><Pattern>${PATTERN}</Pattern></encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/root.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/root.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder><pattern>${PATTERN}</pattern></encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE_HIBERNATE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/hibernate.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/hibernate.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/http-session.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
|
||||
|
||||
<logger name="com.eactive.apim" level="DEBUG" />
|
||||
<logger name="org.thymeleaf.templateparser" level="DEBUG" />
|
||||
|
||||
|
||||
<!-- Hibernate SQL Logging -->
|
||||
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.engine.transaction" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.engine.jdbc.spi.SqlExceptionHelper" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.springframework.transaction.interceptor" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.springframework.orm.jpa" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
|
||||
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||
<appender-ref ref="HTTP_SESSION" />
|
||||
</logger>
|
||||
</configuration>
|
||||
@@ -1,97 +0,0 @@
|
||||
<configuration scan="true" scanPeriod="5 seconds">
|
||||
<property name="LOG_PATH" value="/eactive/logs/${inst.Name:-devSvr_undefined}"/>
|
||||
<property name="PATTERN" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n" />
|
||||
<property name="PATTERN_DATE" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n" />
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder><pattern>${PATTERN_DATE}</pattern></encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/root.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/root.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder><pattern>${PATTERN}</pattern></encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE_HIBERNATE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/hibernate.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/hibernate.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE_REPORT" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/report.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/report.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/http-session.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
|
||||
<logger name="com.eactive.eapim" level="DEBUG" />
|
||||
|
||||
<!-- hibernate 로그 -->
|
||||
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.engine.transaction.internal.TransactionImpl" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.Transaction" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.hibernate.engine.jdbc.spi.SqlExceptionHelper" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.springframework.transaction" level="TRACE" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.springframework.transaction.interceptor" level="TRACE" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="org.springframework.orm.jpa.JpaTransactionManager" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="FILE_HIBERNATE" />
|
||||
</logger>
|
||||
<logger name="com.eactive.apim.portal.spring.DatabaseSessionVerifier" level="debug">
|
||||
<appender-ref ref="FILE_REPORT" />
|
||||
</logger>
|
||||
|
||||
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||
<appender-ref ref="HTTP_SESSION" />
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
@@ -1,4 +1,4 @@
|
||||
title.html=KJBank API Portal Portal
|
||||
title.html=DJBank Partner Portal
|
||||
password.not.match=Password is not matched
|
||||
authnumber.not.match=Auth number is not matched.
|
||||
apim.route.register.file=File
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
title.html=\uAD11\uC8FC\uC740\uD589 API Portal
|
||||
title.html=DJBank \uD30C\uD2B8\uB108\uD3EC\uD0C8
|
||||
apim.route.register.file=\uCCA8\uBD80\uD30C\uC77C
|
||||
field.required=\uD544\uC218 \uC785\uB825\uD56D\uBAA9\uC785\uB2C8\uB2E4.
|
||||
companyName=\uD68C\uC0AC\uBA85
|
||||
|
||||
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -35,7 +35,7 @@ $shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
$shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2);
|
||||
|
||||
// Typography
|
||||
$font-family-primary: 'Spoqa Han Sans Neo', 'SpoqaHanSans', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
$font-family-primary: '원신한', 'OneShinhan', 'Spoqa Han Sans Neo', 'SpoqaHanSans', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
$font-family-mono: 'Fira Code', 'Courier New', monospace;
|
||||
|
||||
// Font sizes
|
||||
|
||||
@@ -24,8 +24,68 @@
|
||||
//unicode-range: U+AC00-D7AF, U+0000-0040, U+005B-0060, U+007B-007F
|
||||
}
|
||||
|
||||
/* OneShinhan — 웹폰트 서빙 (제주은행 inbank/css/font/ 원본을 정적 리소스로 호스팅)
|
||||
weight 300=Light / 400&500=Medium / 700=Bold */
|
||||
@font-face {
|
||||
|
||||
font-family: 'OneShinhan';
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
src: local('원신한 Light'),
|
||||
local('OneShinhan Light'),
|
||||
local('OneShinhan-Light'),
|
||||
url('/font/djb/OneShinhanLight.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OneShinhan';
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
src: local('원신한 Medium'),
|
||||
local('OneShinhan Medium'),
|
||||
local('OneShinhan-Medium'),
|
||||
url('/font/djb/OneShinhanMedium.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OneShinhan';
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
src: local('원신한 Medium'),
|
||||
local('OneShinhan Medium'),
|
||||
local('OneShinhan-Medium'),
|
||||
url('/font/djb/OneShinhanMedium.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'OneShinhan';
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
src: local('원신한 Bold'),
|
||||
local('OneShinhan Bold'),
|
||||
local('OneShinhan-Bold'),
|
||||
url('/font/djb/OneShinhanBold.woff') format('woff');
|
||||
}
|
||||
/* 한글 family name alias (변수에서 '원신한' 우선 매칭 시 동일 폰트 사용) */
|
||||
@font-face {
|
||||
font-family: '원신한';
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
src: url('/font/djb/OneShinhanLight.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: '원신한';
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
src: url('/font/djb/OneShinhanMedium.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: '원신한';
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
src: url('/font/djb/OneShinhanMedium.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: '원신한';
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
src: url('/font/djb/OneShinhanBold.woff') format('woff');
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ body.design-survey-active {
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
height: 22px;
|
||||
height: 32px;
|
||||
width: 114px;
|
||||
}
|
||||
}
|
||||
@@ -179,10 +179,10 @@ body.design-survey-active {
|
||||
|
||||
// Logo with modern styling
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 22px;
|
||||
display: block;
|
||||
height: 32px;
|
||||
width: 114px;
|
||||
vertical-align: middle;
|
||||
z-index: 10;
|
||||
|
||||
a {
|
||||
@@ -194,17 +194,22 @@ body.design-survey-active {
|
||||
}
|
||||
|
||||
img {
|
||||
height: 22px;
|
||||
height: 32px;
|
||||
width: 114px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: var(--text-dark);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding-top: 4px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// When logo-text is a link
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
@use 'base/reset' as *;
|
||||
@use 'base/typography' as *;
|
||||
@use 'base/animations' as *;
|
||||
@use 'base/kjb-font' as *;
|
||||
@use 'base/djb-font' as *;
|
||||
|
||||
// 3. Layout
|
||||
@use 'layout/header' as *;
|
||||
|
||||
@@ -381,15 +381,16 @@
|
||||
// Card Grid
|
||||
.api-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); // 3 cards per row on desktop
|
||||
grid-template-columns: repeat(3, 320px); // 3 cards per row on desktop (고정폭)
|
||||
gap: $spacing-lg;
|
||||
justify-content: center;
|
||||
|
||||
// Tablet view - 2 cards per row
|
||||
// Tablet view - 2 cards per row (고정폭)
|
||||
@media (max-width: $breakpoint-md) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(2, 320px);
|
||||
}
|
||||
|
||||
// Mobile view - 1 card per row
|
||||
// Mobile view - 1 card per row (화면에 맞춤)
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "광주은행 OAuth 2.0 토큰 발급",
|
||||
"title": "DJBank OAuth 2.0 토큰 발급",
|
||||
"description": "OAuth 2.0 토큰 발급을 위한 API 문서입니다.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
"paths": {
|
||||
"/api/v1/oauth/token": {
|
||||
"post": {
|
||||
"summary": "광주은행 액세스 토큰 발급",
|
||||
"summary": "DJBank 액세스 토큰 발급",
|
||||
"description": "client_credentials grant type을 사용하여 액세스 토큰을 발급받습니다.",
|
||||
"security": [
|
||||
{
|
||||
@@ -51,12 +51,12 @@
|
||||
"client_id": {
|
||||
"type": "string",
|
||||
"maxLength": 256,
|
||||
"description": "광주은행에서 발급한 client id"
|
||||
"description": "DJBank에서 발급한 client id"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string",
|
||||
"maxLength": 512,
|
||||
"description": "광주은행에서 발급한 client secret"
|
||||
"description": "DJBank에서 발급한 client secret"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_title_layout}">
|
||||
layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_title_layout}">
|
||||
layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<th:block layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/DJBank_base_layout}">
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="inner i_cs h_inner8 h_inner10">
|
||||
<div class="swagger_title m-only">
|
||||
<p class="title">API 테스트 베드</p>
|
||||
<p class="text">Kbank API Portal은 Swagger를 이용해 API를 테스트 할 수 있습니다.</p>
|
||||
<p class="text">DJBank API Portal은 Swagger를 이용해 API를 테스트 할 수 있습니다.</p>
|
||||
</div>
|
||||
|
||||
<div class="custom_select select_w h_inp" id="apiList">
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<div class="swagger_infor">
|
||||
<p class="text">인증키가 없는 경우 입력란에 "Kbank"라고 입력하시면 API요청 테스트를 할 수 있습니다.</p>
|
||||
<p class="text">인증키가 없는 경우 API요청 테스트를 할 수 없습니다.</p>
|
||||
</div>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
<body>
|
||||
|
||||
<section layout:fragment="contentFragment" class="content api_con">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{layout/kjbank_title_layout}">
|
||||
layout:decorate="~{layout/djbank_title_layout}">
|
||||
<head>
|
||||
<title>수수료 관리</title>
|
||||
</head>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
<div id="PrintDIV">
|
||||
<h1 style="text-align:center">
|
||||
<img style="height:30px;" th:src="@{/img/logo/logo.png}" alt="광주은행 오픈뱅킹" />
|
||||
<img style="height:30px;" th:src="@{/img/logo/logo-djb.png}" alt="DJBank 오픈뱅킹" />
|
||||
</h1>
|
||||
<p style="text-align:center">우61470 광주광역시 동구 제봉로 225 전화 (062)239-6722 FAX (062)239-6719 오픈뱅크 플랫폼 담당자</p>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
<img th:src="@{/img/img_title_bg.png}" class="title-image" alt="타이틀 배경">
|
||||
<h1>사업 제휴</h1>
|
||||
<h1>피드백/개선요청</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<!-- Title Bar -->
|
||||
<div class="register-title-bar">
|
||||
<h2 class="register-title">사업 제휴 신청</h2>
|
||||
<span class="register-subtitle">광주은행은 온라인 비즈니스 혁신을 위한 사업 제안을 환영합니다.</span>
|
||||
<h2 class="register-title">피드백/개선요청</h2>
|
||||
<span class="register-subtitle">DJBank은 온라인 비즈니스 혁신을 위한 피드백/개선요청을 환영합니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- Form Content -->
|
||||
@@ -24,10 +24,10 @@
|
||||
<form name="partnershipForm" id="partnershipForm" th:action="@{/partnership}" th:object="${partnership}"
|
||||
enctype="multipart/form-data" method="post" class="register-form">
|
||||
|
||||
<!-- 사업 제목 Field -->
|
||||
<!-- 제목 Field -->
|
||||
<div class="form-row">
|
||||
<div class="form-label-wrapper label-offset">
|
||||
<span class="form-label-text">사업 제목</span>
|
||||
<span class="form-label-text">제목</span>
|
||||
<span class="required-badge">필수</span>
|
||||
</div>
|
||||
<div class="form-field-wrapper">
|
||||
@@ -35,7 +35,7 @@
|
||||
id="bizSubject"
|
||||
th:field="*{bizSubject}"
|
||||
class="form-input"
|
||||
placeholder="제안하고자 하시는 사업 제목을 입력해 주세요"
|
||||
placeholder="제안하고자 하는 제목을 입력해 주세요"
|
||||
required
|
||||
maxlength="200">
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- 사업 내용 Field -->
|
||||
<div class="form-row">
|
||||
<div class="form-label-wrapper">
|
||||
<span class="form-label-text">사업 내용</span>
|
||||
<span class="form-label-text">내용</span>
|
||||
<span class="required-badge">필수</span>
|
||||
</div>
|
||||
<div class="form-field-wrapper">
|
||||
@@ -52,7 +52,7 @@
|
||||
th:field="*{bizDetail}"
|
||||
class="form-textarea"
|
||||
rows="10"
|
||||
placeholder="제안하고자 하시는 사업 내용을 상세히 입력해 주세요 (사업의 목적, 내용, 기대 효과 등)"
|
||||
placeholder="제안하고자 하시는 내용을 상세히 입력해 주세요"
|
||||
required
|
||||
maxlength="4000"></textarea>
|
||||
<small class="char-counter-wrapper">
|
||||
@@ -62,7 +62,8 @@
|
||||
</div>
|
||||
|
||||
<!-- File Upload Field -->
|
||||
<div class="form-row">
|
||||
<!--/* 260520 - DJB 에서는 첨부 파일 허용안함. */-->
|
||||
<!--<div class="form-row">
|
||||
<div class="form-label-wrapper label-offset">
|
||||
<span class="form-label-text">첨부파일</span>
|
||||
</div>
|
||||
@@ -99,7 +100,7 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -108,7 +109,7 @@
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-submit btn-secondary" th:onclick="|location.href='@{/}'|">취소</button>
|
||||
<button type="button" class="btn btn-submit btn-primary btn-submit-form">
|
||||
<span>제휴 신청</span>
|
||||
<span>작성 완료</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
<body>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<div class="login-page">
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="login-card">
|
||||
<!-- Logo -->
|
||||
<div class="login-logo">
|
||||
<img th:src="@{/img/logo/logo_box.png}" alt="광주은행">
|
||||
<img th:src="@{/img/logo/logo_box.png}" alt="DJBank">
|
||||
</div>
|
||||
|
||||
<!-- Header Message -->
|
||||
@@ -107,14 +107,6 @@
|
||||
form.checkId.checked = ((form.id.value = getCookie('saveid')) !== null);
|
||||
}
|
||||
|
||||
function encodePassword(password) {
|
||||
return btoa(password).split('').reverse().join('');
|
||||
}
|
||||
|
||||
function decodePassword(encodedPassword) {
|
||||
return atob(encodedPassword.split('').reverse().join(''));
|
||||
}
|
||||
|
||||
function fnInit() {
|
||||
let form = document.getElementById('loginForm');
|
||||
if (form) {
|
||||
@@ -179,8 +171,6 @@
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('[[#{login.passLengthShort}]]');
|
||||
} else {
|
||||
const originalPassword = form.password.value;
|
||||
form.password.value = encodePassword(originalPassword);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_title_layout}">
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_index_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_index_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
@@ -16,12 +16,12 @@
|
||||
<div class="hero-text-content">
|
||||
<div class="hero-text">
|
||||
<p class="hero-subtitle">세상의 모든 서비스</p>
|
||||
<p class="hero-title">광주은행 API가 함께 합니다.</p>
|
||||
<p class="hero-title">DJBank API가 함께 합니다.</p>
|
||||
</div>
|
||||
<a th:href="@{/signup}" class="btn-hero-signup">회원가입하기</a>
|
||||
</div>
|
||||
<div class="hero-image-content">
|
||||
<img th:src="@{/img/hero_image_1.png}" alt="광주은행 API 서비스" class="hero-image">
|
||||
<img th:src="@{/img/hero_image_1.png}" alt="DJBank API 서비스" class="hero-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,12 +32,12 @@
|
||||
<div class="hero-text-content">
|
||||
<div class="hero-text">
|
||||
<p class="hero-subtitle">세상의 모든 서비스</p>
|
||||
<p class="hero-title">광주은행 API가 함께 합니다.</p>
|
||||
<p class="hero-title">DJBank API가 함께 합니다.</p>
|
||||
</div>
|
||||
<a th:href="@{/signup}" class="btn-hero-signup">회원가입하기</a>
|
||||
</div>
|
||||
<div class="hero-image-content">
|
||||
<img th:src="@{/img/hero_image_2.png}" alt="광주은행 API 서비스" class="hero-image">
|
||||
<img th:src="@{/img/hero_image_2.png}" alt="DJBank API 서비스" class="hero-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,12 +48,12 @@
|
||||
<div class="hero-text-content">
|
||||
<div class="hero-text">
|
||||
<p class="hero-subtitle">세상의 모든 서비스</p>
|
||||
<p class="hero-title">광주은행 API가 함께 합니다.</p>
|
||||
<p class="hero-title">DJBank API가 함께 합니다.</p>
|
||||
</div>
|
||||
<a th:href="@{/signup}" class="btn-hero-signup">회원가입하기</a>
|
||||
</div>
|
||||
<div class="hero-image-content">
|
||||
<img th:src="@{/img/hero_image_3.png}" alt="광주은행 API 서비스" class="hero-image">
|
||||
<img th:src="@{/img/hero_image_3.png}" alt="DJBank API 서비스" class="hero-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,7 +156,7 @@
|
||||
<span th:if="${service.groupDesc != null and !#strings.isEmpty(service.groupDesc)}"
|
||||
th:text="${service.groupDesc}">서비스 설명</span>
|
||||
<span th:unless="${service.groupDesc != null and !#strings.isEmpty(service.groupDesc)}">
|
||||
광주은행 API 서비스를 이용해보세요.
|
||||
DJBank API 서비스를 이용해보세요.
|
||||
</span>
|
||||
</p>
|
||||
<div class="card-illustration">
|
||||
@@ -178,15 +178,15 @@
|
||||
<div class="info-content">
|
||||
<h2 class="info-title">
|
||||
차별화된 API 서비스,<br>
|
||||
<span class="title-highlight">광주은행 API Portal</span>
|
||||
<span class="title-highlight">DJBank API Portal</span>
|
||||
</h2>
|
||||
<p class="info-description">
|
||||
광주은행 API Portal은 기업이 금융 서비스를 편리하게 개발할 수 있도록 차별화된 API와 테스트환경을 제공합니다.
|
||||
DJBank API Portal은 기업이 금융 서비스를 편리하게 개발할 수 있도록 차별화된 API와 테스트환경을 제공합니다.
|
||||
</p>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="#" class="action-btn action-btn-primary">
|
||||
처음 만나는 광주은행 API
|
||||
처음 만나는 DJBank API
|
||||
</a>
|
||||
<a href="#" class="action-btn action-btn-secondary">
|
||||
사업 제휴 문의
|
||||
@@ -195,7 +195,7 @@
|
||||
</div>
|
||||
|
||||
<div class="info-image">
|
||||
<img th:src="@{/img/img_api_intro.png}" alt="광주은행 API">
|
||||
<img th:src="@{/img/img_api_intro.png}" alt="DJBank API">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,7 +208,7 @@
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="title-regular">비지니스의 시작,</span><br>
|
||||
<span class="title-bold">광주은행 오픈 API가 함께 하겠습니다.</span>
|
||||
<span class="title-bold">DJBank 오픈 API가 함께 하겠습니다.</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="support-grid">
|
||||
@@ -219,8 +219,8 @@
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3>공지사항</h3>
|
||||
<p class="pc">광주은행 API의 새로운 소식</p>
|
||||
<p class="mobile">광주은행 API의 <br/>새로운 소식</p>
|
||||
<p class="pc">DJBank API의 새로운 소식</p>
|
||||
<p class="mobile">DJBank API의 <br/>새로운 소식</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="title-top">우리곁의 수많은 서비스들이</span>
|
||||
<span class="title-highlight">광주은행 오픈 API</span>와 함께하고 있습니다.
|
||||
<span class="title-highlight">DJBank 오픈 API</span>와 함께하고 있습니다.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="stats-cards">
|
||||
@@ -320,7 +320,7 @@
|
||||
<div class="cta-background"></div>
|
||||
<div class="container">
|
||||
<div class="cta-content">
|
||||
<h2 class="cta-title">광주은행 API를 지금 바로 만나보세요.</h2>
|
||||
<h2 class="cta-title">DJBank API를 지금 바로 만나보세요.</h2>
|
||||
<a th:href="@{/signup}" class="btn-signup-cta">회원가입하기</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||