Skip to content

Commit 8f06ac5

Browse files
committed
migration cloudflare account
1 parent 2bbc48e commit 8f06ac5

3 files changed

Lines changed: 330 additions & 11 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ wrangler deploy
286286
### 커스텀 도메인
287287

288288
- Production: https://github.dalestudy.com
289-
- Worker.dev: https://dalestudy.daleseo.workers.dev
289+
- Worker.dev: https://github.daleseo.workers.dev
290290

291291
자세한 배포 가이드는 `DEPLOYMENT.md` 참고.
292292

@@ -372,31 +372,25 @@ curl -X POST https://github.dalestudy.com/check-weeks \
372372
## 코드 수정 시 주의사항
373373

374374
1. **Octokit 사용 금지**
375-
376375
- Cloudflare Workers에서 작동하지 않음
377376
- fetch API 직접 사용
378377

379378
2. **Private Key 처리**
380-
381379
- PKCS8 또는 PKCS1 형식 지원
382380
- Web Crypto API로 import
383381

384382
3. **GraphQL 쿼리 주의**
385-
386383
- GraphQL 쿼리에서 변수를 문자열 템플릿으로 직접 삽입 (GraphQL 변수 문법 사용 안 함)
387384
- 입력값 검증이 중요 (SQL Injection 스타일 취약점 방지)
388385

389386
4. **에러 핸들링**
390-
391387
- Worker는 에러 발생 시 500 반환
392388
- 로그는 `wrangler tail`로 확인
393389

394390
5. **CORS 헤더**
395-
396391
- 모든 응답에 CORS 헤더 포함 (`Access-Control-Allow-Origin: *`)
397392

398393
6. **코드 재사용**
399-
400394
- GitHub 인증 로직 (`generateGitHubAppToken`, `createJWT` 등)은 모든 기능에서 공통으로 사용
401395
- 새 기능 추가 시 기존 유틸리티 함수 활용
402396

test-migration.sh

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
#!/bin/bash
2+
#
3+
# Cloudflare 계정 이전 후 기능 검증 테스트
4+
# Usage: ./test-migration.sh [BASE_URL]
5+
#
6+
7+
BASE_URL="${1:-https://github.dalestudy.com}"
8+
BASE_URL="${BASE_URL%/}"
9+
10+
# --- Colors ---
11+
GREEN='\033[0;32m'
12+
RED='\033[0;31m'
13+
YELLOW='\033[0;33m'
14+
CYAN='\033[0;36m'
15+
NC='\033[0m'
16+
17+
# --- Counters ---
18+
PASS_COUNT=0
19+
FAIL_COUNT=0
20+
SKIP_COUNT=0
21+
22+
# --- Helpers ---
23+
pass() {
24+
echo -e " ${GREEN}[PASS]${NC} $1"
25+
((PASS_COUNT++))
26+
}
27+
28+
fail() {
29+
echo -e " ${RED}[FAIL]${NC} $1"
30+
[ -n "$2" ] && echo -e " $2"
31+
((FAIL_COUNT++))
32+
}
33+
34+
skip() {
35+
echo -e " ${YELLOW}[SKIP]${NC} $1"
36+
((SKIP_COUNT++))
37+
}
38+
39+
section() {
40+
echo ""
41+
echo -e "${CYAN}=== $1 ===${NC}"
42+
}
43+
44+
# --- Prereq ---
45+
if ! command -v curl &>/dev/null; then
46+
echo "curl이 설치되어 있지 않습니다."
47+
exit 1
48+
fi
49+
50+
echo ""
51+
echo "Target: $BASE_URL"
52+
53+
# ============================================================
54+
# Tier 1: Connectivity & Routing
55+
# ============================================================
56+
section "Tier 1: Connectivity & Routing"
57+
58+
# 1.1 CORS preflight
59+
HEADERS=$(curl -s -D- -o /dev/null -X OPTIONS "$BASE_URL/check-weeks" --max-time 30 2>/dev/null)
60+
if echo "$HEADERS" | grep -qi "Access-Control-Allow-Origin"; then
61+
pass "1.1 CORS preflight"
62+
else
63+
fail "1.1 CORS preflight" "Access-Control-Allow-Origin 헤더 없음"
64+
fi
65+
66+
# 1.2 GET 차단 (405)
67+
BODY=$(curl -s -w '\n%{http_code}' -X GET "$BASE_URL/check-weeks" --max-time 30 2>/dev/null)
68+
STATUS=$(echo "$BODY" | tail -1)
69+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
70+
if [ "$STATUS" = "405" ] && echo "$BODY_CONTENT" | grep -q "Method not allowed"; then
71+
pass "1.2 GET -> 405 Method Not Allowed"
72+
else
73+
fail "1.2 GET -> 405 Method Not Allowed" "status=$STATUS, body=$BODY_CONTENT"
74+
fi
75+
76+
# 1.3 없는 경로 (404)
77+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/nonexistent-path" \
78+
-H "Content-Type: application/json" -d '{}' --max-time 30 2>/dev/null)
79+
STATUS=$(echo "$BODY" | tail -1)
80+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
81+
if [ "$STATUS" = "404" ] && echo "$BODY_CONTENT" | grep -q "Not found"; then
82+
pass "1.3 POST /nonexistent -> 404"
83+
else
84+
fail "1.3 POST /nonexistent -> 404" "status=$STATUS, body=$BODY_CONTENT"
85+
fi
86+
87+
# 1.4 조직 검증 (403)
88+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/check-weeks" \
89+
-H "Content-Type: application/json" \
90+
-d '{"repo_owner":"FakeOrg","repo_name":"leetcode-study"}' --max-time 30 2>/dev/null)
91+
STATUS=$(echo "$BODY" | tail -1)
92+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
93+
if [ "$STATUS" = "403" ] && echo "$BODY_CONTENT" | grep -q "Unauthorized"; then
94+
pass "1.4 check-weeks 조직 검증 -> 403"
95+
else
96+
fail "1.4 check-weeks 조직 검증 -> 403" "status=$STATUS, body=$BODY_CONTENT"
97+
fi
98+
99+
# 1.5 필수 필드 누락 (400)
100+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/check-weeks" \
101+
-H "Content-Type: application/json" -d '{}' --max-time 30 2>/dev/null)
102+
STATUS=$(echo "$BODY" | tail -1)
103+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
104+
if [ "$STATUS" = "400" ] && echo "$BODY_CONTENT" | grep -q "Missing required field"; then
105+
pass "1.5 check-weeks 필수 필드 누락 -> 400"
106+
else
107+
fail "1.5 check-weeks 필수 필드 누락 -> 400" "status=$STATUS, body=$BODY_CONTENT"
108+
fi
109+
110+
# 1.6 approve-prs week 누락 (400)
111+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/approve-prs" \
112+
-H "Content-Type: application/json" \
113+
-d '{"repo_name":"leetcode-study"}' --max-time 30 2>/dev/null)
114+
STATUS=$(echo "$BODY" | tail -1)
115+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
116+
if [ "$STATUS" = "400" ] && echo "$BODY_CONTENT" | grep -q "week"; then
117+
pass "1.6 approve-prs week 누락 -> 400"
118+
else
119+
fail "1.6 approve-prs week 누락 -> 400" "status=$STATUS, body=$BODY_CONTENT"
120+
fi
121+
122+
# 1.7 merge-prs week 누락 (400)
123+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/merge-prs" \
124+
-H "Content-Type: application/json" \
125+
-d '{"repo_name":"leetcode-study"}' --max-time 30 2>/dev/null)
126+
STATUS=$(echo "$BODY" | tail -1)
127+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
128+
if [ "$STATUS" = "400" ] && echo "$BODY_CONTENT" | grep -q "week"; then
129+
pass "1.7 merge-prs week 누락 -> 400"
130+
else
131+
fail "1.7 merge-prs week 누락 -> 400" "status=$STATUS, body=$BODY_CONTENT"
132+
fi
133+
134+
# 1.8 경로 별칭 (underscore)
135+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/approve_prs" \
136+
-H "Content-Type: application/json" \
137+
-d '{"repo_name":"leetcode-study"}' --max-time 30 2>/dev/null)
138+
STATUS=$(echo "$BODY" | tail -1)
139+
if [ "$STATUS" = "400" ]; then
140+
pass "1.8 /approve_prs 경로 별칭 라우팅"
141+
else
142+
fail "1.8 /approve_prs 경로 별칭 라우팅" "status=$STATUS (expected 400)"
143+
fi
144+
145+
# ============================================================
146+
# Tier 2: GitHub App 인증 & check-weeks
147+
# ============================================================
148+
section "Tier 2: GitHub App 인증 (check-weeks)"
149+
150+
echo -e " ${YELLOW}(PR 수에 따라 최대 60초 소요)${NC}"
151+
152+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/check-weeks" \
153+
-H "Content-Type: application/json" \
154+
-d '{"repo_name":"leetcode-study"}' --max-time 60 2>/dev/null)
155+
STATUS=$(echo "$BODY" | tail -1)
156+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
157+
158+
# 2.1 성공 응답
159+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q '"success":true'; then
160+
pass "2.1 check-weeks 성공 (GitHub App 인증 OK)"
161+
else
162+
fail "2.1 check-weeks 성공" "status=$STATUS, body=$BODY_CONTENT"
163+
fi
164+
165+
# 2.2 응답 구조 검증
166+
FIELDS_OK=true
167+
for field in total_prs checked results; do
168+
if ! echo "$BODY_CONTENT" | grep -q "\"$field\""; then
169+
FIELDS_OK=false
170+
break
171+
fi
172+
done
173+
if [ "$FIELDS_OK" = true ] && [ "$STATUS" = "200" ]; then
174+
pass "2.2 응답 구조 (total_prs, checked, results)"
175+
else
176+
fail "2.2 응답 구조" "필드 누락: $BODY_CONTENT"
177+
fi
178+
179+
# ============================================================
180+
# Tier 3: Webhook 엔드포인트
181+
# ============================================================
182+
section "Tier 3: Webhook 엔드포인트"
183+
184+
# WEBHOOK_SECRET 감지
185+
PROBE=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
186+
-H "Content-Type: application/json" -d '{}' --max-time 30 2>/dev/null)
187+
PROBE_STATUS=$(echo "$PROBE" | tail -1)
188+
189+
if [ "$PROBE_STATUS" = "401" ]; then
190+
echo -e " ${YELLOW}WEBHOOK_SECRET이 활성 상태입니다. 서명 없이 테스트 불가.${NC}"
191+
skip "3.1 미지원 이벤트 무시"
192+
skip "3.2 잘못된 조직 무시"
193+
skip "3.3 잘못된 저장소 무시"
194+
skip "3.4 미처리 액션 무시"
195+
skip "3.5 멘션 없는 댓글 무시"
196+
skip "3.6 PR 아닌 댓글 무시"
197+
else
198+
# 3.1 미지원 이벤트
199+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
200+
-H "Content-Type: application/json" \
201+
-H "X-GitHub-Event: ping" \
202+
-d '{"organization":{"login":"DaleStudy"},"repository":{"name":"leetcode-study"}}' \
203+
--max-time 30 2>/dev/null)
204+
STATUS=$(echo "$BODY" | tail -1)
205+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
206+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "Ignored"; then
207+
pass "3.1 ping 이벤트 -> Ignored"
208+
else
209+
fail "3.1 ping 이벤트 -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
210+
fi
211+
212+
# 3.2 잘못된 조직
213+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
214+
-H "Content-Type: application/json" \
215+
-H "X-GitHub-Event: pull_request" \
216+
-d '{"organization":{"login":"FakeOrg"}}' \
217+
--max-time 30 2>/dev/null)
218+
STATUS=$(echo "$BODY" | tail -1)
219+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
220+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "not DaleStudy"; then
221+
pass "3.2 잘못된 조직 -> Ignored"
222+
else
223+
fail "3.2 잘못된 조직 -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
224+
fi
225+
226+
# 3.3 잘못된 저장소
227+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
228+
-H "Content-Type: application/json" \
229+
-H "X-GitHub-Event: pull_request" \
230+
-d '{"organization":{"login":"DaleStudy"},"repository":{"name":"other-repo"}}' \
231+
--max-time 30 2>/dev/null)
232+
STATUS=$(echo "$BODY" | tail -1)
233+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
234+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "other-repo"; then
235+
pass "3.3 잘못된 저장소 -> Ignored"
236+
else
237+
fail "3.3 잘못된 저장소 -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
238+
fi
239+
240+
# 3.4 미처리 액션 (closed)
241+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
242+
-H "Content-Type: application/json" \
243+
-H "X-GitHub-Event: pull_request" \
244+
-d '{"organization":{"login":"DaleStudy"},"repository":{"name":"leetcode-study"},"action":"closed"}' \
245+
--max-time 30 2>/dev/null)
246+
STATUS=$(echo "$BODY" | tail -1)
247+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
248+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "Ignored"; then
249+
pass "3.4 pull_request closed -> Ignored"
250+
else
251+
fail "3.4 pull_request closed -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
252+
fi
253+
254+
# 3.5 멘션 없는 댓글
255+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
256+
-H "Content-Type: application/json" \
257+
-H "X-GitHub-Event: issue_comment" \
258+
-d '{"organization":{"login":"DaleStudy"},"repository":{"name":"leetcode-study","owner":{"login":"DaleStudy"}},"action":"created","comment":{"body":"just a regular comment","id":1},"issue":{"number":1,"pull_request":{"url":"https://api.github.com/repos/DaleStudy/leetcode-study/pulls/1"}}}' \
259+
--max-time 30 2>/dev/null)
260+
STATUS=$(echo "$BODY" | tail -1)
261+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
262+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "not mentioned"; then
263+
pass "3.5 멘션 없는 댓글 -> Ignored"
264+
else
265+
fail "3.5 멘션 없는 댓글 -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
266+
fi
267+
268+
# 3.6 PR 아닌 이슈 댓글
269+
BODY=$(curl -s -w '\n%{http_code}' -X POST "$BASE_URL/webhooks" \
270+
-H "Content-Type: application/json" \
271+
-H "X-GitHub-Event: issue_comment" \
272+
-d '{"organization":{"login":"DaleStudy"},"repository":{"name":"leetcode-study","owner":{"login":"DaleStudy"}},"action":"created","comment":{"body":"@dalestudy review","id":1},"issue":{"number":1}}' \
273+
--max-time 30 2>/dev/null)
274+
STATUS=$(echo "$BODY" | tail -1)
275+
BODY_CONTENT=$(echo "$BODY" | sed '$d')
276+
if [ "$STATUS" = "200" ] && echo "$BODY_CONTENT" | grep -q "not a PR comment"; then
277+
pass "3.6 이슈 댓글 (PR 아님) -> Ignored"
278+
else
279+
fail "3.6 이슈 댓글 (PR 아님) -> Ignored" "status=$STATUS, body=$BODY_CONTENT"
280+
fi
281+
fi
282+
283+
# ============================================================
284+
# Tier 4: 수동 검증 안내
285+
# ============================================================
286+
section "Tier 4: 수동 검증 (아래 명령을 직접 실행하세요)"
287+
288+
echo ""
289+
echo -e " ${YELLOW}[approve-prs]${NC} PR 일괄 승인 (Week 번호를 실제 값으로 변경)"
290+
echo " curl -X POST $BASE_URL/approve-prs \\"
291+
echo " -H 'Content-Type: application/json' \\"
292+
echo " -d '{\"repo_name\":\"leetcode-study\",\"week\":\"Week 14\"}'"
293+
294+
echo ""
295+
echo -e " ${YELLOW}[merge-prs]${NC} PR 일괄 병합"
296+
echo " curl -X POST $BASE_URL/merge-prs \\"
297+
echo " -H 'Content-Type: application/json' \\"
298+
echo " -d '{\"repo_name\":\"leetcode-study\",\"week\":\"Week 14\",\"merge_method\":\"squash\"}'"
299+
300+
echo ""
301+
echo -e " ${YELLOW}[Webhook]${NC} 실제 이벤트로 검증"
302+
echo " 1. wrangler tail (로그 모니터링 시작)"
303+
echo " 2. leetcode-study에서 PR 생성/재오픈 -> Week 경고 댓글 확인"
304+
echo " 3. PR 댓글에 '@dalestudy review' 작성 -> AI 리뷰 확인"
305+
echo " 4. PR 댓글에 '@dalestudy approve' 작성 -> 자동 승인 확인"
306+
echo " 5. 코드 라인 댓글에 '@dalestudy' 작성 -> 스레드 답변 확인"
307+
308+
# ============================================================
309+
# Summary
310+
# ============================================================
311+
echo ""
312+
echo "=========================================="
313+
TOTAL=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))
314+
echo -e " 결과: ${GREEN}${PASS_COUNT} passed${NC}, ${RED}${FAIL_COUNT} failed${NC}, ${YELLOW}${SKIP_COUNT} skipped${NC} / $TOTAL total"
315+
316+
if [ "$FAIL_COUNT" -gt 0 ]; then
317+
echo -e " ${RED}EXIT: 1 (실패한 테스트 있음)${NC}"
318+
echo "=========================================="
319+
exit 1
320+
else
321+
echo -e " ${GREEN}EXIT: 0 (모든 테스트 통과)${NC}"
322+
echo "=========================================="
323+
exit 0
324+
fi

wrangler.jsonc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
2-
"name": "dalestudy",
2+
"name": "github",
33
"main": "index.js",
44
"compatibility_date": "2024-01-01",
5+
"account_id": "94480614a6df7731f1e4491bdac5c440",
56
"observability": {
67
"logs": {
78
"enabled": true,
89
"head_sampling_rate": 1,
910
"invocation_logs": true,
10-
"persist": true
11-
}
12-
}
11+
"persist": true,
12+
},
13+
},
1314
}

0 commit comments

Comments
 (0)