From 26660d7fee684f61c97e13f735fe284ae90ef9e7 Mon Sep 17 00:00:00 2001 From: pizzazoa Date: Fri, 16 Jan 2026 17:07:31 +0900 Subject: [PATCH 1/2] =?UTF-8?q?init:=20gitignore=20=EB=B3=B4=EA=B0=95,=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=98=88=EC=8B=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 17 +++++++++++++++++ .gitignore | 29 ++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..359d6d4 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# Spring +SPRING_PROFILES_ACTIVE=local +SERVER_PORT=8081 + +# Backend API +SAEROK_API_BASE_URL=http://localhost:8080 +SAEROK_API_PREFIX=/api/v1 + +# OAuth +KAKAO_CLIENT_ID=your-kakao-client-id +KAKAO_REDIRECT_URI=http://localhost:8081/auth/kakao/callback +APPLE_CLIENT_ID=your-apple-client-id +APPLE_REDIRECT_URI=http://localhost:8081/auth/apple/callback + +# Unsplash +UNSPLASH_ACCESS_KEY=your-unsplash-access-key +UNSPLASH_APP_NAME=your-unsplash-app-name diff --git a/.gitignore b/.gitignore index 3ee1300..7d80e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,32 @@ out/ ### VS Code ### .vscode/ +.codex/ + .env -context/ \ No newline at end of file +.env.local +.env.development +.env.test +.env.production +.env.*.local +!.env.example +context/ + +### OS ### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes + +### Logs ### +logs/ +*.log +*.log.* + +### JVM ### +hs_err_pid* +replay_pid* + +### Spring Boot ### +/.spring-boot-devtools.properties From ab7b1b6e72e66bd70f0d583599c14c8962c18aa1 Mon Sep 17 00:00:00 2001 From: pizzazoa Date: Thu, 26 Feb 2026 16:52:40 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EB=A9=94?= =?UTF-8?q?=ED=8A=B8=EB=A6=AD=20=EC=B6=94=EA=B0=80=20-=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EA=B0=80=EC=9E=85=20=EA=B2=BD=EB=A1=9C,=20?= =?UTF-8?q?=EB=94=94=EB=B0=94=EC=9D=B4=EC=8A=A4=20=ED=94=8C=EB=9E=AB?= =?UTF-8?q?=ED=8F=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../saerok_admin/infra/stat/StatMetric.java | 16 ++++++ .../resources/static/js/service-insight.js | 49 ++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/main/java/apu/saerok_admin/infra/stat/StatMetric.java b/src/main/java/apu/saerok_admin/infra/stat/StatMetric.java index 1406844..cab2b87 100644 --- a/src/main/java/apu/saerok_admin/infra/stat/StatMetric.java +++ b/src/main/java/apu/saerok_admin/infra/stat/StatMetric.java @@ -96,6 +96,22 @@ public enum StatMetric { false, Map.of(), false + ), + USER_SIGNUP_SOURCE_TOTAL( + "가입 경로별 누적 사용자", + "가입 경로(소셜 로그인 종류)별 누적 가입자 수", + MetricUnit.COUNT, + true, + Map.of(), + false + ), + USER_DEVICE_PLATFORM_TOTAL( + "기기 플랫폼별 누적 사용자", + "기기 플랫폼(iOS/Android/Web 등)별 누적 사용자 수", + MetricUnit.COUNT, + true, + Map.of(), + false ); private final String label; diff --git a/src/main/resources/static/js/service-insight.js b/src/main/resources/static/js/service-insight.js index 771d1e2..0c08f66 100644 --- a/src/main/resources/static/js/service-insight.js +++ b/src/main/resources/static/js/service-insight.js @@ -73,6 +73,8 @@ USER_DAU: 'USER_DAU', USER_WAU: 'USER_WAU', USER_MAU: 'USER_MAU', + USER_SIGNUP_SOURCE_TOTAL: 'USER_SIGNUP_SOURCE_TOTAL', + USER_DEVICE_PLATFORM_TOTAL: 'USER_DEVICE_PLATFORM_TOTAL', }; // (아래부터는 기존 로직 그대로입니다. 색상/그룹/플롯/툴팁/박스플롯 렌더링 등 전체 원본 유지) @@ -167,7 +169,8 @@ { key:'collection', name:'새록', metrics:[METRICS.TOTAL_COUNT, METRICS.PRIVATE_RATIO] }, { key:'user', name:'유저', metrics:[ METRICS.USER_COMPLETED_TOTAL, METRICS.USER_SIGNUP_DAILY, METRICS.USER_WITHDRAWAL_DAILY, - METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU + METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU, + METRICS.USER_SIGNUP_SOURCE_TOTAL, METRICS.USER_DEVICE_PLATFORM_TOTAL ] }, { key:'id', name:'동정 요청', metrics:[METRICS.PENDING_COUNT, METRICS.RESOLVED_COUNT, METRICS.RESOLUTION_STATS] }, ]; @@ -683,6 +686,50 @@ groupSet.add(id); changed = true; } + } else if (opt.multiSeries) { + // 일반 다중 시리즈: 컴포넌트별 라인 렌더링 + const series = seriesMap.get(metric); + const comps = Array.isArray(series?.components) ? series.components : []; + const labelsMap = (componentLabels[metric] && typeof componentLabels[metric] === 'object') ? componentLabels[metric] : {}; + const unit = String(opt.unit || '').toUpperCase(); + const yAxis = axisByUnit(unit); + + comps.forEach(comp => { + if (!comp || !comp.key) return; + const compKey = comp.key; + const id = datasetIdFor(metric, compKey); + if (p.datasets.has(id)) return; + + const compColor = colorForMetric(id); + const compLabel = labelsMap[compKey] || compKey; + const points = (Array.isArray(comp.points) ? comp.points : []) + .map(pt => { + const x = toDateKST(pt.date); + const y = normalizeValue(pt.value, unit); + return (x && y != null) ? { x, y } : null; + }) + .filter(Boolean); + + const ds = { + _id: id, + label: `${opt.label || metric} (${compLabel})`, + data: points, + parsing: { xAxisKey: 'x', yAxisKey: 'y' }, + borderColor: compColor, + backgroundColor: compColor, + tension: 0.25, + pointRadius: 0, + pointHoverRadius: 4, + fill: false, + spanGaps: true, + yAxisID: yAxis, + _saUnit: unit + }; + p.chart.data.datasets.push(ds); + p.datasets.add(id); + groupSet.add(id); + changed = true; + }); } else { const unit = String(optionMap.get(metric)?.unit || '').toUpperCase(); const s = seriesMap.get(metric);