v1.3.5: 관리자 로그인·대시보드 차트·통계 보관 정리

운영 HTTP에서 관리자 세션이 유지되지 않던 문제를 쿠키 공통화로 수정하고, 통계 클라이언트 분리·조회 오류·기간별 차트를 보강했다. 방문자 해시는 32일 초과분만 정리하고 일별 집계는 누적 보관한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-20 13:54:38 +09:00
parent abb77dbb4d
commit c43873ce5f
16 changed files with 571 additions and 230 deletions

View File

@@ -204,6 +204,13 @@ docker compose --env-file .env.production up -d --build
- 회원 마지막 로그인 표시(`previous_last_seen_at`, `previous_last_seen_ip`)는 `021_add_member_previous_login.sql` 적용 후 정상 동작한다.
- 사이트 로고와 파비콘 저장(`logo_url`, `favicon_url`)은 `022_add_site_logo_urls.sql` 적용 후 정상 동작한다.
### 통계 데이터 보관 정책
- `site_analytics_daily`, `post_analytics_daily`: 사이트 전체 방문자와 게시물별 조회수의 누적 원본이므로 자동 삭제하지 않는다.
- `analytics_daily_visitors`: 일별 중복 방문 제거용 해시만 담으므로 32일 초과 행은 통계 수집·관리자 조회 흐름에서 주기적으로 삭제한다.
- `analytics_active_sessions`: 현재 접속자 목록용 임시 데이터이며 90초 초과 행은 조회·수집 시 삭제한다.
- 관리자 대시보드 차트는 최대 365일 범위를 조회하며, 차트 범위를 넘는 집계도 누적 통계 원본으로 보관한다.
### 개발/운영 DB 분리 검증 절차
검증 전제는 실제 비밀번호나 전체 `DATABASE_URL`을 화면 공유, 문서, 커밋 메시지에 노출하지 않는 것이다. 확인할 때는 호스트, 포트, DB 이름, 파일명만 대조한다.

View File

@@ -1,5 +1,11 @@
# 의사결정 이력
## 2026-05-20 v1.3.5
### 관리자 로그인·대시보드 차트·통계 보관 후속
v1.3.4 통계 확장 이후 운영에서 로그인 쿠키·클라이언트 번들·차트 조회 오류가 겹쳐 후속 정리가 필요했다. 세션 쿠키는 공통 유틸로 묶고, 통계 상수는 `analytics-shared`로 분리했다. 대시보드는 기간별 `trends` 차트와 접속자 목록 가독성을 맞췄다. 저장 용량은 일별 집계는 누적 원본으로 두고 방문자 해시만 32일 초과 시 정리한다.
## 2026-05-15 v1.1.18
### 에디터 미디어 UX·발행일·수정일 표시 설정

View File

@@ -26,7 +26,8 @@
| lib/external-favicon-url.js | 외부 URL 호스트 기준 Google `s2/favicons` 썸네일 URL 생성(내부 경로는 빈 문자열) |
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree`, 관리자 상단 네비 평면 표용 `flattenNavigationEditorWrappers` |
| lib/markdown-content-normalizer.js | 관리자 Markdown-first 전환 후 레거시 블록 배열·객체 본문 값을 저장용 마크다운 문자열로 변환 |
| lib/analytics.js | 통계 추적 경로 필터·봇 UA·일별 visitor hash |
| lib/analytics-shared.js | 통계 추적 경로 필터·체류/스크롤 상수(클라이언트·서버 공용) |
| lib/analytics.js | 서버 전용 visitor/session hash(`node:crypto`) |
## Nuxt 모듈
@@ -115,7 +116,7 @@
| 파일 | 화면 |
|------|------|
| pages/admin/index.vue | 대시보드(실시간 접속자·체류·스크롤·인기 게시물 참여 지표) |
| pages/admin/index.vue | 대시보드(상단 요약: 현재 접속자·오늘 접속자·게시물 수, 기간 선택형 방문자수·평균 체류·50% 스크롤 차트, 접속자 목록, 인기 게시물 참여 지표) |
| pages/admin/login.vue | 관리자 로그인(이메일·비밀번호 모두 입력 시에만 제출 버튼 활성) |
| pages/admin/posts/index.vue | 글 목록, 헤더 우측에 필터(상태·태그·정렬)와 «새 글»을 한 줄 배치, 예약/초안/발행 텍스트 상태, 제목 옆 댓글 수, 읽기 전용 태그 배지, 행 끝 more vert 메뉴(추천·삭제) |
| pages/admin/posts/new.vue | 글 작성, Ghost 스타일 작성 폼, 초안 `POST` 디바운스 자동 저장·이탈 직전 플러시, 저장 토스트 |
@@ -242,7 +243,7 @@
| server/repositories/content-repository.js | 콘텐츠 조회 저장소 |
| server/repositories/member-repository.js | 회원 조회/생성 저장소 |
| server/repositories/comment-repository.js | 댓글 조회/생성 저장소 |
| server/repositories/analytics-repository.js | 방문·게시물 통계 집계·관리자 요약 조회 |
| server/repositories/analytics-repository.js | 방문·게시물 통계 집계·관리자 요약 조회·방문자 해시 보관 정리 |
| server/utils/analytics-pageview-input.js | `POST /api/analytics/pageview` 검증·기록 |
| server/api/analytics/pageview.post.js | 공개 통계 수집 API |
| server/api/analytics/heartbeat.post.js | 공개 heartbeat·체류·스크롤 수집 API |

View File

@@ -366,7 +366,11 @@ components/content/
- 봇 User-Agent는 서버에서 무시.
- 게시물 `reads`는 클라이언트에서 15초 이상 체류·50% 이상 스크롤 후 별도 전송.
- `POST /api/analytics/heartbeat`는 20초 간격으로 체류시간·스크롤·현재 경로를 전송한다. 로그인 사용자는 서버 세션으로 `user_id`를 연결한다.
- 관리자 대시보드는 `GET /admin/api/analytics/realtime`으로 현재 접속자 목록(닉네임·아바타·경로·마지막 활동)을 조회한다.
- 관리자 대시보드는 `GET /admin/api/analytics/realtime`으로 현재 접속자 목록(닉네임·아바타·게시물 제목·접속 유지시간)을 조회한다.
- 관리자 차트는 최대 365일 범위를 조회한다.
- `site_analytics_daily`, `post_analytics_daily`는 사이트 전체 방문자와 게시물별 조회수 누적 원본이므로 자동 삭제하지 않는다.
- `analytics_daily_visitors`는 일별 중복 방문 제거용이며, 수집·조회 흐름에서 32일보다 오래된 행을 주기적으로 삭제한다.
- `analytics_active_sessions`는 현재 접속자 목록용이며, 90초보다 오래된 행을 삭제한다.
---
@@ -429,7 +433,7 @@ components/content/
- `POST /admin/api/auth/login` - 로그인
- `POST /admin/api/auth/logout` - 로그아웃
- `GET /admin/api/auth/me` - 현재 관리자 세션 조회
- `GET /admin/api/analytics/summary?days=30` - 통계 요약(오늘/7일 방문, 30일 조회, 현재 접속자, 평균 체류, 50% 스크롤 도달)
- `GET /admin/api/analytics/summary?days=30` - 통계 요약(오늘/7일 방문, 30일 조회, 현재 접속자, 평균 체류, 50% 스크롤 도달, 일자별 `trends`). `days`는 대시보드에서 7/30/90/180/365로 전환한다.
- `GET /admin/api/analytics/posts?days=30&limit=5` - 기간 내 인기 게시물(조회·읽음·평균 체류·스크롤 구간)
- `GET /admin/api/analytics/realtime?limit=20` - 현재 접속자 요약·목록(로그인 사용자 닉네임·아바타 포함)
- `GET /admin/api/posts` - 글 목록
@@ -614,8 +618,8 @@ components/content/
- DB에 사용자가 없으면 `/signup`에서 관리자 등록 모드(관리자 이름/이메일/비밀번호/비밀번호 확인)로 첫 계정을 생성한다.
- DB에 owner/admin 계정이 없는 최초 상태에서 `/admin/login``ADMIN_EMAIL`/`ADMIN_PASSWORD`와 같은 값을 입력하면 첫 owner 계정을 DB에 생성한 뒤 로그인한다. 같은 이메일의 일반 회원이 이미 있으면 해당 회원을 owner로 승격하고 비밀번호를 `ADMIN_PASSWORD` 기준 bcrypt 해시로 갱신한다. 이미 owner/admin 계정이 있으면 환경 변수 계정으로 우회 로그인하지 않고 DB의 관리자 계정만 사용한다.
- 최초 owner 생성 시 `is_admin=true`, `user_role=owner`를 자동 부여한다. 최초 관리자 판정은 동시 요청에서 중복 owner가 생기지 않도록 `users` 테이블 잠금 안에서 처리한다.
- 관리자 로그인 성공 시 httpOnly 세션 쿠키`/admin` 경로에 설정한다.
- `/admin/login` 로그인 제출 버튼은 이메일·비밀번호가 모두 입력된 경우에만 활성화한다.
- 관리자 로그인 성공 시 httpOnly 세션 쿠키(`sori_admin_session`)를 `/` 경로에 설정한다. Secure는 실제 HTTPS 요청(`x-forwarded-proto` 포함)일 때만 사용한다.
- `/admin/login` 로그인 제출 버튼은 제출 중일 때만 비활성화한다. 빈 값은 브라우저 `required`와 서버 검증으로 처리하며, 자동완성 값은 제출 직전 동기화한다.
- 관리자 로그인 성공 시 관리자/회원 세션 쿠키를 함께 설정하고 `users.previous_last_seen_at`/`previous_last_seen_ip`에 직전 로그인 값을 보존한 뒤 `last_seen_at`/`last_seen_ip`를 현재 로그인으로 갱신한다.
- 관리자 설정 화면은 사이트 자체 설정만 관리한다. 관리자 프로필과 비밀번호는 멤버 편집 화면의 계정 작업에서 처리한다.
- 관리자 사이드바 하단 사용자 메뉴의 `내 프로필``/admin/members/:id` 멤버 편집 화면으로 이동한다.

View File

@@ -1,5 +1,12 @@
# 업데이트 이력
## v1.3.5
- 관리자 로그인: 자동완성 값 동기화·제출 중만 버튼 비활성. 운영 HTTP에서 Secure 쿠키 미저장으로 로그인 루프되던 문제 수정. `server/utils/session-cookie.js`로 path `/`·`x-forwarded-proto` Secure 통일.
- 통계: `lib/analytics-shared.js` 분리로 클라이언트 `node:crypto` 오류 수정. 통계 조회 시작일 JS 계산으로 `date >= integer` 오류 수정.
- 관리자 대시보드: 상단 요약 한 줄·7일~12개월 차트·접속자 목록(게시물 제목·유지시간). `trends` 일자별 0 채움.
- 통계 보관: 일별 집계 누적 보관, 방문자 해시 32일 초과 정리, 실시간 세션 90초 TTL.
## v1.3.4
- 통계 확장: 체류시간·스크롤 구간(25/50/75/100%)·실시간 접속 세션. 마이그레이션 `031_analytics_engagement_and_realtime.sql`.