13 Commits

Author SHA1 Message Date
ttipo 97780eb4b6 feat(backend): 최고관리자가 만드는 업체 계정과 업체 실적 화면 API
관리자 화면에는 env 계정 하나만 있어 업체가 자기 실적을 볼 방법이 없었다.
env 계정은 최고관리자로 그대로 두고, 최고관리자가 업체마다 로그인 계정을 만든다.

- admin_accounts(업체 계정) · admin_sessions.account_id — 비밀번호는 bcrypt 해시만
- 토큰에 역할(super·partner)을 싣고, 계정 지문으로 비밀번호 변경·초기화 시 다른 기기를 끊는다.
  중지된 계정은 즉시 거부한다
- 가드 분리: AdminAuthGuard 는 최고관리자 전용(기존 관리 API 전부, 업체 토큰은 AUTH_003),
  PartnerAuthGuard 는 업체 계정 전용(임시 비밀번호면 ACCOUNT_003), AccountAuthGuard 는 공통
- 업체 계정 관리: 만들기·사용/중지·비밀번호 초기화 — 임시 비밀번호는 응답에서 한 번만
- 업체 화면 /api/partner-portal: 요약·링크·신청·구매. 업체 ID 는 로그인 정보에서만 가져오고
  신청·구매자는 가린 이름만 내려준다 (연락처·이메일·아이디·주문번호 없음)
- 없는 아이디도 bcrypt 비교를 거쳐 응답 시간으로 아이디 존재를 알 수 없게 한다.
  bcryptjs(순수 JS)라 비용은 10 — 무차별 대입은 로그인 요청 제한이 함께 막는다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:22:20 +09:00
ttipo 7b5d16bfea feat(backend): 업체 계정용 에러 코드 등록 (ACCOUNT_001~003)
- ACCOUNT_001 (409): 이미 사용 중인 아이디
- ACCOUNT_002 (400): 현재 비밀번호 불일치
- ACCOUNT_003 (403): 임시 비밀번호를 바꾸기 전 실적 조회

npm run sync:error-codes 로 프론트엔드·Flutter 사전을 함께 갱신했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:22:20 +09:00
ttipo 1f71ce048f feat(backend): 주문완료 화면의 구매 때 유입 수집과 비회원 구매 업체 귀속
아임웹 주문 API 에는 손님이 어디서 왔는지가 없어 구매는 가입 경로로만 볼 수 있었고,
비회원 구매는 회원 코드가 없어 업체 링크 실적으로 잡을 수 없었다.

- POST /api/order-inflows: 주문완료 화면이 주문번호·유입·업체 클릭 ID 를 보낸다
  (공개 · 1분 10회 · 항상 204 · 같은 주문번호는 처음 값만)
- 주문 동기화가 같은 주문번호의 구매에 유입을 채운다 (비어 있는 구매에만)
- 업체 귀속 후보 = 회원 클릭 연결 + 주문완료 클릭 ID → 비회원 구매도 마지막 클릭에 귀속
  (partner_conversions.member_code nullable, 실적 상세에 주문자 이름)
- 90일 동안 주문과 이어지지 않은 값은 동기화가 끝날 때 지운다
- 관리자 조회: 유입 기준 apply 를 신청·구매 때 유입으로 넓힌다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:14:33 +09:00
ttipo 8d4adfd7f1 feat(imweb): 업체 링크로 들어오면 신청 때 유입을 그 업체로 바꿈
광고로 가입한 회원이 업체 링크로 다시 와서 신청해도, 보관 중인 첫 경로(광고)가
남아 신청 때 유입이 광고로 잡혔다. 업체 칸(업체 실적)은 그 업체라 한 줄 안에서
유입과 업체가 어긋났다.

업체 단축 링크가 붙인 UTM(utm_medium=partner)으로 들어오면 첫 경로를 그 업체로
덮어쓴다. 여러 업체를 거치면 마지막 업체 — 업체 실적의 마지막 클릭 규칙과 같다.
화면의 유입 기준은 그대로 하나만 둔다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:14:22 +09:00
ttipo ab7ef371dc feat: 무료 신청과 유료 구매를 한 화면에서 보는 신청·구매 내역 추가
유료 구매는 업체 링크로 귀속된 것만 남아 누가 구매했는지 볼 수 없었다.
주문 동기화가 결제된 아임웹 주문을 회원·비회원 모두 purchases 에 남기고
(서버 기동 후 첫 실행 1년, 이후 60일), 관리자 조회는 신청과 구매를 합쳐
보여주는 /admin/activities 로 옮겼다. 예전 /admin/applications 조회는 없앴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 02:33:56 +09:00
ttipo b5ed8850c7 feat: 관리자 화면 전체 개편과 신청 내역 상품별·업체별 보기
관리자 화면이 기능을 덧붙이며 복잡해져(회원 표 15열, 업체 화면에 폼·표·설명이 모두 펼쳐짐,
브라우저 기본 prompt/confirm) 보기 어렵고, 신청을 업체 기준으로 볼 방법이 없었다.

- 메뉴를 개요 / 운영(신청 내역·업체 링크·회원) / 설정(연동 관리)으로 묶음
- 개요: 오늘·7일 신청, 7일 업체 링크 신청, 7일 가입 지표 + 신청 유입·업체 실적 상위·최근 신청
- 신청 내역: 상품별 ↔ 업체별 전환 — 요약 표의 행을 누르면 목록과 유입 집계를 그 항목으로 좁힘,
  신청자 표에 업체 열 추가, 행을 누르면 상세 패널(신청·유입·회원·가입 경로)
- 업체 링크: 왼쪽 업체 목록 + 오른쪽 링크 표, 실적 지표, 링크 발급·수정은 대화상자,
  사용/중지 스위치, 링크 실적은 상세 패널, 집계 기준은 접는 도움말
- 회원: 표를 7열로 줄이고 유입경로 필터는 접어 둠, 행을 누르면 상세 패널
- 토스트 알림·대화상자·상세 패널 공통 컴포넌트, 연락처 하이픈 표시, 휴대폰 화면 정리
- API: GET /api/admin/applications/groups (상품별·업체별 신청 수·오픈카톡 입장 수),
  신청 목록·유입 집계에 partnerId 필터(업체 ID 또는 none), 신청 응답에 데려온 업체(partner)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:37:49 +09:00
ttipo cab7e840fe feat: 업체 링크를 아임웹 주소(/go)로 바꾸고 업체 UTM 자동 부착
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
업체에 우리 백엔드 도메인(moneyguide.wandergo.net)이 아니라 사이트 주소를 주기 위해서다.
또 업체 링크로 온 방문이 UTM 이 없어 direct 로 분류되면서, 이전에 남은 유입(예: 개발자센터)이
신청 때 유입으로 잡히는 문제가 있었다.

- 업체에 줄 링크: https://www.moneyguider.co.kr/go?c={코드}
  imweb-header-top.html 맨 위의 입구 블록이 페이지를 그리기 전에 백엔드 /r/{코드} 로 넘긴다
  (입구 페이지에서는 UTM 스크립트가 기록하지 않는다). 아임웹에 'go' 페이지가 필요하다
- 기존 백엔드 링크(/r/{코드})는 그대로 동작한다
- 단축 링크 리다이렉트에 업체 UTM 자동 부착 — utm_source=업체 이름, utm_medium=partner,
  utm_campaign=링크 코드 (이동 주소에 관리자가 넣은 UTM 이 있으면 그대로 둔다)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:01:00 +09:00
ttipo 38e945767b feat: 신청 때 최종 유입 저장과 신청 유입 집계 추가
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
가입 경로와 신청(수강) 유입을 구분해 보기 위해서다. 기존 신청 유입은 신청 시점
브라우저의 '최초' 유입(2일 보관)이라, 그 사이 다른 경로로 다시 와서 신청하면
신청을 일으킨 경로가 가려졌다.

- member_applications 에 최종 유입(inflow_last_*) 저장 — 없으면 최초 유입으로 채움 (기존 행도 동일)
- 신청 완료 위젯이 MG_UTM.peek().last 를 lastInflow 로 함께 보냄
- GET /api/admin/applications/inflow-stats: 신청 때 최종·최초 / 가입 경로(가입 때 기록만) 기준 집계
- 신청 목록에 inflowBasis·inflowField·inflowValue 필터
- 관리자 화면 신청 내역: '신청 유입 집계' 카드(기준·항목 전환, 막대 클릭으로 목록 필터),
  신청 때 유입 칸에 최종 유입 표시 + 최초가 다르면 함께 표시

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:22:02 +09:00
ttipo e91f7620a3 feat: 업체 링크 추적을 실제 아임웹 Header Code 로 합치고 신청 유입정보 누락 수정
- imweb-header-top.html·imweb-header.html: 아임웹에 실제로 들어가 있는 Header Code 원본을 저장소에 둔다
- 업체 링크 추적을 imweb-header-top.html 의 UTM 스크립트 아래로 옮기고 partner-ref.html 은 삭제
  (UTM 스크립트처럼 localStorage + .moneyguider.co.kr 쿠키에 저장 — www ↔ apex 이동·인앱브라우저 대비)
- 신청 완료 위젯: MG_UTM.peek() 는 객체라 문자열 검사에 걸려 유입정보가 빠지고 있었다 — peek().value 를 보낸다
- 단축 링크 리다이렉트에 Referrer-Policy strict-origin-when-cross-origin
  (helmet 기본 no-referrer 때문에 업체가 링크를 올린 곳이 지워져 유입원이 direct 로 잡혔다)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:11:52 +09:00
ttipo 7bef853664 feat(backend): 업체 링크별 신청·구매 추적 API 추가
상품마다 업체에 단축 링크를 발급하고, 그 링크로 들어온 회원의 신청·구매를
업체별로 세기 위해서다. 아임웹은 주문 완료를 알려주지 않으므로 구매는
주문 API 를 주기적으로 훑어 찾는다.

- 아임웹 상품 동기화(imweb_products) — 회원 동기화와 같은 주기, 실패해도 회원 동기화는 계속
- 단축 링크 /r/{코드}: 사람의 클릭만 기록하고 클릭 ID(mg_ref)를 붙여 상품 페이지로 이동
  (이동 주소는 아임웹 사이트 안만 허용 — open redirect 방지)
- 회원 ↔ 클릭 연결(POST /api/partner-touches), 신청 기록에 클릭 ID 추가
- 마지막 클릭·30일 귀속, 결제완료 주문만 구매로 집계하고 환불·취소는 제외 (금액은 관리하지 않음)
- 관리자 API: 업체·링크 관리, 링크별 실적 상세, 구매 집계 새로고침
- 권한 요청에 product:read·order:read 추가, 토큰 권한 부족은 IMWEB_006
- 에러 코드 IMWEB_006, PARTNER_001~004 추가 및 클라이언트 사전 동기화

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:47:10 +09:00
ttipo 2f01bea6ef fix: 운영 백엔드 주소 오타 수정 (moneyguider → moneyguide.wandergo.net)
운영 백엔드 도메인은 moneyguide.wandergo.net 인데 관리자 화면 API_BASE 와 문서가
moneyguider.wandergo.net 을 가리켜 로그인 요청이 TLS 단계에서 거절되었다
(ERR_SSL_UNRECOGNIZED_NAME_ALERT). 아임웹 사이트 주소 moneyguider.co.kr 은 그대로 둔다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:14:11 +09:00
ttipo c54df9cefa feat(backend): 아임웹 회원 유입경로·신청 기록 관리자 API 추가
아임웹 관리자 페이지에서 회원을 유입경로·그룹 기준으로 보고, 무료 강의 신청을
기록·조회하기 위한 백엔드다. 관리자 화면(아임웹)과 API(wandergo.net)가 다른 사이트라
인증은 쿠키 대신 Bearer 토큰으로 한다.

- 관리자 인증: env 단일 계정(ADMIN_USERNAME/PASSWORD)을 여러 사람이 공유,
  기기별 세션(로그아웃은 그 기기만, 비밀번호 변경 시 전체 무효)
- 아임웹 Open API 연동: OAuth 연결·토큰 갱신, 회원·그룹 주기 동기화,
  가입 폼 '홈페이지' 항목의 유입정보 파싱, 회원 엑셀 '유입경로' 가져오기 스크립트
- 회원 조회: 유입경로·그룹·기간 필터, 유입원 집계
- 강의 신청 기록: 신청 완료 화면이 보내는 공개 API(회원 확인·중복 방지·요청 제한),
  오픈카톡 클릭 기록, 관리자 신청 내역 조회
- CORS 다중 도메인, env 검증(운영 필수값), CI e2e 전 마이그레이션 적용,
  개발 컨테이너 메모리 한도 2g, 에러 코드 IMWEB_001~005·APPLY_001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:21:25 +09:00
ttipo 4be82a716b Initial commit 2026-10-04 21:27:26 +09:00