Skip to content

Commit bbdfc1a

Browse files
committed
initial commit
1 parent a66258e commit bbdfc1a

File tree

26 files changed

+1739
-0
lines changed

26 files changed

+1739
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @yeongbin1999

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blank_issues_enabled: false

.github/ISSUE_TEMPLATE/task.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: "📝 Task"
2+
description: 작업 이슈 (기능, 버그, 리팩터링, 문서, 테스트 등)
3+
title: "type(scope): 한 줄 요약"
4+
projects: ["yeongbin1999/9"]
5+
6+
body:
7+
- type: dropdown
8+
id: type
9+
attributes:
10+
label: Type
11+
description: "작업의 성격 선택"
12+
options:
13+
- feat (새로운 기능)
14+
- fix (버그 수정)
15+
- refactor (리팩터링)
16+
- docs (문서 작업)
17+
- chore (설정/잡일)
18+
- test (테스트 코드)
19+
validations:
20+
required: true
21+
22+
- type: dropdown
23+
id: scope
24+
attributes:
25+
label: Scope
26+
description: "작업 범위 선택"
27+
options:
28+
- fe (Frontend)
29+
- be (Backend)
30+
- infra (Infra/배포/환경)
31+
validations:
32+
required: true
33+
34+
- type: input
35+
id: summary
36+
attributes:
37+
label: Summary
38+
description: "짧은 요약 (브랜치명은 번호 기반)"
39+
placeholder: "로그인 API 구현"
40+
validations:
41+
required: true
42+
43+
- type: textarea
44+
id: details
45+
attributes:
46+
label: Details / Acceptance Criteria
47+
placeholder: |
48+
- As a user, ...
49+
- Done when: ...
50+
validations:
51+
required: false

.github/workflows/backend-ci.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Backend CI
2+
3+
on:
4+
push:
5+
branches: [main, dev]
6+
paths: ['backend/**']
7+
pull_request:
8+
types: [opened, synchronize, reopened, edited]
9+
branches: [main, dev]
10+
paths: ['backend/**']
11+
12+
# 같은 브랜치에서 연속 트리거 시 이전 잡 취소 (중복/불필요 비용 절감)
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
15+
cancel-in-progress: true
16+
17+
# 최소 권한 원칙
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
backend-ci:
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
31+
- name: Set up JDK 21
32+
uses: actions/setup-java@v4
33+
with:
34+
distribution: 'temurin'
35+
java-version: '21'
36+
cache: 'gradle' # Gradle 의존성 캐시
37+
38+
# Gradle 캐시/빌드 최적화 (권장)
39+
- name: Setup Gradle
40+
uses: gradle/actions/setup-gradle@v4
41+
42+
- name: Grant execute permission to gradlew
43+
working-directory: backend
44+
run: chmod +x ./gradlew
45+
46+
# .env 주입 (로그에 값 노출 금지: echo만 파일로 리다이렉트)
47+
- name: Load .env from secrets
48+
run: |
49+
mkdir -p ./backend
50+
printf "%s" "${{ secrets.DOT_ENV }}" > ./backend/.env
51+
52+
- name: Build & Test (backend)
53+
working-directory: backend
54+
run: |
55+
./gradlew clean test build --no-daemon --warning-mode=all
56+
57+
# 실패 디버깅용 테스트 리포트/로그 업로드 (항상)
58+
- name: Upload test reports
59+
if: always()
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: backend-test-reports
63+
path: |
64+
backend/build/reports/tests/test
65+
backend/build/test-results/test
66+
backend/build/reports/jacoco/**/*
67+
if-no-files-found: ignore

.github/workflows/issue-branch.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Issue → Branch
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: write
9+
issues: read
10+
11+
jobs:
12+
create-branch:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Determine base branch (prefer dev, fallback main)
21+
id: base
22+
run: |
23+
git fetch --prune origin
24+
if git ls-remote --heads origin dev | grep -q refs/heads/dev; then
25+
echo "base=dev" >> $GITHUB_OUTPUT
26+
else
27+
echo "base=main" >> $GITHUB_OUTPUT
28+
fi
29+
30+
- name: Generate Branch Name (type/scope/number)
31+
id: gen
32+
shell: bash
33+
run: |
34+
set -euo pipefail
35+
36+
TITLE="${{ github.event.issue.title }}"
37+
NUM="${{ github.event.issue.number }}"
38+
39+
# type(scope): summary 형식에서 type/scope 추출
40+
TYPE=$(echo "$TITLE" | sed -nE 's/^(feat|fix|refactor|docs|chore|hotfix)\((fe|be|infra)\):.*/\1/p')
41+
SCOPE=$(echo "$TITLE" | sed -nE 's/^(feat|fix|refactor|docs|chore|hotfix)\((fe|be|infra)\):.*/\2/p')
42+
43+
# 기본값 보정
44+
[ -z "$TYPE" ] && TYPE="chore"
45+
[ -z "$SCOPE" ] && SCOPE="infra"
46+
47+
BRANCH="${TYPE}/${SCOPE}/${NUM}"
48+
49+
echo "✅ TYPE : $TYPE"
50+
echo "✅ SCOPE : $SCOPE"
51+
echo "✅ BRANCH : $BRANCH"
52+
53+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
54+
55+
- name: Skip if branch already exists
56+
id: exists
57+
run: |
58+
if git ls-remote --heads origin "${{ steps.gen.outputs.branch }}" | grep -q .; then
59+
echo "exists=true" >> $GITHUB_OUTPUT
60+
else
61+
echo "exists=false" >> $GITHUB_OUTPUT
62+
fi
63+
64+
- name: Create & push branch from base
65+
if: steps.exists.outputs.exists == 'false'
66+
run: |
67+
git config user.name "github-actions[bot]"
68+
git config user.email "github-actions[bot]@users.noreply.github.com"
69+
git fetch --prune origin
70+
git checkout -b "${{ steps.gen.outputs.branch }}" "origin/${{ steps.base.outputs.base }}"
71+
git push origin "${{ steps.gen.outputs.branch }}"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Close issues & delete feature branch on dev merge
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
7+
permissions:
8+
issues: write
9+
contents: write
10+
pull-requests: read
11+
12+
jobs:
13+
on-dev-merge:
14+
if: >
15+
github.event.pull_request.merged == true &&
16+
github.event.pull_request.base.ref == 'dev'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Close linked issues
20+
uses: actions/github-script@v7
21+
with:
22+
github-token: ${{ secrets.GITHUB_TOKEN }}
23+
script: |
24+
const pr = context.payload.pull_request;
25+
const text = `${pr.title}\n${pr.body || ''}`;
26+
27+
// #123, (#123)
28+
const directRefs = [...text.matchAll(/#(\d+)/g)].map(m => Number(m[1]));
29+
// closes/fixes/resolves #123 (대소문자 무시)
30+
const verbRefs = [...text.matchAll(/\b(closes?|fix(?:e|es)?s?|resolves?)\s+#(\d+)/ig)].map(m => Number(m[2]));
31+
32+
const issueSet = new Set([...directRefs, ...verbRefs]);
33+
34+
if (issueSet.size === 0) {
35+
console.log('ℹ️ No linked issues found in PR title/body.');
36+
} else {
37+
const { owner, repo } = context.repo;
38+
for (const issue_number of issueSet) {
39+
try {
40+
await github.rest.issues.update({ owner, repo, issue_number, state: 'closed' });
41+
console.log(`✅ Closed issue #${issue_number}`);
42+
} catch (e) {
43+
console.log(`⚠️ Skip closing #${issue_number}: ${e.message}`);
44+
}
45+
}
46+
}
47+
48+
- name: Delete head branch (skip protected/main/dev and fork PRs)
49+
uses: actions/github-script@v7
50+
with:
51+
github-token: ${{ secrets.GITHUB_TOKEN }}
52+
script: |
53+
const pr = context.payload.pull_request;
54+
const headRef = pr.head.ref; // ex) feat/be/123
55+
const baseRef = pr.base.ref; // dev
56+
const headRepo = pr.head.repo?.full_name;
57+
const thisRepo = context.repo.owner + '/' + context.repo.repo;
58+
59+
// 포크에서 온 PR은 삭제 불가
60+
if (headRepo !== thisRepo) {
61+
console.log(`ℹ️ Head branch is on a fork (${headRepo}); cannot delete.`);
62+
return;
63+
}
64+
65+
const protectedBranches = ['main', 'dev'];
66+
if (protectedBranches.includes(headRef)) {
67+
console.log(`ℹ️ Skip deleting protected branch: ${headRef}`);
68+
return;
69+
}
70+
71+
try {
72+
await github.rest.git.deleteRef({
73+
owner: context.repo.owner,
74+
repo: context.repo.repo,
75+
ref: `heads/${headRef}`
76+
});
77+
console.log(`🗑️ Deleted branch: ${headRef}`);
78+
} catch (e) {
79+
console.log(`⚠️ Failed to delete ${headRef}: ${e.message}`);
80+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: Auto PR Title from Issue
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, edited]
6+
7+
permissions:
8+
issues: read
9+
pull-requests: write
10+
11+
jobs:
12+
set-pr-title:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Set PR title same as issue title + number
16+
uses: actions/github-script@v7
17+
with:
18+
github-token: ${{ secrets.GITHUB_TOKEN }}
19+
script: |
20+
const pr = context.payload.pull_request;
21+
22+
// 1) Dependabot 등 봇 PR은 패스
23+
const botLogins = new Set(['dependabot[bot]']);
24+
if (botLogins.has(pr.user.login)) {
25+
console.log('ℹ️ Bot PR detected, skipping');
26+
return;
27+
}
28+
29+
const branch = pr.head.ref; // ex) feat/be/123
30+
// 2) 우리 브랜치 컨벤션이 아니면 스킵(실수 방지)
31+
const branchOK = /^(feat|fix|refactor|docs|chore|test)\/(fe|be|infra)\/\d+$/.test(branch);
32+
if (!branchOK) {
33+
console.log(`ℹ️ Branch "${branch}" does not match our pattern; skipping`);
34+
return;
35+
}
36+
37+
// 3) 브랜치에서 이슈 번호 추출 (마지막 세그먼트 숫자)
38+
const m = branch.match(/(?:\/|^)(\d+)$/);
39+
if (!m) {
40+
console.log(`❌ Could not extract issue number from "${branch}"`);
41+
return;
42+
}
43+
const issueNumber = Number(m[1]);
44+
45+
// 4) 이슈 조회 (404면 스킵)
46+
let issue;
47+
try {
48+
issue = await github.rest.issues.get({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
issue_number: issueNumber
52+
});
53+
} catch (error) {
54+
if (error.status === 404) {
55+
console.log(`❌ Issue #${issueNumber} not found; skipping`);
56+
return;
57+
}
58+
throw error;
59+
}
60+
61+
const issueTitle = (issue.data.title || '').trim();
62+
if (!issueTitle) {
63+
console.log(`❌ Issue #${issueNumber} has empty title; skipping`);
64+
return;
65+
}
66+
67+
// 5) PR 제목 동기화: "<이슈제목> (#번호)"
68+
const desiredTitle = `${issueTitle} (#${issueNumber})`;
69+
if (desiredTitle !== pr.title) {
70+
await github.rest.pulls.update({
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
pull_number: pr.number,
74+
title: desiredTitle
75+
});
76+
console.log(`✅ PR title updated → ${desiredTitle}`);
77+
} else {
78+
console.log('ℹ️ PR title already up-to-date');
79+
}
80+
81+
// 6) (옵션) PR 본문에 이슈 링크 없으면 추가
82+
const body = pr.body || '';
83+
if (!new RegExp(`\\(#${issueNumber}\\)`).test(body) && !new RegExp(`#${issueNumber}(\\b|$)`).test(body)) {
84+
const appended = `${body}\n\nLinked: #${issueNumber}`.trim();
85+
await github.rest.pulls.update({
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
pull_number: pr.number,
89+
body: appended
90+
});
91+
console.log(`📝 Appended issue link to PR body`);
92+
}

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# IntelliJ IDEA
2+
.idea/
3+
*.iml
4+
5+
# Mac
6+
.DS_Store
7+
8+
# Windows
9+
Thumbs.db
10+
Desktop.ini
11+
12+
# terraform
13+
terraform.tfvars
14+
*.tfvars
15+
*.tfstate
16+
*.tfstate.*
17+
.terraform/
18+
infra/terraform/.terraform.lock.hcl
19+
infra/terraform/.terraform/
20+
infra/terraform/terraform.tfvars

0 commit comments

Comments
 (0)