diff --git a/.dev.vars.example b/.dev.vars.example index e2b4712..e4aca53 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -1,8 +1,9 @@ -DISCORD_BOT_TOKEN= +DISCORD_TOKEN= DISCORD_GUILD_ID= DISCORD_PUBLIC_KEY= -DISCORD_ROLE_ID= +DISCORD_APPLICATION_ID= GITHUB_APP_ID= GITHUB_APP_INSTALLATION_ID= GITHUB_APP_PRIVATE_KEY= +GH_PAT= STUDY_JOIN_CHANNEL_ID= diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1fb2c71..0fd5941 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,21 +41,20 @@ jobs: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} secrets: | DISCORD_PUBLIC_KEY - DISCORD_BOT_TOKEN + DISCORD_TOKEN DISCORD_GUILD_ID - DISCORD_ROLE_ID DISCORD_APPLICATION_ID - APP_ID - APP_INSTALLATION_ID - APP_PRIVATE_KEY + GITHUB_APP_ID + GITHUB_APP_INSTALLATION_ID + GITHUB_APP_PRIVATE_KEY GH_PAT env: DISCORD_PUBLIC_KEY: ${{ secrets.DISCORD_PUBLIC_KEY }} - DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }} - DISCORD_ROLE_ID: ${{ secrets.DISCORD_ROLE_ID }} DISCORD_APPLICATION_ID: ${{ secrets.DISCORD_APPLICATION_ID }} - APP_ID: ${{ secrets.APP_ID }} - APP_INSTALLATION_ID: ${{ secrets.APP_INSTALLATION_ID }} - APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + # GitHub App: 코드/Worker는 GITHUB_APP_* 를 읽지만, 기존 저장소 secret 이름은 APP_* 라 여기서 매핑 + GITHUB_APP_ID: ${{ secrets.APP_ID }} + GITHUB_APP_INSTALLATION_ID: ${{ secrets.APP_INSTALLATION_ID }} + GITHUB_APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} GH_PAT: ${{ secrets.GH_PAT }} diff --git a/README.md b/README.md index e6d69a6..ce2332d 100644 --- a/README.md +++ b/README.md @@ -64,20 +64,24 @@ community-manager/ | Key | 설명 | | ---------------------------- | ------------------------------------------ | -| `DISCORD_PUBLIC_KEY` | Discord App의 Ed25519 공개키 (서명 검증용) | -| `DISCORD_BOT_TOKEN` | Discord Bot Token | +| `DISCORD_PUBLIC_KEY` | Discord App의 Ed25519 공개키, 서명 검증용 (조직 공용 봇) | +| `DISCORD_TOKEN` | Discord Bot Token (조직 공용 봇) | +| `DISCORD_APPLICATION_ID` | Discord Application ID (조직 공용 봇) | | `DISCORD_GUILD_ID` | 대상 Discord 서버 ID | -| `DISCORD_ROLE_ID` | 부여할 역할 ID | | `GITHUB_APP_ID` | GitHub App ID | | `GITHUB_APP_INSTALLATION_ID` | GitHub App의 조직 Installation ID | | `GITHUB_APP_PRIVATE_KEY` | GitHub App Private Key (PEM 형식) | +| `GH_PAT` | GitHub PAT (후원 조회 GraphQL용) | +| `STUDY_JOIN_CHANNEL_ID` | 스터디 신청 포럼 채널 ID (cron 자동 처리) | + +> **봇 자격증명 일원화**: `DISCORD_TOKEN`·`DISCORD_PUBLIC_KEY`·`DISCORD_APPLICATION_ID`는 커뮤니티 공용 봇(`DaleStudy`)의 **조직 레벨** 시크릿이며, CI(GitHub Actions) 배포 시 자동으로 주입됩니다. 저장소별로 개인 봇 자격증명을 따로 관리하지 않습니다. ### wrangler.jsonc vars (공개값, 커밋됨) | Key | 설명 | | ------------------ | ----------------- | -| `GITHUB_ORG` | GitHub 조직 이름 | -| `GITHUB_TEAM_SLUG` | 초대할 팀 slug | +| `GITHUB_ORG` | GitHub 조직 이름 | +| `ROLE_TEAM_CONFIG` | 역할-팀 매핑 (JSON, 슬래시 선택지) | ### 로컬 개발 (.dev.vars) diff --git a/plan.md b/plan.md index 5a31c1c..378095f 100644 --- a/plan.md +++ b/plan.md @@ -162,7 +162,7 @@ community-manager/ | Key | 설명 | | -------------------- | ------------------------------------------ | | `DISCORD_PUBLIC_KEY` | Discord App의 Ed25519 공개키 (서명 검증용) | -| `DISCORD_BOT_TOKEN` | Discord Bot Token | +| `DISCORD_TOKEN` | Discord Bot Token | | `DISCORD_GUILD_ID` | 대상 Discord 서버 ID | | `APP_ID` | GitHub App ID | | `APP_INSTALLATION_ID`| GitHub App의 조직 Installation ID | diff --git a/scripts/register-commands.ts b/scripts/register-commands.ts index d326b99..d49f405 100644 --- a/scripts/register-commands.ts +++ b/scripts/register-commands.ts @@ -11,7 +11,7 @@ try { } catch {} const APPLICATION_ID = process.env.DISCORD_APPLICATION_ID!; -const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN!; +const BOT_TOKEN = process.env.DISCORD_TOKEN!; const GUILD_ID = process.env.DISCORD_GUILD_ID!; const ROLE_TEAM_CONFIG = JSON.parse(process.env.ROLE_TEAM_CONFIG!); diff --git a/scripts/register.ts b/scripts/register.ts index acbbf41..1d967f3 100644 --- a/scripts/register.ts +++ b/scripts/register.ts @@ -1,6 +1,6 @@ import type { RoleTeamConfig } from "../src/types.js"; -const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN; +const BOT_TOKEN = process.env.DISCORD_TOKEN; const APPLICATION_ID = process.env.DISCORD_APPLICATION_ID; const GUILD_ID = process.env.DISCORD_GUILD_ID; const ROLE_TEAM_CONFIG = process.env.ROLE_TEAM_CONFIG; @@ -8,7 +8,7 @@ const ROLE_TEAM_CONFIG = process.env.ROLE_TEAM_CONFIG; if (!BOT_TOKEN || !APPLICATION_ID || !GUILD_ID || !ROLE_TEAM_CONFIG) { console.error("필요한 환경 변수가 설정되지 않았습니다."); console.error( - "필요: DISCORD_BOT_TOKEN, DISCORD_APPLICATION_ID, DISCORD_GUILD_ID, ROLE_TEAM_CONFIG", + "필요: DISCORD_TOKEN, DISCORD_APPLICATION_ID, DISCORD_GUILD_ID, ROLE_TEAM_CONFIG", ); process.exit(1); } diff --git a/src/github.test.ts b/src/github.test.ts index b3fb7c7..2084baa 100644 --- a/src/github.test.ts +++ b/src/github.test.ts @@ -4,13 +4,13 @@ import type { Env } from "./types.js"; const mockEnv: Env = { DISCORD_PUBLIC_KEY: "test-public-key", - DISCORD_BOT_TOKEN: "test-bot-token", + DISCORD_TOKEN: "test-bot-token", DISCORD_GUILD_ID: "test-guild-id", DISCORD_APPLICATION_ID: "test-app-id", GITHUB_ORG: "DaleStudy", - APP_ID: "123456", - APP_INSTALLATION_ID: "789", - APP_PRIVATE_KEY: "", + GITHUB_APP_ID: "123456", + GITHUB_APP_INSTALLATION_ID: "789", + GITHUB_APP_PRIVATE_KEY: "", ROLE_TEAM_CONFIG: "[]", GH_PAT: "test-pat", }; diff --git a/src/github.ts b/src/github.ts index 632b394..342c959 100644 --- a/src/github.ts +++ b/src/github.ts @@ -4,10 +4,10 @@ import type { Env } from "./types.js"; * GitHub App Installation Token 발급 */ export async function getInstallationToken(env: Env): Promise { - const jwt = await createJWT(env.APP_ID, env.APP_PRIVATE_KEY); + const jwt = await createJWT(env.GITHUB_APP_ID, env.GITHUB_APP_PRIVATE_KEY); const tokenResponse = await fetch( - `https://api.github.com/app/installations/${env.APP_INSTALLATION_ID}/access_tokens`, + `https://api.github.com/app/installations/${env.GITHUB_APP_INSTALLATION_ID}/access_tokens`, { method: "POST", headers: { diff --git a/src/index.ts b/src/index.ts index 906c733..9a6b515 100644 --- a/src/index.ts +++ b/src/index.ts @@ -112,7 +112,7 @@ async function processVerify( const [state] = await Promise.all([ inviteToTeam(env.GITHUB_ORG, teamConfig.teamSlug, githubUsername, token), - assignRole(env.DISCORD_GUILD_ID, discordUserId, roleConfig.discordRoleId, env.DISCORD_BOT_TOKEN), + assignRole(env.DISCORD_GUILD_ID, discordUserId, roleConfig.discordRoleId, env.DISCORD_TOKEN), ]); console.log(`[verify] existingMembership=${existingMembership}, invited to team state=${state}, assigned Discord role=${roleConfig.discordRoleId}`); @@ -149,7 +149,7 @@ function parseJoinMessage(content: string): ParsedJoinMessage | null { } async function handleChannelJoin(env: Env): Promise { - const posts = await getForumPosts(env.DISCORD_GUILD_ID, env.STUDY_JOIN_CHANNEL_ID, env.DISCORD_BOT_TOKEN); + const posts = await getForumPosts(env.DISCORD_GUILD_ID, env.STUDY_JOIN_CHANNEL_ID, env.DISCORD_TOKEN); const unprocessed = posts.filter((msg) => { if (msg.author?.bot) return false; @@ -171,9 +171,9 @@ async function handleChannelJoin(env: Env): Promise { threadId, msg.id, "⚠️ 메시지 형식이 올바르지 않습니다. 포스트 **제목(title)** 에 아래 형식으로 작성해주세요.\n```\n리트코드 스터디 8기 신청 (github_username, team)\n```", - env.DISCORD_BOT_TOKEN, + env.DISCORD_TOKEN, ); - await addReaction(threadId, msg.id, "❌", env.DISCORD_BOT_TOKEN); + await addReaction(threadId, msg.id, "❌", env.DISCORD_TOKEN); continue; } @@ -187,18 +187,18 @@ async function handleChannelJoin(env: Env): Promise { msg.author.id, env, ); - await replyToMessage(threadId, msg.id, result, env.DISCORD_BOT_TOKEN); + await replyToMessage(threadId, msg.id, result, env.DISCORD_TOKEN); const reaction = result.startsWith("❌") || result.startsWith("⚠️") ? "❌" : "✅"; - await addReaction(threadId, msg.id, reaction, env.DISCORD_BOT_TOKEN); + await addReaction(threadId, msg.id, reaction, env.DISCORD_TOKEN); } catch (err: any) { console.error(`[cron] error threadId=${threadId}`, err); await replyToMessage( threadId, msg.id, `⚠️ 오류가 발생했습니다: ${err?.message ?? "알 수 없는 오류"}`, - env.DISCORD_BOT_TOKEN, + env.DISCORD_TOKEN, ); - await addReaction(threadId, msg.id, "❌", env.DISCORD_BOT_TOKEN); + await addReaction(threadId, msg.id, "❌", env.DISCORD_TOKEN); } } } diff --git a/src/types.ts b/src/types.ts index e705abc..944cc98 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,12 +7,12 @@ export interface RoleTeamConfig { export interface Env { DISCORD_PUBLIC_KEY: string; - DISCORD_BOT_TOKEN: string; + DISCORD_TOKEN: string; DISCORD_GUILD_ID: string; GITHUB_ORG: string; - APP_ID: string; - APP_INSTALLATION_ID: string; - APP_PRIVATE_KEY: string; + GITHUB_APP_ID: string; + GITHUB_APP_INSTALLATION_ID: string; + GITHUB_APP_PRIVATE_KEY: string; ROLE_TEAM_CONFIG: string; GH_PAT: string; DISCORD_APPLICATION_ID: string; diff --git a/wrangler.jsonc b/wrangler.jsonc index fdf1c6b..bac31b1 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -6,7 +6,7 @@ "compatibility_flags": ["nodejs_compat"], "vars": { "GITHUB_ORG": "DaleStudy", - "ROLE_TEAM_CONFIG": "[{\"value\":\"leetcode07\",\"label\":\"leetcode07\",\"discordRoleId\":\"1233209102495383572\",\"teamSlug\":\"leetcode07\"},{\"value\":\"leetcode08\",\"label\":\"leetcode08\",\"discordRoleId\":\"1513390176200818769\",\"teamSlug\":\"leetcode08\"}]", + "ROLE_TEAM_CONFIG": "[{\"value\":\"leetcode07\",\"label\":\"leetcode07\",\"discordRoleId\":\"1474508579523268701\",\"teamSlug\":\"leetcode07\"},{\"value\":\"leetcode08\",\"label\":\"leetcode08\",\"discordRoleId\":\"1513390176200818769\",\"teamSlug\":\"leetcode08\"}]", }, "kv_namespaces": [], "durable_objects": {