Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .claude/worktrees/availability-aggregation
Submodule availability-aggregation deleted from fdbed6
14 changes: 0 additions & 14 deletions .env

This file was deleted.

44 changes: 40 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,65 @@
name: CI Pipeline

permissions:
contents: read

on:
pull_request:
branches:
- main
- dev
push:
branches:
- main
- dev

jobs:
build_and_lint:
code_quality:
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_BASE_URL: http://localhost:3000
NEXT_PUBLIC_SUPABASE_URL: https://example-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY: test-anon-key
KAKAO_REST_API_KEY: test-kakao-key
KAKAO_REDIRECT_URI: http://localhost:3000/api/auth/kakao/callback
GOOGLE_CLIENT_ID: test-google-client-id
GOOGLE_CLIENT_SECRET: test-google-client-secret
DATABASE_URL: file:./ci.db

steps:
- name: 저장소 체크아웃 (Checkout Repository)
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Node.js 세팅 (Setup Node.js)
uses: actions/setup-node@v4
uses: actions/setup-node@v6
Comment on lines 30 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "[검증1] actions 참조가 SHA(40 hex)로 고정됐는지 확인"
python - <<'PY'
import re, pathlib
text = pathlib.Path(".github/workflows/ci.yml").read_text()
for action in ("checkout", "setup-node"):
    m = re.search(rf'uses:\s*actions/{action}@([^\n]+)', text)
    ref = m.group(1).strip() if m else None
    ok = bool(ref and re.fullmatch(r"[0-9a-f]{40}", ref))
    print(f"{action}: ref={ref!r}, sha_pinned={ok}")
PY

echo "[검증2] checkout persist-credentials: false 존재 여부"
rg -n 'persist-credentials:\s*false' "$file" || true

Repository: Siul49/moim

Length of output: 219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "=== Relevant checkout/setup-node steps (lines ~1-120) ==="
nl -ba "$file" | sed -n '1,120p'

echo "=== Exact checkout uses line and any adjacent with/params ==="
nl -ba "$file" | sed -n '/actions\/checkout@/ {p; n; p; n; p; n; p; }'

Repository: Siul49/moim

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "=== Relevant checkout/setup-node steps (first 120 lines) ==="
sed -n '1,120p' "$file"

echo "=== Exact lines containing actions/checkout@ and actions/setup-node@ ==="
rg -n 'actions/(checkout|setup-node)@' "$file"

echo "=== Any persist-credentials under checkout step ==="
rg -n --context=3 'actions/checkout@' "$file" || true
rg -n 'persist-credentials:' "$file" || true

Repository: Siul49/moim

Length of output: 2128


🌐 Web query:

actions/checkout persist-credentials default value true

💡 Result:

The actions/checkout input persist-credentials defaults to true (i.e., credentials/token are persisted in the local git config unless you set persist-credentials: false). [1][2] Source detail: the actions/checkout documentation states the auth token is persisted in the local git config to enable authenticated git commands, and that you can opt out by setting persist-credentials: false; it also lists “Default: true” for persist-credentials in the action inputs section. [1][2]

Citations:


CI 공급망/자격증명 노출: actions 참조 SHA 고정 + checkout persist-credentials 차단 필요

  • .github/workflows/ci.yml에서 actions/checkout@v6, actions/setup-node@v6가 SHA로 미고정되어 태그 재지정 시 파이프라인 무결성/재현성이 약해집니다(라인 31, 34).
  • actions/checkoutpersist-credentials: false가 없어서 persist-credentials 기본값(true)으로 토큰이 로컬 git config에 남을 수 있습니다.

Why: CI 실행 환경에서 미고정 액션/자격증명 영속은 공급망·자격증명 노출 표면을 키웁니다.
How: uses:는 40-hex 커밋 SHA로 고정하고, checkout step에 with: persist-credentials: false를 명시하세요.

최소 수정 예시
-      - name: 저장소 체크아웃 (Checkout Repository)
-        uses: actions/checkout@v6
+      - name: 저장소 체크아웃 (Checkout Repository)
+        uses: actions/checkout@<검증된_커밋_SHA>
+        with:
+          persist-credentials: false

-      - name: Node.js 세팅 (Setup Node.js)
-        uses: actions/setup-node@v6
+      - name: Node.js 세팅 (Setup Node.js)
+        uses: actions/setup-node@<검증된_커밋_SHA>
         with:
           node-version: "20"
           cache: "npm"
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 30-31: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 31-31: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 34-34: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 30 - 34, The workflow uses floating
action tags (actions/checkout@v6 and actions/setup-node@v6) and misses the
checkout credential safeguard; update the Checkout Repository step (uses:
actions/checkout@v6) to pin to the action's exact 40-hex commit SHA and add
with: persist-credentials: false, and likewise pin the Setup Node.js step (uses:
actions/setup-node@v6) to its 40-hex commit SHA to ensure reproducibility and
prevent credential persistence.

with:
node-version: '20'
node-version: "20"
cache: "npm"

- name: 패키지 설치 (Install Dependencies)
run: npm ci

- name: CI 전용 보안 키 생성 (Generate CI Secrets)
run: |
{
echo "JWT_SECRET=$(openssl rand -base64 32)"
echo "ENCRYPTION_SECRET=$(openssl rand -hex 32)"
} >> "$GITHUB_ENV"

- name: 데이터베이스 마이그레이션 (Database Migration)
run: npm run db:migrate

- name: 코드 린트 검사 (Lint Check)
run: npm run lint

- name: 테스트 실행 (Unit Test)
run: npm run test

- name: Next.js 빌드 테스트 (Build Test)
run: npm run build

- name: Playwright 브라우저 설치 (Chromium)
run: npx playwright install --with-deps chromium

- name: E2E 테스트 실행 (Chromium)
run: npm run test:e2e -- --project=chromium
3 changes: 3 additions & 0 deletions .github/workflows/issue-compliance.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: 이슈 템플릿 검사 (Compliance Check)

permissions:
issues: read

on:
issues:
types: [opened, edited]
Expand Down
39 changes: 11 additions & 28 deletions .github/workflows/pr-compliance.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
name: PR 품질 검사 (Compliance + Code Quality)
name: PR 템플릿 검사

permissions:
pull-requests: read

on:
pull_request:
types: [opened, edited, synchronize]

jobs:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# 1. PR 템플릿 양식 검사
check-template:
runs-on: ubuntu-latest
steps:
- name: 체크리스트 달성도 검사
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
# 체크리스트 자체가 누락된 PR은 템플릿을 사용하지 않은 것으로 본다
if ! echo "$PR_BODY" | grep -Eq "\- \[[xX ]\]"; then
echo "❌ PR 템플릿의 체크리스트가 누락되었습니다. 템플릿에 맞게 작성해주세요."
exit 1
fi

# 체크 안 된 항목('- [ ]')이 하나라도 남아있으면 에러 뱉기
if echo "$PR_BODY" | grep -q "\- \[ \]"; then
echo "❌ 체크리스트 중 완료되지 않은 항목이 있습니다! 모두 [x]로 체크해야 Merge할 수 있습니다."
Expand All @@ -24,30 +32,5 @@ jobs:
echo "❌ PR 설명이 너무 짧습니다. 템플릿에 맞게 어떤 작업을 했는지 명확하게 설명해주세요."
exit 1
fi

echo "✅ PR 템플릿 검사 통과 성공!"

# 2. 코드 품질 검증 (린트 → 테스트 → 빌드)
code-quality:
runs-on: ubuntu-latest
steps:
- name: 📥 코드 체크아웃
uses: actions/checkout@v4

- name: 🟢 Node.js 설치
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: 📦 의존성 설치
run: npm ci

- name: 🔍 린트 검사
run: npm run lint

- name: 🧪 테스트 실행
run: npm run test

- name: 🏗️ 빌드 테스트
run: npm run build
echo "✅ PR 템플릿 검사 통과 성공!"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ yarn-error.log*
next-env.d.ts

# AI & Local configs
.claude/
.agents/*
!.agents/docs/
*.pdf
Expand Down
74 changes: 39 additions & 35 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

## 기술 스택 요약

| 영역 | 기술 | 버전 |
|------|------|------|
| 프레임워크 | Next.js (App Router) | 14 |
| 언어 | TypeScript | 5.x |
| 스타일링 | Tailwind CSS + shadcn/ui | v3 |
| 단위 테스트 | Vitest + React Testing Library | - |
| E2E 테스트 | Playwright | - |
| 백엔드 | Supabase (PostgreSQL + Auth + Realtime) | - |
| 배포 | Vercel | - |
| 영역 | 기술 | 버전 |
| ----------- | --------------------------------------- | ---- |
| 프레임워크 | Next.js (App Router) | 14 |
| 언어 | TypeScript | 5.x |
| 스타일링 | Tailwind CSS + shadcn/ui | v3 |
| 단위 테스트 | Vitest + React Testing Library | - |
| E2E 테스트 | Playwright | - |
| 백엔드 | Supabase (PostgreSQL + Auth + Realtime) | - |
| 배포 | Vercel | - |

---

Expand All @@ -36,11 +36,7 @@ MOIM/
│ │ └── globals.css # 전역 CSS + 디자인 토큰
│ │
│ ├── 📁 components/ # 재사용 가능한 UI 컴포넌트
│ │ ├── 📁 ui/ # shadcn/ui 원시 컴포넌트 (Button, Card 등)
│ │ └── 📁 schedule/ # 스케줄링 도메인 전용 컴포넌트
│ │ ├── TimeGrid.tsx # When2meet 스타일 시간 선택 그리드
│ │ ├── ParticipantList.tsx # 참여자 현황 목록
│ │ └── AvailabilityResult.tsx # 공통 가용시간 결과
│ │ └── 📁 ui/ # shadcn/ui 원시 컴포넌트 (Button 등)
│ │
│ ├── 📁 lib/ # 비즈니스 로직 & 유틸리티
│ │ ├── 📁 scheduling/ # ⭐ 핵심 알고리즘 (순수 함수)
Expand All @@ -51,6 +47,7 @@ MOIM/
│ │ ├── 📁 supabase/ # Supabase DB 클라이언트
│ │ │ ├── client.ts # 브라우저용 클라이언트
│ │ │ └── server.ts # 서버 사이드 클라이언트
│ │ ├── 📁 schedules/ # 일정 링크/참여 데이터 저장소
│ │ └── utils.ts # 공용 유틸리티
│ │
│ ├── 📁 types/ # TypeScript 타입 정의
Expand All @@ -66,6 +63,7 @@ MOIM/
├── 📁 .github/ # GitHub 자동화
│ └── 📁 workflows/ # CI/CD 파이프라인
├── scripts/ # 로컬 DB schema 보장 등 개발 스크립트
├── vitest.config.ts # Vitest 설정
├── playwright.config.ts # Playwright 설정
├── tailwind.config.ts # Tailwind CSS 설정
Expand All @@ -81,18 +79,20 @@ MOIM/

Next.js App Router의 **파일 = URL** 규칙을 활용하여, PRD의 사용자 플로우를 그대로 폴더로 표현합니다.

| URL | 파일 | PRD 매핑 |
|-----|------|----------|
| `/login` | `(auth)/login/page.tsx` | 3.1 로그인/회원가입 |
| `/schedule/create` | `schedule/create/page.tsx` | 3.1 일정 잡기 생성 |
| `/schedule/:id` | `schedule/[id]/page.tsx` | 3.2~3.3 참여자 진입 |
| URL | 파일 | PRD 매핑 |
| ------------------ | -------------------------- | ------------------- |
| `/login` | `(auth)/login/page.tsx` | 3.1 로그인/회원가입 |
| `/schedule/create` | `schedule/create/page.tsx` | 3.1 일정 잡기 생성 |
| `/schedule/:id` | `schedule/[id]/page.tsx` | 3.2~3.3 참여자 진입 |

**`(auth)` 괄호 그룹이란?**
Next.js에서 폴더명을 괄호로 감싸면 URL에 영향을 주지 않고 레이아웃만 분리할 수 있습니다.

- `/login`의 URL은 그대로지만, 별도의 `layout.tsx`를 가져 GNB가 없는 레이아웃을 사용합니다.

**`[id]` 동적 라우트란?**
대괄호로 감싼 폴더는 URL의 일부를 변수로 받습니다.

- `/schedule/abc123` → `params.id = 'abc123'`

### 2. `src/components/` — UI는 범용과 도메인으로 분리한다
Expand All @@ -104,7 +104,7 @@ components/
```

- `ui/`: 버튼, 카드, 모달 등 어떤 프로젝트에서든 쓸 수 있는 범용 컴포넌트
- `schedule/`: TimeGrid, ParticipantList 등 MOIM 서비스에만 존재하는 컴포넌트
- 스케줄 화면처럼 한 라우트에 강하게 묶인 UI는 현재 `src/app/schedule/*` 아래에 둔다.

### 3. `src/lib/scheduling/` — 핵심 로직은 UI와 분리한다

Expand All @@ -113,11 +113,12 @@ UI 없이 독립적으로 실행되므로, 테스트가 빠르고 정확합니

```typescript
// 이렇게 React 없이 단독으로 테스트 가능
const result = findCommonSlots([userA, userB])
expect(result).toEqual([{ day: 'MON', startHour: 13, endHour: 15 }])
const result = findCommonSlots([userA, userB]);
expect(result).toEqual([{ day: "MON", startHour: 13, endHour: 15 }]);
```

**왜 분리하나?**

- 순수 함수는 입력→출력만 검증하면 되므로 TDD에 최적
- 브라우저 환경(jsdom)이 필요 없어 테스트가 10배 빨라짐
- 나중에 서버 사이드에서도 같은 로직을 재사용 가능
Expand All @@ -132,6 +133,7 @@ lib/scheduling/
```

**왜 루트 `tests/` 폴더가 아니라 코드 옆에 두는가?**

- 파일 탐색 시 구현과 테스트를 한눈에 볼 수 있음
- 새 기능 추가 시 "테스트 파일은 어디 만들지?" 고민이 없음
- AI 도구에게 "이 파일 테스트해줘"라고 할 때 컨텍스트 파악이 빠름
Expand All @@ -153,20 +155,21 @@ Python에서 Pydantic 모델 → pytest 테스트 → 비즈니스 로직 순서

### 3계층 테스트 (Testing Trophy)

| 계층 | 도구 | 대상 | 비중 |
|------|------|------|------|
| **Unit** | Vitest | `lib/scheduling/` 순수 로직 | 50% |
| **Integration** | React Testing Library + Vitest | `components/` 상호작용 | 30% |
| **E2E** | Playwright | 사용자 전체 플로우 | 20% |
| 계층 | 도구 | 대상 | 비중 |
| --------------- | ------------------------------ | --------------------------- | ---- |
| **Unit** | Vitest | `lib/scheduling/` 순수 로직 | 50% |
| **Integration** | React Testing Library + Vitest | `components/` 상호작용 | 30% |
| **E2E** | Playwright | 사용자 전체 플로우 | 20% |

### 테스트 실행 명령어

| 명령어 | 용도 |
|--------|------|
| `npm run test` | 모든 단위 테스트 한 번 실행 (CI용) |
| `npm run test:watch` | 파일 저장 시 자동 재실행 (개발용) |
| `npm run test:coverage` | 커버리지 리포트와 함께 실행 |
| `npm run test:e2e` | 브라우저 E2E 테스트 실행 |
| 명령어 | 용도 |
| ----------------------- | ---------------------------------- |
| `npm run test` | 모든 단위 테스트 한 번 실행 (CI용) |
| `npm run test:watch` | 파일 저장 시 자동 재실행 (개발용) |
| `npm run test:coverage` | 커버리지 리포트와 함께 실행 |
| `npm run test:e2e` | 브라우저 E2E 테스트 실행 |
| `npm run db:migrate` | 로컬 SQLite schema 준비 |

### TDD 워크플로우

Expand All @@ -186,8 +189,9 @@ PR을 올리면 아래 검사가 자동으로 실행됩니다:

```
PR 생성 → 템플릿 검사 → 린트(npm run lint)
→ 테스트(npm run test)
→ 빌드(npm run build)
→ 테스트(npm run test)
→ 빌드(npm run build)
→ Chromium E2E(npm run test:e2e -- --project=chromium)
→ 모두 ✅ → Merge 가능
```

Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# MOIM

MOIM은 주최자가 모임 링크를 만들고, 참여자가 로그인 없이 가능한 시간을 제출하면 공통 가능한 시간을 추천해 주는 일정 조율 앱입니다.

## 빠른 시작

```powershell
npm ci
npm run db:migrate
npm run dev
```

로컬 기본 DB는 `DATABASE_URL`이 없을 때 `file:./dev.db`를 사용합니다. 실제 배포나 공유 환경에서는 `.env.example`을 기준으로 `.env`를 준비하세요.

## 검증 명령

```powershell
npm run lint
npm run test
npm run test:coverage
npm run build
npm run test:e2e -- --project=chromium
```

`test`, `test:coverage`, `test:e2e`, `dev`는 실행 전에 로컬 SQLite schema를 보장합니다.

## 먼저 읽을 문서

1. `docs/README.md`: 현재 문서 기준과 v1/v2 구분
2. `docs/v2/user-flow.md`: 최신 사용자 흐름 및 수익화 연결안
3. `docs/v1/codex-work-context.md`: 현재 개발 handoff와 주의점
4. `ARCHITECTURE.md`: 코드 구조와 테스트 전략
5. `convention.md`: 커밋, 브랜치, 이슈, PR 규칙

## 현재 안정성 기준

- CI는 lint, unit test, build, Chromium E2E를 실행합니다.
- schedule 생성/참여 API는 process memory가 아니라 SQLite-backed Prisma store를 사용합니다.
- `.env`, 로컬 DB, Playwright 결과물, 문서 제출용 바이너리는 Git에 올리지 않습니다.
Loading
Loading