diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index e8cb903..d276889 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -62,7 +62,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - VITE_API_BASE_URL=${{ vars.VITE_API_BASE_URL || 'https://api.caquick.site' }} + VITE_API_BASE_URL=${{ vars.VITE_API_BASE_URL || 'https://api.caquick.com' }} VITE_NAVER_MAP_CLIENT_ID=${{ vars.VITE_NAVER_MAP_CLIENT_ID }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 5cdcd3b..aa4fdda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN corepack enable COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . -ARG VITE_API_BASE_URL=https://api.caquick.site +ARG VITE_API_BASE_URL=https://api.caquick.com ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} # 네이버 지도 키(공개 키지만 레포가 public이라 기본값을 두지 않는다). 비면 지도 미리보기만 꺼진다 ARG VITE_NAVER_MAP_CLIENT_ID= diff --git a/README.md b/README.md index 510b36b..bc17365 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # caquick-admin-fe -케이퀵(CaQuick) 관리자 웹입니다. React + Vite + TypeScript SPA로, 백엔드([caquick-be](https://github.com/CaQuick/caquick-be))의 관리자 GraphQL·REST API를 사용합니다. 운영 주소는 `admin.caquick.site`입니다. +케이퀵(CaQuick) 관리자 웹입니다. React + Vite + TypeScript SPA로, 백엔드([caquick-be](https://github.com/CaQuick/caquick-be))의 관리자 GraphQL·REST API를 사용합니다. 운영 주소는 `admin.caquick.com`입니다. ## 스택 diff --git a/docs/guide/architecture-conventions.md b/docs/guide/architecture-conventions.md index 44a122f..1c27b36 100644 --- a/docs/guide/architecture-conventions.md +++ b/docs/guide/architecture-conventions.md @@ -5,7 +5,7 @@ ## 1. 큰 그림 ``` -브라우저(admin.caquick.site) ──GraphQL·REST──> api.caquick.site (caquick-be) +브라우저(admin.caquick.com) ──GraphQL·REST──> api.caquick.com (caquick-be) ``` - SPA 1개. 서버 코드가 없다. 정적 파일을 nginx가 서빙하고, 데이터는 전부 백엔드 관리자 API에서 온다. diff --git a/docs/guide/decisions.md b/docs/guide/decisions.md index cc5aa8c..a43f654 100644 --- a/docs/guide/decisions.md +++ b/docs/guide/decisions.md @@ -21,3 +21,4 @@ | D15 | 2026-09-27 | 의존 방향은 ESLint boundaries로 강제(shared→features 금지, feature 간은 index.ts만). 검사기는 반증 케이스로 확인하고 넣는다 | BE `arch:check`와 같은 역할 | | D16 | 2026-10-05 | 커버리지 임계를 실측 정수 내림으로 상향: statements 95 · branches 88 · functions 94 · lines 96. CI는 테스트를 3샤드로 나누고 coverage-report가 합친 결과로 검사 | 실측(95.54·88.07·94.29·96.07)보다 한참 낮은 임계는 회귀를 못 잡는다. 샤드 합산이 단일 실행과 같음을 확인 | | D17 | 2026-10-06 | 리뷰 봇은 Codex만 쓴다. CodeRabbit은 BE 전용 — `.coderabbit.yaml`은 자동 리뷰를 끈 최소 설정만 둔다 | 사용자 결정(판매자 앱도 같음) | +| D18 | 2026-11-01 | 도메인을 `admin.caquick.com`·API `https://api.caquick.com`으로 옮긴다(D9 대체). 이미지 하나에 API 주소와 CSP가 같이 들어가므로 둘을 한 번에 바꾼다 | caquick.site 만료(2026-11-29). 관리자 웹과 API가 같은 사이트여야 refresh 쿠키가 실린다 | diff --git a/infra/README.md b/infra/README.md index abc6f4c..552b737 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,6 +1,6 @@ # infra — 홈서버 배포 -관리자 웹은 nginx 정적 이미지 1개다. BE compose(프로젝트 `caquick`)의 네트워크 `caquick_default`에 external로 참여하고, BE 쪽 cloudflared가 `admin.caquick.site → http://caquick-admin:80`으로 라우팅한다. 인바운드 포트는 열지 않는다. +관리자 웹은 nginx 정적 이미지 1개다. BE compose(프로젝트 `caquick`)의 네트워크 `caquick_default`에 external로 참여하고, BE 쪽 cloudflared가 `admin.caquick.com → http://caquick-admin:80`으로 라우팅한다. 인바운드 포트는 열지 않는다. | 파일 | 역할 | | ----------------------------------- | ----------------------------------------------------------------- | @@ -18,7 +18,7 @@ 1. BE compose가 떠 있어야 한다(네트워크 `caquick_default`). 2. 이 레포용 셀프호스트 러너를 같은 맥미니에 등록한다(라벨 `macmini`). Settings → Actions → Runners → New self-hosted runner. 러너 디렉터리는 BE 러너와 분리(예 `~/actions-runner-admin`). -3. Cloudflare Zero Trust → Tunnel → Public hostname 추가: `admin.caquick.site` → `HTTP` → `caquick-admin:80`. +3. Cloudflare Zero Trust → Tunnel → Public hostname 추가: `admin.caquick.com` → `HTTP` → `caquick-admin:80`. 4. GitHub Environment `production`(선택: 시크릿 `DISCORD_WEBHOOK_URL`, 변수 `DEPLOY_DIR` 기본 `/opt/caquick-admin`). ## 점검 diff --git a/infra/compose.yml b/infra/compose.yml index 13a2aee..7c5972d 100644 --- a/infra/compose.yml +++ b/infra/compose.yml @@ -1,5 +1,5 @@ # 운영 compose(홈서버). BE compose(프로젝트 caquick)의 네트워크에 external로 참여해 기존 cloudflared가 -# admin.caquick.site → http://caquick-admin:80 으로 라우팅한다. 인바운드 포트는 열지 않는다(진단용 127.0.0.1만). +# admin.caquick.com → http://caquick-admin:80 으로 라우팅한다. 인바운드 포트는 열지 않는다(진단용 127.0.0.1만). # 값은 .env — 배포 잡이 IMAGE·IMAGE_TAG를 쓴다. 롤백은 IMAGE_TAG를 이전 sha로 바꿔 다시 실행. name: caquick-admin diff --git a/infra/security-headers.inc b/infra/security-headers.inc index cf3badd..7901c76 100644 --- a/infra/security-headers.inc +++ b/infra/security-headers.inc @@ -6,4 +6,4 @@ add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; -add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://t1.kakaocdn.net https://oapi.map.naver.com https://nrbe.pstatic.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.caquick.site https://*.amazonaws.com https://kr-col-ext.nelo.navercorp.com; frame-src https://postcode.map.kakao.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always; +add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://t1.kakaocdn.net https://oapi.map.naver.com https://nrbe.pstatic.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.caquick.com https://*.amazonaws.com https://kr-col-ext.nelo.navercorp.com; frame-src https://postcode.map.kakao.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always; diff --git a/src/app/shell/app-shell.tsx b/src/app/shell/app-shell.tsx index 5600baf..3b3b073 100644 --- a/src/app/shell/app-shell.tsx +++ b/src/app/shell/app-shell.tsx @@ -17,7 +17,7 @@ function Brand() {
케이퀵 어드민
-
admin.caquick.site
+
admin.caquick.com
); diff --git a/src/shared/api/config.ts b/src/shared/api/config.ts index 4f0020e..4db07aa 100644 --- a/src/shared/api/config.ts +++ b/src/shared/api/config.ts @@ -1,4 +1,4 @@ -/** API 오리진. 개발은 빈 문자열(same-origin, Vite 프록시), 운영 빌드는 VITE_API_BASE_URL(예 https://api.caquick.site). */ +/** API 오리진. 개발은 빈 문자열(same-origin, Vite 프록시), 운영 빌드는 VITE_API_BASE_URL(예 https://api.caquick.com). */ const API_BASE_URL: string = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '') ?? ''; diff --git a/src/test/infra/nginx-config.spec.ts b/src/test/infra/nginx-config.spec.ts index 0812680..0bf6ee0 100644 --- a/src/test/infra/nginx-config.spec.ts +++ b/src/test/infra/nginx-config.spec.ts @@ -28,7 +28,7 @@ describe('nginx 설정', () => { it('CSP는 API·S3 오리진을 허용하고 프레임을 막는다', () => { const csp = /Content-Security-Policy "([^"]+)"/.exec(read('security-headers.inc'))?.[1] ?? ''; - expect(csp).toContain("connect-src 'self' https://api.caquick.site https://*.amazonaws.com"); + expect(csp).toContain("connect-src 'self' https://api.caquick.com https://*.amazonaws.com"); expect(csp).toContain("frame-ancestors 'none'"); expect(csp).not.toContain('\n'); }); @@ -58,7 +58,7 @@ describe('nginx 설정', () => { // maps.js의 오류 수집 XHR. 막아도 지도는 뜨지만 콘솔 위반이 장애로 오인된다 expect(d.get('connect-src')).toEqual([ "'self'", - 'https://api.caquick.site', + 'https://api.caquick.com', 'https://*.amazonaws.com', 'https://kr-col-ext.nelo.navercorp.com', ]); diff --git a/vite.config.ts b/vite.config.ts index cd03942..e640d2f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,7 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // 로컬 개발은 /graphql·/auth를 백엔드(기본 localhost:4000)로 프록시해 same-origin으로 만든다 — CORS·쿠키 설정 없이 refresh 쿠키가 돈다. -// 운영 빌드는 VITE_API_BASE_URL(절대 URL)로 api.caquick.site에 직접 붙는다. +// 운영 빌드는 VITE_API_BASE_URL(절대 URL)로 api.caquick.com에 직접 붙는다. export default defineConfig({ plugins: [ tanstackRouter({