From 45ec51e7f5693feeb34298854d366af8018f4a5e Mon Sep 17 00:00:00 2001 From: Dale Seo <5466341+DaleSeo@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:20:20 -0400 Subject: [PATCH 1/4] ci: deploy with github actions --- .github/workflows/deploy.yml | 41 ++++++++++++++++++++++++++++++++++++ AGENTS.md | 35 ++++++++---------------------- README.md | 23 ++++---------------- wrangler.jsonc | 2 ++ 4 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3b28cff --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,41 @@ +name: Deploy ๐ + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: deploy-production + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + - run: bun run test + + # PRIVATE_KEY ๋ ์ฌ๋ฌ ์ค์ง๋ฆฌ PEM ์ด๋ผ JSON ์ผ๋ก ์ด๋ค + - name: Write secrets file + env: + PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + AI_GATEWAY_TOKEN: ${{ secrets.AI_GATEWAY_TOKEN }} + INTERNAL_SECRET: ${{ secrets.INTERNAL_SECRET }} + run: | + jq -n --arg private_key "$PRIVATE_KEY" --arg ai "$AI_GATEWAY_TOKEN" --arg internal "$INTERNAL_SECRET" \ + '{PRIVATE_KEY: $private_key, AI_GATEWAY_TOKEN: $ai, INTERNAL_SECRET: $internal}' > "$RUNNER_TEMP/secrets.json" + + - name: Deploy + uses: cloudflare/wrangler-action@v4 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + packageManager: bun + command: deploy --secrets-file ${{ runner.temp }}/secrets.json diff --git a/AGENTS.md b/AGENTS.md index 0b6ad98..effa62c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -95,13 +95,9 @@ curl -X POST http://localhost:8787/check-weeks \ ### ๋ฐฐํฌ ```bash -# Worker ๋ฐฐํฌ +# Worker ์๋ ๋ฐฐํฌ (๋ณดํต์ main ๋จธ์ง ์ GitHub Actions ๊ฐ ํ๋ค) wrangler deploy -# Secrets ์ค์ -wrangler secret put APP_ID # GitHub App ID (์ซ์) -wrangler secret put PRIVATE_KEY # GitHub App Private Key (PEM ์ ์ฒด) - # Secrets ํ์ธ wrangler secret list @@ -316,12 +312,9 @@ GitHub webhook **์ ๋ ์ปค๋ฐ ๊ธ์ง**: `.env`, `.dev.vars`, `*.pem`, `*.key` -#### Cloudflare Workers Secrets (ํ๋ก๋์ ) +#### Secrets (ํ๋ก๋์ ) -```bash -wrangler secret put APP_ID # GitHub App ID -wrangler secret put PRIVATE_KEY # GitHub App Private Key (PEM) -``` +`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions(`.github/workflows/deploy.yml`)๊ฐ ํ ์คํธ ํ `wrangler deploy --secrets-file` ๋ก ๋ฐฐํฌํ๋ค. Worker secret(`PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์ GitHub secret(`APP_PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์์ ๋ฐฐํฌ์ ํจ๊ป ๋ฃ์ผ๋ฏ๋ก `wrangler secret put` ์ผ๋ก ๋ฐ๋ก ๋ฐ๊พธ์ง ์๋๋ค โ ๋ค์ ๋ฐฐํฌ ๋ GitHub ๊ฐ์ผ๋ก ๋ฎ์ธ๋ค. `APP_ID` ๋ ๊ณต๊ฐ ๊ฐ์ด๋ผ `wrangler.jsonc` ์ `vars` ์ ์๋ค. #### ๋ก์ปฌ ๊ฐ๋ฐ (.dev.vars) @@ -334,16 +327,13 @@ PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----" ### Cloudflare Workers -```bash -wrangler deploy -``` +`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions ๊ฐ ๋ฐฐํฌํ๋ค. ์ํฌ๋ฆฟ์ ์ "Secrets (ํ๋ก๋์ )" ์ฐธ๊ณ . ### ์ปค์คํ ๋๋ฉ์ธ - Production: https://github.dalestudy.com - Worker.dev: https://github.dalestudy.workers.dev -์์ธํ ๋ฐฐํฌ ๊ฐ์ด๋๋ `DEPLOYMENT.md` ์ฐธ๊ณ . ## GitHub App Webhook ์ค์ @@ -377,19 +367,12 @@ https://github.com/settings/apps/dalestudy ### 4. Worker Secrets ์ค์ -```bash -# AI Gateway ์ธ์ฆ ํ ํฐ (AI ์ฝ๋ ๋ฆฌ๋ทฐ์ฉ, ํ์) -wrangler secret put AI_GATEWAY_TOKEN - -# Webhook Secret (์ ํ์ฌํญ) -wrangler secret put WEBHOOK_SECRET +GitHub secret ์ ๋ฃ์ผ๋ฉด ๋ฐฐํฌํ ๋ Worker ๋ก ๋ค์ด๊ฐ๋ค (`.github/workflows/deploy.yml`). -# Internal Dispatch Secret (AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ์ฉ, ๊ถ์ฅ) -# ์ค์ ํ๋ฉด tagPatterns, learningStatus๊ฐ ๋ณ๋ Worker ํธ์ถ๋ก -# ๋์คํจ์น๋์ด ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ subrequest ์์ฐ์ ๊ฐ์ง. -# WORKER_URL๊ณผ ํจ๊ป ์ค์ ๋์ด์ผ ํ์ฑํ๋๋ค. -wrangler secret put INTERNAL_SECRET -``` +- `APP_PRIVATE_KEY` (org secret): GitHub App Private Key โ Worker `PRIVATE_KEY` +- `AI_GATEWAY_TOKEN` (org secret): AI Gateway ์ธ์ฆ ํ ํฐ (AI ์ฝ๋ ๋ฆฌ๋ทฐ์ฉ, ํ์) +- `INTERNAL_SECRET` (์ ์ฅ์ secret): AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ์ฉ. ์ค์ ํ๋ฉด tagPatterns, learningStatus๊ฐ ๋ณ๋ Worker ํธ์ถ๋ก ๋์คํจ์น๋์ด ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ subrequest ์์ฐ์ ๊ฐ์ง๋ค. `WORKER_URL`๊ณผ ํจ๊ป ์์ด์ผ ํ์ฑํ๋๋ค. +- `WEBHOOK_SECRET` (์ ํ): ์ค์ ํ๋ฉด webhook ์๋ช ์ ๊ฒ์ฆํ๋ค. ์ฐ๋ ค๋ฉด GitHub secret ์ ๋ง๋ค๊ณ `deploy.yml` ์ secrets ํ์ผ์ ์ถ๊ฐํ๋ค. `WORKER_URL`์ `wrangler.jsonc`์ `vars`์ ์ ์๋์ด ์์ด ๊ธฐ๋ณธ ๋ฐฐํฌ์๋ ์ถ๊ฐ ์ค์ ์ด ํ์ ์๋ค. ์คํ ์ด์ง/๋ค๋ฅธ ๊ณ์ ๋ฑ์ผ๋ก ๋ฐฐํฌํ ๋๋ง ๋ฎ์ด์ฐ๋ฉด ๋๋ค. diff --git a/README.md b/README.md index 6f446fb..4830536 100644 --- a/README.md +++ b/README.md @@ -38,26 +38,11 @@ npm install -g wrangler wrangler login ``` -### 3. Secrets ์ค์ +### 3. ๋ฐฐํฌ -GitHub App์ credentials๋ฅผ Worker secrets์ ์ ์ฅ: +`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions(`.github/workflows/deploy.yml`)๊ฐ ํ ์คํธ ํ `wrangler deploy --secrets-file` ๋ก ๋ฐฐํฌํ๋ค. Worker secret(`PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์ GitHub secret(`APP_PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์์ ๋ฐฐํฌ์ ํจ๊ป ๋ฃ์ผ๋ฏ๋ก `wrangler secret put` ์ผ๋ก ๋ฐ๋ก ๋ฐ๊พธ์ง ์๋๋ค โ ๋ค์ ๋ฐฐํฌ ๋ GitHub ๊ฐ์ผ๋ก ๋ฎ์ธ๋ค. `APP_ID` ๋ ๊ณต๊ฐ ๊ฐ์ด๋ผ `wrangler.jsonc` ์ `vars` ์ ์๋ค. -```bash -# APP_ID ์ค์ -wrangler secret put APP_ID -# ํ๋กฌํํธ์์ GitHub App ID ์ ๋ ฅ - -# PRIVATE_KEY ์ค์ -wrangler secret put PRIVATE_KEY -# ํ๋กฌํํธ์์ GitHub App Private Key ์ ์ฒด ๋ด์ฉ ์ ๋ ฅ -# (-----BEGIN RSA PRIVATE KEY----- ๋ถํฐ -----END RSA PRIVATE KEY----- ๊น์ง) -``` - -### 4. Worker ๋ฐฐํฌ - -```bash -wrangler deploy -``` +์๋์ผ๋ก ๋ฐฐํฌํด์ผ ํ ๋๋ง `wrangler deploy` ๋ฅผ ์ด๋ค. ๋ฐฐํฌ ์๋ฃ ํ Custom Domain์ ํตํด ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค: @@ -256,7 +241,7 @@ GitHub App credentials ํ์ธ: wrangler secret list ``` -APP_ID์ PRIVATE_KEY๊ฐ ๋ชจ๋ ์ค์ ๋์ด ์์ด์ผ ํฉ๋๋ค. +`APP_ID`(`wrangler.jsonc`)์ `PRIVATE_KEY`(GitHub secret `APP_PRIVATE_KEY`)๊ฐ ๋ชจ๋ ์์ด์ผ ํฉ๋๋ค. ### Worker๊ฐ 403 ์๋ฌ ๋ฐํ diff --git a/wrangler.jsonc b/wrangler.jsonc index 9864b1b..be8c081 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -5,6 +5,8 @@ "compatibility_flags": ["global_fetch_strictly_public"], "account_id": "94480614a6df7731f1e4491bdac5c440", "vars": { + // GitHub App(dalestudy)์ ID. ๊ณต๊ฐ ๊ฐ์ด๋ค. + "APP_ID": "1067270", "WORKER_URL": "https://github.dalestudy.workers.dev", }, "observability": { From 38bd43d0bc5e9868d1ae937574a24d9240b3062a Mon Sep 17 00:00:00 2001 From: Dale Seo <5466341+DaleSeo@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:25:45 -0400 Subject: [PATCH 2/4] docs: simplify AGENTS.md and align README with other repos --- .all-contributorsrc | 77 +++++++ AGENTS.md | 541 ++++++-------------------------------------- README.md | 277 +++-------------------- 3 files changed, 179 insertions(+), 716 deletions(-) create mode 100644 .all-contributorsrc diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000..ccfc040 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,77 @@ +{ + "projectName": "github", + "projectOwner": "DaleStudy", + "commit": true, + "repoType": "github", + "commitConvention": "angular", + "contributors": [ + { + "login": "DaleSeo", + "name": "Dale Seo", + "avatar_url": "https://avatars.githubusercontent.com/u/5466341?v=4", + "profile": "https://daleseo.com", + "contributions": [ + "code" + ] + }, + { + "login": "soobing", + "name": "Soobin Bak", + "avatar_url": "https://avatars.githubusercontent.com/u/16860535?v=4", + "profile": "https://soobing.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "sounmind", + "name": "Evan Suhyeong Lee", + "avatar_url": "https://avatars.githubusercontent.com/u/37020415?v=4", + "profile": "https://blog.evanshlee.com", + "contributions": [ + "code" + ] + }, + { + "login": "devchanki", + "name": "Gichan Kim", + "avatar_url": "https://avatars.githubusercontent.com/u/31515116?v=4", + "profile": "https://github.com/devchanki", + "contributions": [ + "code" + ] + }, + { + "login": "lkhoony", + "name": "KwangHun Lee", + "avatar_url": "https://avatars.githubusercontent.com/u/66773320?v=4", + "profile": "https://lkhoony.notion.site/Front-End-1a3f0e5e085480668ba0ebc54986f6bc", + "contributions": [ + "code" + ] + }, + { + "login": "parkhojeong", + "name": "Hojeong Park", + "avatar_url": "https://avatars.githubusercontent.com/u/34734102?v=4", + "profile": "https://github.com/parkhojeong", + "contributions": [ + "code" + ] + }, + { + "login": "jylee2033", + "name": "Jooyoung Julia Lee", + "avatar_url": "https://avatars.githubusercontent.com/u/85793553?v=4", + "profile": "https://github.com/jylee2033", + "contributions": [ + "code" + ] + } + ], + "contributorsPerLine": 7, + "linkToUsage": false, + "files": [ + "README.md" + ] +} diff --git a/AGENTS.md b/AGENTS.md index effa62c..703dc2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,519 +1,114 @@ -# AI Agents Guide +# AGENTS.md -์ด ๋ฌธ์๋ AI ์์ด์ ํธ๊ฐ ํ๋ก์ ํธ๋ฅผ ์ดํดํ๊ณ ์์ ํ ์ ์๋๋ก ๋๋ ๊ฐ์ด๋์ ๋๋ค. +DaleStudy ์กฐ์ง์ [GitHub App](https://github.com/apps/dalestudy)์ด ๋ฐ๋ ์นํ ๊ณผ ์๋ํ ์์ฒญ์ ์ฒ๋ฆฌํ๋ Cloudflare Worker. ์ฃผ ๋์์ [leetcode-study](https://github.com/DaleStudy/leetcode-study) ์ ์ฅ์๋ค. -## ํ๋ก์ ํธ ๊ฐ์ +- ์ด์: https://github.dalestudy.com (workers.dev: https://github.dalestudy.workers.dev) +- ์คํ: Cloudflare Workers, JavaScript(ES Modules), Vitest, Bun -DaleStudy ์กฐ์ง์ GitHub App(https://github.com/apps/dalestudy) - -### ํ์ฌ ๊ตฌํ๋ ๊ธฐ๋ฅ - -#### 1. PR Week ์ค์ ์๋ ๊ฒ์ฌ (๋ฆฌํธ์ฝ๋ ์คํฐ๋) - -Fork PR์์๋ ์๋ํ๋๋ก GitHub Projects v2์ Week ํ๋๋ฅผ ์กฐํํ๊ณ , Week ์ค์ ์ด ๋๋ฝ๋ PR์ ์๋์ผ๋ก ๊ฒฝ๊ณ ๋๊ธ์ ์์ฑํ๋ฉฐ, Week ์ค์ ์ด ์๋ฃ๋๋ฉด ๊ฒฝ๊ณ ๋๊ธ์ ์๋์ผ๋ก ์ญ์ ํ๋ค. - -- **๋์ Repository**: https://github.com/DaleStudy/leetcode-study -- **ํธ๋ฆฌ๊ฑฐ ๋ฐฉ์**: - - **์ค์๊ฐ**: GitHub Organization Webhook (`projects_v2_item`, `pull_request` ์ด๋ฒคํธ) - - **์๋**: `POST /check-weeks` ์๋ํฌ์ธํธ ์ง์ ํธ์ถ - -### ๊ธฐ์ ์คํ - -- **Runtime**: Cloudflare Workers -- **Language**: JavaScript (ES Modules) -- **Authentication**: GitHub App (JWT + Installation Token) -- **APIs**: GitHub REST API, GitHub GraphQL API - -## ํ๋ก์ ํธ ๊ตฌ์กฐ - -``` -~/work/github/ -โโโ index.js # Worker ๋ฉ์ธ ์ฝ๋ (์๋ํฌ์ธํธ ๋ผ์ฐํ ) -โโโ wrangler.jsonc # Cloudflare Workers ์ค์ ๊ณผ vars -โโโ package.json # Vitest ํ ์คํธ ์คํฌ๋ฆฝํธ ๋ฐ devDependencies -โโโ bun.lock # Bun ์์กด์ฑ lockfile -โโโ vitest.config.js # Cloudflare Workers Vitest integration ์ค์ -โโโ .github/workflows/ # CI ์ํฌํ๋ก์ฐ (bun install โ bun run test) -โโโ handlers/ # ๊ธฐ๋ฅ๋ณ ํธ๋ค๋ฌ์ ํธ๋ค๋ฌ ๋จ์ ํ ์คํธ -โโโ utils/ # ๊ณตํต ์ ํธ๋ฆฌํฐ์ ์ ํธ๋ฆฌํฐ ๋จ์ ํ ์คํธ -โโโ tests/ # Worker runtime smoke test์ ๊ต์ฐจ ๋ชจ๋ ํ ์คํธ -โโโ README.md # ํ๋ก์ ํธ ์ค๋ช -โโโ AGENTS.md # ์ด ํ์ผ (AI ์์ด์ ํธ ๊ฐ์ด๋) -โโโ CLAUDE.md # Claude Code ์ฐธ์กฐ ํ์ผ (AGENTS.md๋ก ๋ฆฌ๋ค์ด๋ ํธ) -โโโ *.pem # GitHub App Private Keys (์ปค๋ฐ ์ ์ธ) -``` - -### ์ฝ๋ ๊ตฌ์กฐ ์ค๋ช - -- **index.js**: ์๋ํฌ์ธํธ ๋ผ์ฐํ ๋ง ๋ด๋น. pathname๋ณ ํธ๋ค๋ฌ ํธ์ถ -- **handlers/**: ๊ธฐ๋ฅ๋ณ ํธ๋ค๋ฌ์ `*.test.js` ํธ๋ค๋ฌ ๋จ์ ํ ์คํธ - - `check-weeks.js`: PR Week ์ค์ ๊ฒ์ฌ, ๋๊ธ ์์ฑ/์ญ์ - - `webhooks.js`: GitHub webhook ์ด๋ฒคํธ ์ฒ๋ฆฌ - - `internal-dispatch.js`: self-fetch ๋ด๋ถ AI ํธ๋ค๋ฌ ๋์คํจ์น - - `approve_prs.js`, `merge_prs.js`: PR ์ผ๊ด ์น์ธ/๋ณํฉ -- **utils/**: ์ฌ๋ฌ ํธ๋ค๋ฌ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ๋ ์ ํธ๋ฆฌํฐ์ `*.test.js` ์ ํธ๋ฆฌํฐ ๋จ์ ํ ์คํธ - - `cors.js`: CORS ํค๋ ๊ด๋ฆฌ ๋ฐ ์๋ต ์์ฑ (`corsResponse`, `errorResponse`) - - `github.js`: GitHub App ์ธ์ฆ (JWT, Installation Token), RSA ์๋ช - - `webhook.js`: Webhook signature ๊ฒ์ฆ -- **tests/**: Worker runtime smoke test, subrequest budget, cross-module ํ ์คํธ -- **vitest.config.js**: `@cloudflare/vitest-pool-workers`๊ฐ `wrangler.jsonc`๋ฅผ ์ฝ๋๋ก ์ค์ - -### ์ ๊ธฐ๋ฅ ์ถ๊ฐ ์ - -1. ๊ธฐ๋ฅ๋ณ ํธ๋ค๋ฌ ํ์ผ ์์ฑ (์: `handlers/new-feature.js`) -2. `index.js`์ pathname ๋ผ์ฐํ ์ถ๊ฐ - -```javascript -// handlers/new-feature.js ์์ฑ -export async function newFeature(request, env) { - // ๋น์ฆ๋์ค ๋ก์ง - return corsResponse({ success: true }); -} - -// index.js์ ๋ผ์ฐํ ์ถ๊ฐ -import { newFeature } from "./handlers/new-feature.js"; - -if (url.pathname === "/new-feature") { - return newFeature(request, env); -} -``` - -## ์ฃผ์ ๋ช ๋ น์ด - -### ๋ก์ปฌ ๊ฐ๋ฐ - -```bash -# ๋ก์ปฌ ๊ฐ๋ฐ ์๋ฒ ์คํ (ํฌํธ 8787) -wrangler dev - -# ๋ก์ปฌ ํ ์คํธ (๋ณ๋ ํฐ๋ฏธ๋) -curl -X POST http://localhost:8787/check-weeks \ - -H "Content-Type: application/json" \ - -d '{"repo_owner": "DaleStudy", "repo_name": "leetcode-study"}' -``` - -### ๋ฐฐํฌ - -```bash -# Worker ์๋ ๋ฐฐํฌ (๋ณดํต์ main ๋จธ์ง ์ GitHub Actions ๊ฐ ํ๋ค) -wrangler deploy - -# Secrets ํ์ธ -wrangler secret list - -# ์ค์๊ฐ ๋ก๊ทธ ํ์ธ -wrangler tail -``` - -### ํ๋ก๋์ ํ ์คํธ +## ๋ช ๋ น์ด ```bash -# ๋ฐฐํฌ๋ Worker ํ ์คํธ -curl -X POST https://github.dalestudy.com/check-weeks \ - -H "Content-Type: application/json" \ - -d '{"repo_owner": "DaleStudy", "repo_name": "leetcode-study"}' -``` - -## ํต์ฌ ๊ธฐ๋ฅ - -### 1. GitHub App ์ธ์ฆ - -์ธ์ฆ ํ๋ฆ: - -1. RS256 ์๊ณ ๋ฆฌ์ฆ์ผ๋ก JWT ์์ฑ (Web Crypto API ์ฌ์ฉ) -2. JWT๋ก Installation ID ์กฐํ -3. Installation Token ๋ฐ๊ธ (10๋ถ ์ ํจ) -4. ๋ชจ๋ API ์์ฒญ์ Installation Token ์ฌ์ฉ - -์ธ์ฆ ๊ด๋ จ ํจ์: - -- `generateGitHubAppToken()`: GitHub App Installation Token ๋ฐ๊ธ (์ ์ฒด ํ๋ฆ ๊ด๋ฆฌ) -- `createJWT()`: RS256 JWT ์์ฑ (GitHub App ์ธ์ฆ์ฉ, 10๋ถ ์ ํจ) -- `importPrivateKey()`: PEM ํ์ Private Key๋ฅผ Web Crypto API์ฉ์ผ๋ก ๋ณํ (PKCS8/PKCS1 ๋ชจ๋ ์ง์) -- `sign()`: RS256 ์๋ช ์์ฑ -- `base64UrlEncode()`: Base64 URL-safe ์ธ์ฝ๋ฉ - -### 2. API ์๋ํฌ์ธํธ ๊ตฌ์กฐ - -ํ์ฌ ๊ตฌํ๋ ์๋ํฌ์ธํธ: - -#### `POST /webhooks` - -GitHub Organization webhook ์์ ์ฉ ์๋ํฌ์ธํธ - -- **์ด๋ฒคํธ**: `projects_v2_item`, `pull_request` -- **์ค์๊ฐ ์ฒ๋ฆฌ**: Week ์ค์ ๋ณ๊ฒฝ ์ฆ์ ๊ฐ์ง ๋ฐ ๋๊ธ ์์ฑ/์ญ์ - -#### `POST /check-weeks` - -๋ชจ๋ Open PR์์ Week ์ค์ ์ ๊ฒ์ฌํ๊ณ ์๋์ผ๋ก ๋๊ธ ์์ฑ/์ญ์ (์๋ ํธ์ถ์ฉ) - -**Request:** - -`repo_owner` ์๋ต ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก `DaleStudy`๊ฐ ์ฌ์ฉ๋ฉ๋๋ค. - -```json -{ - "repo_name": "leetcode-study" -} -``` - -**Response:** - -```json -{ - "success": true, - "total_prs": 3, - "checked": 3, - "commented": 1, - "deleted": 1, - "results": [ - { "pr": 1970, "week": null, "commented": true }, - { "pr": 1969, "week": "Week 8", "commented": false, "deleted": true } - ] -} +bun install +bun run test # ์ ์ฒด ํ ์คํธ (CI ์ ๋ฐฐํฌ ์ ์ ๊ฐ์ ๋ช ๋ น์ ๋๋ค) +bun run test -- handlers/webhooks.test.js +wrangler dev # http://localhost:8787 +wrangler tail # ์ด์ ๋ก๊ทธ ``` -#### `POST /approve-prs` - -์ด๋ ค์๋ ๋ต์ ์ ์ถ PR์ ์ผ๊ด ์น์ธํฉ๋๋ค. `excludes` ๋ฐฐ์ด๋ก ํน์ PR์ ์ ์ธํฉ๋๋ค. ์ด๋ฏธ ์น์ธ๋ PR๊ณผ Draft ์ํ์ PR์ ์๋์ผ๋ก ์คํต๋ฉ๋๋ค. CI๊ฐ ํต๊ณผํ์ง ์์ PR(`statusCheckRollup.state !== "SUCCESS"`)๋ ์คํต๋์ด, ์ฒดํฌ๊ฐ ๊นจ์ง PR์๋ ์น์ธ ๋ฆฌ๋ทฐ๋ฅผ ๋จ๊ธฐ์ง ์์ต๋๋ค. - -**Request:** - -```json -{ "repo_name": "leetcode-study", "excludes": [1972] } -``` +## ๊ตฌ์กฐ -**Response:** - -```json -{ - "success": true, - "action": "approve", - "repo": "DaleStudy/leetcode-study", - "total_open_prs": 5, - "processed": 2, - "approved": 2, - "skipped": 0, - "results": [ - { "pr": 1970, "title": "week8 solutions", "approved": true }, - { "pr": 1971, "title": "week8 extras", "approved": true } - ] -} ``` - -#### `POST /merge-prs` - -์ด๋ ค์๋ PR์ ์ผ๊ด ๋ณํฉํฉ๋๋ค. ๊ธฐ๋ณธ ๋ณํฉ ๋ฐฉ์์ `squash`์ด๋ฉฐ `merge_method` ๊ฐ์ผ๋ก `merge | squash | rebase` ์ค ์ ํํ ์ ์์ต๋๋ค. `excludes`๋ก ํน์ PR์ ์ ์ธํ ์ ์์ต๋๋ค. ์น์ธ ๋ฆฌ๋ทฐ๊ฐ ์๋ PR, Draft PR, GitHub `mergeable_state !== "clean"` PR์ ์คํต๋๋ฉฐ `unknown`/`behind` ์ํ๋ ์ต๋ 1์ด ํ ํ ๋ฒ ๋ ํ์ธํฉ๋๋ค. - -**Request:** - -```json -{ - "repo_name": "leetcode-study", - "merge_method": "squash", - "excludes": [1972] -} +index.js ์๋ํฌ์ธํธ ๋ผ์ฐํ ๋ง ํ๋ค (POST ๋ง ๋ฐ๋๋ค) +handlers/ ๊ธฐ๋ฅ๋ณ ํธ๋ค๋ฌ์ ๋จ์ ํ ์คํธ(*.test.js) +utils/ ๊ณตํต ์ ํธ๋ฆฌํฐ์ ๋จ์ ํ ์คํธ (github.js: App ์ธ์ฆ, prActions.js: ์น์ธยท๋ณํฉ ๊ณตํต ๋ก์ง) +tests/ Worker runtime smoke test, subrequest ์์ฐ, ๊ต์ฐจ ๋ชจ๋ ํ ์คํธ +wrangler.jsonc Worker ์ค์ ๊ณผ ๊ณต๊ฐ ๊ฐ(vars) ``` -**Response:** - -```json -{ - "success": true, - "action": "merge", - "repo": "DaleStudy/leetcode-study", - "merge_method": "squash", - "total_open_prs": 5, - "processed": 2, - "merged": 2, - "skipped": 0, - "results": [ - { "pr": 1970, "title": "week8 solutions", "merged": true, "sha": "abc123" }, - { "pr": 1971, "title": "week8 extras", "merged": true, "sha": "def456" } - ] -} -``` +์ ๊ธฐ๋ฅ์ `handlers/` ์ ํธ๋ค๋ฌ๋ฅผ ๋ง๋ค๊ณ `index.js` ์ ๊ฒฝ๋ก๋ฅผ ์ถ๊ฐํ๋ค. ํ์ํ GitHub App ๊ถํ์ด ์๋์ง ํ์ธํ๊ณ , ํธ๋ค๋ฌ ์์ ํ ์คํธ๋ฅผ ๋๋ค. -#### `POST /resolve-iteration` +## ์๋ํฌ์ธํธ -์ฃผ์ด์ง ๋ ์ง๊ฐ ์ด๋ ๊ธฐ์์ ๋ช ์ฃผ์ฐจ์ธ์ง ์กฐํํฉ๋๋ค. ๋ฆฌํธ์ฝ๋ ์คํฐ๋๋ ๊ธฐ์๋ง๋ค ํ๋ก์ ํธ ๋ณด๋๋ฅผ ์๋ก ๋ง๋ค๊ณ ๋ณด๋์ Week ํ๋(Iteration)๊ฐ ์ฃผ์ฐจ ์ผ์ ์ ๊ด๋ฆฌํ๋ฏ๋ก, ์ฃผ์ฐจ ๋ฒํธ๋ฅผ ํ๋์ฝ๋ฉํ์ง ์๊ณ ์ฌ๊ธฐ์ ์ฝ์ต๋๋ค. +`repo_owner` ๋ฅผ ๋ฐ๋ ์๋ํฌ์ธํธ๋ `DaleStudy` ๊ฐ ์๋๋ฉด 403 ์ด๋ค (์๋ตํ๋ฉด `DaleStudy`). `/check-weeks` ๋ ์ ์ฅ์๋ leetcode-study ๋ง ๋ฐ๋๋ค. -Actions์ ๊ธฐ๋ณธ `GITHUB_TOKEN`์ผ๋ก๋ org ํ๋ก์ ํธ ๋ณด๋๋ฅผ ์ฝ์ ์ ์์ด(`read:project` ์ค์ฝํ ๋ถ์ฌ) App ํ ํฐ์ ๊ฐ์ง ์ด ์์ปค๊ฐ ์กฐํ๋ฅผ ๋์ ํฉ๋๋ค. ์๋ฃ๋ ์ฃผ์ฐจ๋ `completedIterations`๋ก ์ฎ๊ฒจ๊ฐ๋ฏ๋ก ๋ ๋ฐฐ์ด์ ๋ชจ๋ ํ์ธํฉ๋๋ค. +| ๊ฒฝ๋ก | ํ๋ ์ผ | +| ------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `POST /webhooks` | GitHub App ์นํ ์์ (์๋ "์นํ ") | +| `POST /check-weeks` | ์ด๋ฆฐ PR ์ ์ฒด์ Week ์ค์ ์ ๊ฒ์ฌํด ๊ฒฝ๊ณ ๋๊ธ์ ๋ฌ๊ฑฐ๋ ์ง์ด๋ค. `{ "repo_name" }` | +| `POST /approve-prs` | ํด๋น ์ฃผ์ฐจ ํ์ด PR ์ ์ผ๊ด ์น์ธํ๋ค. `{ "repo_name", "week": "Week 8", "excludes"? }` | +| `POST /merge-prs` | ํด๋น ์ฃผ์ฐจ์ ์น์ธ๋ PR ์ ์ผ๊ด ๋ณํฉํ๋ค. `{ "repo_name", "week", "excludes"?, "merge_method"? }` (๊ธฐ๋ณธ `squash`) | +| `POST /resolve-iteration` | ๋ ์ง๊ฐ ๋ช ๊ธฐ ๋ช ์ฃผ์ฐจ์ธ์ง ํ๋ก์ ํธ ๋ณด๋์์ ์ฝ๋๋ค. `{ "date": "2026-08-08" }` | +| `POST /internal/*` | ์นํ ํธ๋ค๋ฌ๊ฐ AI ์์ ์ ๋ณ๋ invocation ์ผ๋ก ๋๊ธธ ๋ ์ด๋ค (`X-Internal-Secret` ์ธ์ฆ) | -**Request:** +์น์ธยท๋ณํฉ์ ๊ณตํต ๊ท์น์ `utils/prActions.js` ์ ์๋ค. ์์ฝํ๋ฉด Week ๊ฐ ๋ค๋ฅด๊ฑฐ๋ Status ๊ฐ `Solving` ์ธ PR, Draft PR ์ ๊ฑด๋๋ฐ๊ณ , ์น์ธ์ CI ๊ฐ ํต๊ณผํ PR ์๋ง, ๋ณํฉ์ ์น์ธ ๋ฆฌ๋ทฐ๊ฐ ์๊ณ `mergeable_state` ๊ฐ `clean` ์ธ PR ์๋ง ํ๋ค. -```json -{ "date": "2026-08-08" } -``` +`/resolve-iteration` ์ด ์๋ ์ด์ : Actions ์ ๊ธฐ๋ณธ `GITHUB_TOKEN` ์ org ํ๋ก์ ํธ ๋ณด๋๋ฅผ ์ฝ์ง ๋ชปํด์(`read:project` ์์) App ํ ํฐ์ ๊ฐ์ง ์ด Worker ๊ฐ ๋์ ์ฝ๋๋ค. ์ฃผ์ฐจ๋ ๋ณด๋์ Week(Iteration) ํ๋์์ ์ค๋ฏ๋ก ํ๋์ฝ๋ฉํ์ง ์๋๋ค. -**Response:** - -```json -{ - "success": true, - "date": "2026-08-08", - "found": true, - "cohort": 8, - "week": 7, - "week_label": "Week 7", - "project_number": 29, - "project_title": "๋ฆฌํธ์ฝ๋ ์คํฐ๋ 8๊ธฐ", - "start_date": "2026-08-02", - "end_date": "2026-08-08" -} -``` +## ์นํ -ํด๋น ๋ ์ง๋ฅผ ํฌํจํ๋ ์ฃผ์ฐจ๊ฐ ์์ผ๋ฉด(๊ธฐ์ ์ฌ์ด ํด์๊ธฐ ๋ฑ) `{ "success": true, "date": "...", "found": false }`๋ฅผ ๋๋ ค์ค๋๋ค. +`handlers/webhooks.js` ๊ฐ leetcode-study ์ ์ฅ์์ ์ด๋ฒคํธ๋ง ์ฒ๋ฆฌํ๋ค. -### 3. ์ํฌํ๋ก์ฐ +| ์ด๋ฒคํธ | ํ๋ ์ผ | +| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `projects_v2_item` | Week ์ค์ ์ด ๋ฐ๋๋ฉด ๊ฒฝ๊ณ ๋๊ธ์ ๋ฌ๊ฑฐ๋ ์ง์ด๋ค | +| `pull_request` (`opened`, `reopened`, `synchronize`) | Week ๊ฒ์ฌ, ํ์ด ํจํด ํ๊น (`tagPatterns`), ํ์ต ํํฉ ๋๊ธ(`postLearningStatus`) | +| `issue_comment`, `pull_request_review_comment` (`created`) | `@dalestudy` ๋ฉ์ ์ ์๋ตํ๋ค. `approve`ยท`์น์ธ` ์ด๋ฉด PR ์ ์น์ธํ๊ณ , ๊ทธ ๋ฐ์๋ AI ๋ฆฌ๋ทฐ๋ฅผ ๋จ๋ค | -1. Open PR ๋ชฉ๋ก ์กฐํ (GitHub REST API) -2. ํ์ด ์ ์ถ PR์ด ์๋ PR ์คํต (์ ๋ชฉ์ด `WEEK NN Solutions` ํํ๊ฐ ์๋๋ฉด ๋ด ํ ์คํธยท์ ์ฅ์ ์ ๋ฆฌ PR๋ก ๊ฐ์ฃผ) -3. ๊ฐ PR์ Week ์ค์ ํ์ธ (GitHub GraphQL API - Projects v2 ์ ๊ทผ ํ์) -4. Week ์์ โ ๊ฒฝ๊ณ ๋๊ธ ์์ฑ (์ค๋ณต ๋ฐฉ์ง: Bot์ด ์์ฑํ ๊ฒฝ๊ณ ๋๊ธ์ด ์ด๋ฏธ ์์ผ๋ฉด ์คํต) -5. Week ์์ โ ๊ธฐ์กด ๊ฒฝ๊ณ ๋๊ธ ์ญ์ (Bot์ด ์์ฑํ Week ๊ฒฝ๊ณ ๋๊ธ๋ง) +Week ๊ฒ์ฌ๋ ์ ๋ชฉ์ด `WEEK NN Solutions` ํํ์ธ ํ์ด PR ๋ง ๋์์ผ๋ก ํ๊ณ , ๊ฒฝ๊ณ ๋๊ธ์ ๋ด์ด ๋จ ๊ฒ๋ง ์ง์ด๋ค. -### 4. AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ ์ํคํ ์ฒ +### AI ์์ ์ ๋ณ๋ invocation ์ผ๋ก ๋๋๊ธฐ -PR ์ด๋ฒคํธ๋ฅผ ๋ฐ์ผ๋ฉด webhook ํธ๋ค๋ฌ๊ฐ ๋ AI ํธ๋ค๋ฌ(`tagPatterns`, `postLearningStatus`)๋ฅผ **๋ณ๋ Worker invocation**์ผ๋ก ๋ถ๋ฆฌ ๋์คํจ์นํ๋ค. ๊ฐ invocation์ ๋ ๋ฆฝ์ ์ธ Cloudflare subrequest ์์ฐ(50)์ ๊ฐ์ง๋ฏ๋ก ํ์ผ์ด ๋ง์ PR์์๋ ์์ฐ ์ด๊ณผ๋ฅผ ๋ฐฉ์งํ๋ค. +Worker ํ ๋ฒ์ ์คํ(invocation)์ subrequest ๋ฅผ 50 ๊ฐ๊น์ง ์ธ ์ ์๋ค. ํ์ผ์ด ๋ง์ PR ์์ ๋์น์ง ์๋๋ก, ์นํ ํธ๋ค๋ฌ๋ ๋ AI ์์ ์ ์๊ธฐ ์์ ์๊ฒ ๋ค์ ์์ฒญํด ๋ฐ๋ก ์คํํ๋ค. ``` GitHub webhook - โ โผ -[Invocation #1] webhook ํธ๋ค๋ฌ โ 50 subrequest ์์ฐ - โ ctx.waitUntil(fetch("/internal/tag-patterns")) โโ self-fetch๋ - โ ctx.waitUntil(fetch("/internal/learning-status")) โโค ์ธ๋ถ ์์ฒญ์ด๋ผ - โ โ ์ invocation ํธ๋ฆฌ๊ฑฐ - โโโโโโโโโโโโโโโโถ [Invocation #2] tagPatterns โโโ โ ๋ ๋ฆฝ 50 ์์ฐ - โ - โโโโโโโโโโโโโโโโถ [Invocation #3] postLearningStatus โ ๋ ๋ฆฝ 50 ์์ฐ -``` - -- `INTERNAL_SECRET`๊ณผ `WORKER_URL`์ด ๋ชจ๋ ์ค์ ๋์ด์ผ ํ์ฑํ๋๋ค. ๋ ์ค ํ๋๋ผ๋ ์์ผ๋ฉด ๊ธฐ์กด์ฒ๋ผ ๊ฐ์ invocation์์ ์์ฐจ ์คํ(subrequest ์์ฐ ๊ณต์ )๋์ด ํ์ผ์ด ๋ง์ PR์์ ์์ฐ์ ์ด๊ณผํ ์ ์๋ค. -- ๋ด๋ถ ์๋ํฌ์ธํธ๋ `/internal/tag-patterns`, `/internal/learning-status`์ด๋ฉฐ `X-Internal-Secret` ํค๋๋ก ์ธ์ฆํ๋ค. -- ์ฐธ๊ณ : `tests/subrequest-budget.test.js`๊ฐ 5๊ฐ ํ์ผ ๋ณ๊ฒฝ ์๋๋ฆฌ์ค์์ ๊ฐ ํธ๋ค๋ฌ์ fetch ํธ์ถ ์(`tagPatterns` 20ํ, `postLearningStatus` 31ํ)๋ฅผ ํ๊ท ํ ์คํธ๋ก ๋ฐ์๋๋ค. - -## ๋ณด์ ๋ฐ ๊ถํ - -### DaleStudy Organization ์ ์ฉ - -`repo_owner !== 'DaleStudy'` ์์ฒญ์ 403 Forbidden ๋ฐํ. - -### GitHub App ํ์ ๊ถํ - -- `contents: read`: PR ์ ๋ณด ์กฐํ -- `issues: write`: ๋๊ธ ์์ฑ ๋ฐ ์ญ์ -- `pull_requests: read & write`: PR ๋ชฉ๋ก/์ํ ์กฐํ, ๋ฆฌ๋ทฐ ์์ฑ, ๋ณํฉ ์ํ -- `organization_projects: read`: Projects v2์ Week ํ๋ ์ ๊ทผ (GraphQL API) - -### Secrets ๊ด๋ฆฌ - -**์ ๋ ์ปค๋ฐ ๊ธ์ง**: `.env`, `.dev.vars`, `*.pem`, `*.key` - -#### Secrets (ํ๋ก๋์ ) - -`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions(`.github/workflows/deploy.yml`)๊ฐ ํ ์คํธ ํ `wrangler deploy --secrets-file` ๋ก ๋ฐฐํฌํ๋ค. Worker secret(`PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์ GitHub secret(`APP_PRIVATE_KEY`, `AI_GATEWAY_TOKEN`, `INTERNAL_SECRET`)์์ ๋ฐฐํฌ์ ํจ๊ป ๋ฃ์ผ๋ฏ๋ก `wrangler secret put` ์ผ๋ก ๋ฐ๋ก ๋ฐ๊พธ์ง ์๋๋ค โ ๋ค์ ๋ฐฐํฌ ๋ GitHub ๊ฐ์ผ๋ก ๋ฎ์ธ๋ค. `APP_ID` ๋ ๊ณต๊ฐ ๊ฐ์ด๋ผ `wrangler.jsonc` ์ `vars` ์ ์๋ค. - -#### ๋ก์ปฌ ๊ฐ๋ฐ (.dev.vars) - +[#1] webhook ํธ๋ค๋ฌ โ 50 ์์ฐ + โโ ctx.waitUntil(fetch("/internal/tag-patterns")) โ [#2] tagPatterns โ ๋ ๋ฆฝ 50 ์์ฐ + โโ ctx.waitUntil(fetch("/internal/learning-status")) โ [#3] postLearningStatus โ ๋ ๋ฆฝ 50 ์์ฐ ``` -APP_ID=123456 -PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----" -``` - -## ๋ฐฐํฌ - -### Cloudflare Workers - -`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions ๊ฐ ๋ฐฐํฌํ๋ค. ์ํฌ๋ฆฟ์ ์ "Secrets (ํ๋ก๋์ )" ์ฐธ๊ณ . - -### ์ปค์คํ ๋๋ฉ์ธ - -- Production: https://github.dalestudy.com -- Worker.dev: https://github.dalestudy.workers.dev - - -## GitHub App Webhook ์ค์ - -### 1. GitHub App ์ค์ ํ์ด์ง ์ ๊ทผ - -``` -https://github.com/settings/apps/dalestudy -``` - -### 2. General ํญ - Webhook ์ค์ - -- **Webhook URL**: `https://github.dalestudy.com/webhooks` -- **Webhook Secret**: (์ ํ์ฌํญ) ์์ ํ ๋๋ค ๋ฌธ์์ด -- **โ Active**: ์ฒดํฌ - -### 3. Permissions & events ํญ - ๊ถํ ์ค์ - -**Repository permissions:** - -- **Contents**: Read -- **Issues**: Read & write (issue_comment ์ด๋ฒคํธ์ฉ) -- **Metadata**: Read -- **Pull requests**: Read & write -- **Projects**: Read & write (Projects V2) - -**Subscribe to events:** - -- โ๏ธ **Issue comments** (`issue_comment` - AI ์ฝ๋ ๋ฆฌ๋ทฐ) -- โ๏ธ **Projects v2 item** (`projects_v2_item` - Week ์ฒดํฌ) -- โ๏ธ **Pull requests** (`pull_request` - Week ์ฒดํฌ) - -### 4. Worker Secrets ์ค์ - -GitHub secret ์ ๋ฃ์ผ๋ฉด ๋ฐฐํฌํ ๋ Worker ๋ก ๋ค์ด๊ฐ๋ค (`.github/workflows/deploy.yml`). - -- `APP_PRIVATE_KEY` (org secret): GitHub App Private Key โ Worker `PRIVATE_KEY` -- `AI_GATEWAY_TOKEN` (org secret): AI Gateway ์ธ์ฆ ํ ํฐ (AI ์ฝ๋ ๋ฆฌ๋ทฐ์ฉ, ํ์) -- `INTERNAL_SECRET` (์ ์ฅ์ secret): AI ํธ๋ค๋ฌ Worker ๋ถ๋ฆฌ์ฉ. ์ค์ ํ๋ฉด tagPatterns, learningStatus๊ฐ ๋ณ๋ Worker ํธ์ถ๋ก ๋์คํจ์น๋์ด ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ธ subrequest ์์ฐ์ ๊ฐ์ง๋ค. `WORKER_URL`๊ณผ ํจ๊ป ์์ด์ผ ํ์ฑํ๋๋ค. -- `WEBHOOK_SECRET` (์ ํ): ์ค์ ํ๋ฉด webhook ์๋ช ์ ๊ฒ์ฆํ๋ค. ์ฐ๋ ค๋ฉด GitHub secret ์ ๋ง๋ค๊ณ `deploy.yml` ์ secrets ํ์ผ์ ์ถ๊ฐํ๋ค. - -`WORKER_URL`์ `wrangler.jsonc`์ `vars`์ ์ ์๋์ด ์์ด ๊ธฐ๋ณธ ๋ฐฐํฌ์๋ ์ถ๊ฐ ์ค์ ์ด ํ์ ์๋ค. ์คํ ์ด์ง/๋ค๋ฅธ ๊ณ์ ๋ฑ์ผ๋ก ๋ฐฐํฌํ ๋๋ง ๋ฎ์ด์ฐ๋ฉด ๋๋ค. - -### 5. GitHub App ์ค์น -์ ์ฅ์์ App์ด ์ค์น๋์ด ์๋์ง ํ์ธ: +`INTERNAL_SECRET` ๊ณผ `WORKER_URL` ์ด ๋ ๋ค ์์ด์ผ ์ผ์ง๋ค. ํ๋๋ผ๋ ์์ผ๋ฉด ๊ฐ์ invocation ์์ ์ฐจ๋ก๋ก ์คํ๋๋ค. +`tests/subrequest-budget.test.js` ๊ฐ ํ์ผ 5 ๊ฐ ์๋๋ฆฌ์ค์ fetch ํ์(`tagPatterns` 20, `postLearningStatus` 31)๋ฅผ ํ๊ท ํ ์คํธ๋ก ๊ณ ์ ํ๋ค. -``` -https://github.com/apps/dalestudy/installations -``` - -**DaleStudy organization์ ์ค์น**๋์ด ์์ด์ผ ํ๋ฉฐ, **leetcode-study ์ ์ฅ์ ์ ๊ทผ ๊ถํ** ํ์ +## ๋ฐฐํฌ์ ์ํฌ๋ฆฟ -## ์๋ ํธ์ถ (์ ํ์ฌํญ) +`main` ์ ๋จธ์ง๋๋ฉด GitHub Actions(`.github/workflows/deploy.yml`)๊ฐ ํ ์คํธ ํ `wrangler deploy --secrets-file` ๋ก ๋ฐฐํฌํ๋ค. PR ์์๋ `integration.yaml` ์ด ํ ์คํธ๋ง ๋๋ค. ์นํ ์ ๋ฐ๋ ์๋ฒ๋ผ PR ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ ๋์ง ์๋๋ค. -์ ์ฒด PR์ ํ ๋ฒ์ ๊ฒ์ฌํ๊ณ ์ถ์ ๋: +| Worker ์์ ์ฝ๋ ์ด๋ฆ | ์ด๋์ ์ค๋ | +| ---------------------- | ----------------------------------------------------------- | +| `APP_ID`, `WORKER_URL` | `wrangler.jsonc` ์ `vars` (๊ณต๊ฐ ๊ฐ) | +| `PRIVATE_KEY` | GitHub org secret `APP_PRIVATE_KEY` | +| `AI_GATEWAY_TOKEN` | GitHub org secret `AI_GATEWAY_TOKEN` | +| `INTERNAL_SECRET` | GitHub ์ ์ฅ์ secret `INTERNAL_SECRET` | +| `WEBHOOK_SECRET` | (์ ํ) ์์ผ๋ฉด ์นํ ์๋ช ์ ๊ฒ์ฆํ๋ค. ์ง๊ธ์ ์ค์ ๋ผ ์์ง ์๋ค | -```bash -curl -X POST https://github.dalestudy.com/check-weeks \ - -H "Content-Type: application/json" \ - -d '{"repo_owner": "DaleStudy", "repo_name": "leetcode-study"}' -``` +GitHub secret ์ ๋ฐฐํฌํ ๋๋ง๋ค Worker secret ์ผ๋ก ๋ค์ด๊ฐ๋ฏ๋ก `wrangler secret put` ์ผ๋ก ๋ฐ๋ก ๋ฐ๊พธ์ง ์๋๋ค. ๋ค์ ๋ฐฐํฌ ๋ GitHub ๊ฐ์ผ๋ก ๋ฎ์ธ๋ค. +๋ก์ปฌ์์๋ `.dev.vars` ์ ๊ฐ์ ์ด๋ฆ์ผ๋ก ๋ฃ๋๋ค. `.dev.vars`, `*.pem` ์ ์ปค๋ฐํ์ง ์๋๋ค. -## ์ค์ํ ์ ์ฝ์ฌํญ +## GitHub App ์ค์ -### Cloudflare Workers ํ๊ฒฝ +https://github.com/organizations/DaleStudy/settings/apps/dalestudy -- โ Node.js ๋ชจ๋ ์ฌ์ฉ ๋ถ๊ฐ (crypto, buffer ๋ฑ) -- โ Web ํ์ค API๋ง ์ฌ์ฉ (fetch, Web Crypto API) -- โ npm ํจํค์ง ๋๋ถ๋ถ ํธํ ์ ๋จ (@octokit/app ๋ฑ) -- โ ์์ JavaScript + Web APIs๋ก ๊ตฌํ +- Webhook URL: `https://github.dalestudy.com/webhooks` +- Repository permissions: Contents(Read), Issues(Read & write), Metadata(Read), Pull requests(Read & write), Projects(Read & write) +- ์ด๋ฒคํธ: Issue comments, Pull request review comments, Projects v2 item, Pull requests +- DaleStudy ์กฐ์ง์ ์ค์น๋ผ ์๊ณ leetcode-study ์ ์ ๊ทผํ ์ ์์ด์ผ ํ๋ค ## ํ ์คํธ -์ด ํ๋ก์ ํธ๋ Cloudflare Workers Vitest integration์ ์ฌ์ฉํฉ๋๋ค. Vitest๋ก ํ ์คํธ๋ฅผ ์คํํ๊ณ , Worker ๋ฐํ์๊ณผ ๋ฐ์ธ๋ฉ์ด ํ์ํ ํ ์คํธ๋ Cloudflare ํ ์คํธ ๋๊ตฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. - -### ํ ์คํธ ์คํ - -ํ ์คํธ๋ `handlers/`(ํธ๋ค๋ฌ ๋จ์ ํ ์คํธ), `utils/`(์ ํธ๋ฆฌํฐ ๋จ์ ํ ์คํธ), `tests/`(Worker runtime smoke test์ ๊ต์ฐจ ๋ชจ๋ ํ ์คํธ)๋ก ๋๋๋ค. ๋ชจ๋ ๋ชจํน์ Vitest์ `vi.mock()`์ ์ฌ์ฉํ๊ณ , Worker runtime๊ณผ bindings๊ฐ ํ์ํ ํ ์คํธ๋ `cloudflare:test`์ `cloudflare:workers`๋ฅผ ์ฌ์ฉํ๋ค. - -```bash -# ์์กด์ฑ ์ค์น -bun install - -# ์ ์ฒด ํ ์คํธ ์คํ -bun run test - -# ๊ฐ์ ๋ชจ๋ -bun run test:watch - -# ํน์ ํ์ผ๋ง ์คํ -bun run test -- handlers/webhooks.test.js -``` - -Bun ์ค์น: https://bun.sh/docs/installation - -### ํ ์คํธ ํ์ผ ์์ฑ ๊ท์น - -- ํ ์คํธ ํ์ผ์ ๋์ ํ์ผ๊ณผ ๊ฐ์ ๋๋ ํ ๋ฆฌ์ `*.test.js` ์ด๋ฆ์ผ๋ก ๋ฐฐ์นํฉ๋๋ค. - - ์: `handlers/webhooks.js` โ `handlers/webhooks.test.js` -- `vitest`์์ ์ ๊ณตํ๋ API(`describe`, `it`, `expect`, `vi`, `beforeEach`)๋ฅผ ์ฌ์ฉํฉ๋๋ค. -- ์ธ๋ถ ์์กด์ฑ(`utils/github.js` ๋ฑ)์ `vi.mock()`์ผ๋ก ๋์ฒดํ๊ณ , `fetch`๋ `globalThis.fetch = vi.fn()...`๋ก ์คํ ํฉ๋๋ค. +ํ ์คํธ๋ ๋์ ํ์ผ ์์ `*.test.js` ๋ก ๋๋ค. `vitest` ์ `describe`ยท`it`ยท`expect`ยท`vi` ๋ฅผ ์ฐ๊ณ , ์ธ๋ถ ๋ชจ๋์ `vi.mock()`, `fetch` ๋ `globalThis.fetch = vi.fn()` ์ผ๋ก ๋ฐ๊พผ๋ค. Worker runtime ๊ณผ ๋ฐ์ธ๋ฉ์ด ํ์ํ ํ ์คํธ๋ `cloudflare:test`, `cloudflare:workers` ๋ฅผ ์ด๋ค (`@cloudflare/vitest-pool-workers` ๊ฐ `wrangler.jsonc` ๋ฅผ ์ฝ๋๋ค). ```javascript -import { describe, it, expect, vi, beforeEach } from "vitest"; - vi.mock("../utils/github.js", () => ({ generateGitHubAppToken: vi.fn().mockResolvedValue("fake-token"), })); - -import { checkWeeks } from "./check-weeks.js"; - -describe("checkWeeks", () => { - beforeEach(() => { - vi.clearAllMocks(); - globalThis.fetch = vi.fn().mockResolvedValue({ - ok: true, - json: () => Promise.resolve([]), - }); - }); - - it("returns 403 for non-DaleStudy organization", async () => { - const request = new Request("https://example.com/check-weeks", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ repo_owner: "OtherOrg", repo_name: "leetcode-study" }), - }); - - const response = await checkWeeks(request, {}); - expect(response.status).toBe(403); - }); -}); ``` -### CI ์๋ ์คํ - -`.github/workflows/integration.yaml`์ด ๋ชจ๋ Pull Request์ `main` ๋ธ๋์น ํธ์์์ `bun install` ๋ค์ `bun run test`๋ฅผ ์๋ ์คํํฉ๋๋ค. ํ ์คํธ๊ฐ ์คํจํ๋ฉด PR ์ฒดํฌ๊ฐ ์คํจํ๋ฏ๋ก, ๋จธ์ง ์ ์ ๋ฐ๋์ ํต๊ณผ์์ผ์ผ ํฉ๋๋ค. - -## ์ ๊ธฐ๋ฅ ์ถ๊ฐ ๊ฐ์ด๋ - -์๋ก์ด ์๋ํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ ๋ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์: - -1. **์๋ํฌ์ธํธ ์ถ๊ฐ**: `index.js`์ `fetch()` ํจ์์ ์๋ก์ด pathname ๋ผ์ฐํ ์ถ๊ฐ -2. **ํธ๋ค๋ฌ ํจ์ ์์ฑ**: ๋น์ฆ๋์ค ๋ก์ง์ ๋ณ๋ ํจ์๋ก ๋ถ๋ฆฌ (์: `handleCheckAllPrs`) -3. **GitHub App ๊ถํ ํ์ธ**: ํ์ํ ๊ถํ์ด ์๋์ง ํ์ธํ๊ณ ์์ผ๋ฉด ์ถ๊ฐ -4. **ํ ์คํธ ์์ฑ**: ํธ๋ค๋ฌ ์์ `*.test.js`๋ฅผ ์ถ๊ฐํ๊ณ `bun run test`๋ก ํต๊ณผ ํ์ธ (์ "ํ ์คํธ" ์น์ ์ฐธ๊ณ ) -5. **๋ฌธ์ ์ ๋ฐ์ดํธ**: AGENTS.md, README.md์ ์ ๊ธฐ๋ฅ ๋ฌธ์ํ -6. **๋ก์ปฌ ์คํ ํ ์คํธ**: `wrangler dev`๋ก ์ค์ ์๋ํฌ์ธํธ ๋์ ํ์ธ ํ ๋ฐฐํฌ - -## ์ฝ๋ ์์ ์ ์ฃผ์์ฌํญ - -1. **Octokit ์ฌ์ฉ ๊ธ์ง** - - Cloudflare Workers์์ ์๋ํ์ง ์์ - - fetch API ์ง์ ์ฌ์ฉ - -2. **Private Key ์ฒ๋ฆฌ** - - PKCS8 ๋๋ PKCS1 ํ์ ์ง์ - - Web Crypto API๋ก import - -3. **GraphQL ์ฟผ๋ฆฌ ์ฃผ์** - - GraphQL ์ฟผ๋ฆฌ์์ ๋ณ์๋ฅผ ๋ฌธ์์ด ํ ํ๋ฆฟ์ผ๋ก ์ง์ ์ฝ์ (GraphQL ๋ณ์ ๋ฌธ๋ฒ ์ฌ์ฉ ์ ํจ) - - ์ ๋ ฅ๊ฐ ๊ฒ์ฆ์ด ์ค์ (SQL Injection ์คํ์ผ ์ทจ์ฝ์ ๋ฐฉ์ง) - -4. **์๋ฌ ํธ๋ค๋ง** - - Worker๋ ์๋ฌ ๋ฐ์ ์ 500 ๋ฐํ - - ๋ก๊ทธ๋ `wrangler tail`๋ก ํ์ธ - -5. **CORS ํค๋** - - ๋ชจ๋ ์๋ต์ CORS ํค๋ ํฌํจ (`Access-Control-Allow-Origin: *`) - -6. **์ฝ๋ ์ฌ์ฌ์ฉ** - - GitHub ์ธ์ฆ ๋ก์ง (`generateGitHubAppToken`, `createJWT` ๋ฑ)์ ๋ชจ๋ ๊ธฐ๋ฅ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ - - ์ ๊ธฐ๋ฅ ์ถ๊ฐ ์ ๊ธฐ์กด ์ ํธ๋ฆฌํฐ ํจ์ ํ์ฉ - -7. **์ฝ๋ฉํธ ์จ๊น ๋ง์ปค ์ง๋ ฌํ ํฌ๋งท ๋ณ๊ฒฝ** - - ์ฝ๋ฉํธ์ `` ํํ๋ก ์จ๊ฒจ ์ ์ฅํ๋ ๋ฐ์ดํฐ์ ์ง๋ ฌํ ํฌ๋งท(๊ฐ์ฒดโ๋ฐฐ์ด ๋ฑ)์ ๋ฐ๊ฟ ๋๋ **์ ๊ท์ยท๋ฌธ์ ์ฃผ์ยทํ ์คํธ๋ฅผ ๊ฐ์ PR์์ ํจ๊ป ๊ฐฑ์ ** - - ํ์ฑ์ด `Array.isArray` ๊ฐ์ ๋ฐฉ์ด ์ฝ๋๋ก ๋น ๊ฐ์ fallbackํ๋ฉด ํ๊ท๊ฐ ์กฐ์ฉํ ๋ฌปํ ๋๋ฒ๊น ์ด ์ด๋ ค์์ง - -## ๊ด๋ จ ๋ฌธ์ +## ์ฃผ์์ฌํญ -- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) -- [GitHub Apps API](https://docs.github.com/en/apps) -- [GitHub GraphQL API](https://docs.github.com/en/graphql) -- [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) +- **Workers ๋ฐํ์์ด๋ค.** Node.js ๋ชจ๋๊ณผ Octokit ๊ฐ์ npm ํจํค์ง๋ ๋๋ถ๋ถ ๋์ํ์ง ์๋๋ค. `fetch` ์ Web Crypto API ๋ก ์ง์ ๊ตฌํํ๋ค. Private Key ๋ PKCS8, PKCS1 ๋ชจ๋ ๋ฐ๋๋ค (`utils/github.js`). +- **GraphQL ์ฟผ๋ฆฌ์ ๊ฐ์ ๋ฌธ์์ด๋ก ์ง์ ๋ผ์ ๋ฃ๋๋ค.** GraphQL ๋ณ์ ๋ฌธ๋ฒ์ ์ฐ์ง ์์ผ๋ฏ๋ก ์ ๋ ฅ๊ฐ ๊ฒ์ฆ์ ๋น ๋จ๋ฆฌ์ง ์๋๋ค. +- **๋๊ธ ์จ๊น ๋ง์ปค(``)์ ์ง๋ ฌํ ํ์์ ๋ฐ๊ฟ ๋๋** ์ ๊ท์, ๋ฌธ์ ์ฃผ์, ํ ์คํธ๋ฅผ ๊ฐ์ PR ์์ ํจ๊ป ๊ณ ์น๋ค. ํ์ฑ์ด ๋น ๊ฐ์ผ๋ก fallback ํ๋ฉด ํ๊ท๊ฐ ์กฐ์ฉํ ๋ฌปํ๋ค. +- ์๋ต์๋ CORS ํค๋(`Access-Control-Allow-Origin: *`)๊ฐ ๋ถ๊ณ , ์๋ฌ๋ 500 ์ผ๋ก ๋๋ ค์ค๋ค. ์์ธ์ `wrangler tail` ๋ก ๋ณธ๋ค. +- 401 ์ด ๋๋ฉด `APP_ID` ์ `PRIVATE_KEY` ๊ฐ ๊ฐ์ App ์ ๊ฒ์ธ์ง ํ์ธํ๋ค. diff --git a/README.md b/README.md index 4830536..36af14a 100644 --- a/README.md +++ b/README.md @@ -1,254 +1,45 @@ -# DaleStudy GitHub App +# github -DaleStudy ์กฐ์ง์ ์๋ํ ์์ ์ ์ฒ๋ฆฌํ๋ GitHub App (https://github.com/apps/dalestudy) +[](#contributors) -## ํ๋ก์ ํธ ๋น์ +๋ฌ๋ ์คํฐ๋ ์กฐ์ง์ [GitHub App](https://github.com/apps/dalestudy)์ด ๋ฐ๋ ์นํ ๊ณผ ์๋ํ ์์ฒญ์ ์ฒ๋ฆฌํ๋ Cloudflare Worker. ๋ฆฌํธ์ฝ๋ ์คํฐ๋์ Week ์ค์ ๊ฒ์ฌ, ํ์ด PR ์ผ๊ด ์น์ธยท๋ณํฉ, AI ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋งก๊ณ ์์ต๋๋ค. -DaleStudy ์กฐ์ง์ ์ฌ๋ฌ repository์์ ๋ฐ๋ณต์ ์ธ ์์ ๋ค์ ์๋ํํ๋ ํ์ฅ ๊ฐ๋ฅํ ํ๋ซํผ์ ๋๋ค. +๊ฐ๋ฐ์ ํ์ํ ๋ด์ฉ์ [AGENTS.md](AGENTS.md)์ ์์ต๋๋ค. -## ํ์ฌ ๊ตฌํ๋ ๊ธฐ๋ฅ +## ๊ธฐ์ฌ (Contribution) -### 1. PR Week ์ค์ ์๋ ๊ฒ์ฌ (๋ฆฌํธ์ฝ๋ ์คํฐ๋) +๋ณธ ํ๋ก์ ํธ๋ [All Contributors](https://github.com/all-contributors/all-contributors) ๊ด๋ก์ ๋ฐ๋ผ ๊ธฐ์ฌ์๋ถ๋ค์ ๊ณตํ์ ์ธ์ ํ๊ณ ๊ฐ์ฌ๋ฅผ ํํํ๊ณ ์์ต๋๋ค. -Fork PR์์๋ ์๋ํ๋๋ก GitHub Projects v2์ Week ํ๋๋ฅผ ์กฐํํ๊ณ , Week ์ค์ ์ด ๋๋ฝ๋ PR์ ์๋์ผ๋ก ๊ฒฝ๊ณ ๋๊ธ์ ์์ฑํฉ๋๋ค. + + + +
Dale Seo ๐ป |
+ Soobin Bak ๐ป |
+ Evan Suhyeong Lee ๐ป |
+ Gichan Kim ๐ป |
+ KwangHun Lee ๐ป |
+ Hojeong Park ๐ป |
+ Jooyoung Julia Lee ๐ป |
+
+
+
+
+