@wondev/dotenv-example 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +38 -0
- package/CLAUDE.md +1 -40
- package/bin/index.js +64 -55
- package/package.json +8 -2
- package/prompts/20260102_165844.md +3 -0
- package/prompts/20260102_170026.md +3 -0
- package/prompts/20260102_170120.md +4 -0
- package/prompts/20260102_170500.md +3 -0
- package/prompts/20260102_170839.md +3 -0
- package/prompts/20260102_171046.md +3 -0
- package/prompts/20260102_171147.md +38 -0
- package/prompts/20260102_171336.md +76 -0
- package/prompts/20260102_171546.md +40 -0
- package/.claude/README.md +0 -60
- package/.claude/commands/business_logic.md +0 -143
- package/.claude/commands/generate-prd.md +0 -175
- package/.claude/commands/gotobackend.md +0 -569
- package/.claude/commands/playwrightMCP_install.md +0 -113
- package/.claude/commands/setting_dev.md +0 -731
- package/.claude/commands/tech-lead.md +0 -404
- package/.claude/commands/user-flow.md +0 -839
- package/.claude/settings.local.json +0 -9
- package/.cursor/README.md +0 -10
- package/.cursor/mcp.json +0 -31
- package/.cursor/rules/common/cursor-rules.mdc +0 -53
- package/.cursor/rules/common/git-convention.mdc +0 -86
- package/.cursor/rules/common/self-improve.mdc +0 -72
- package/.cursor/rules/common/tdd.mdc +0 -81
- package/.cursor/rules/common/vibe-coding.mdc +0 -114
- package/.cursor/rules/supabase/supabase-bootstrap-auth.mdc +0 -236
- package/.cursor/rules/supabase/supabase-create-db-functions.mdc +0 -136
- package/.cursor/rules/supabase/supabase-create-migration.mdc +0 -50
- package/.cursor/rules/supabase/supabase-create-rls-policies.mdc +0 -248
- package/.cursor/rules/supabase/supabase-declarative-database-schema.mdc +0 -78
- package/.cursor/rules/supabase/supabase-postgres-sql-style-guide.mdc +0 -133
- package/.cursor/rules/supabase/supabase-writing-edge-functions.mdc +0 -105
- package/.cursor/rules/web/design-rules.mdc +0 -381
- package/.cursor/rules/web/nextjs-convention.mdc +0 -237
- package/.cursor/rules/web/playwright-test-guide.mdc +0 -176
- package/.cursor/rules/web/toss-frontend.mdc +0 -695
- package/.env +0 -4
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
# Supabase + DrizzleORM 마이그레이션 가이드
|
|
2
|
-
|
|
3
|
-
## 🎯 목적
|
|
4
|
-
|
|
5
|
-
로컬 스토리지 기반으로 완성된 프론트엔드를 **Supabase + DrizzleORM**으로 마이그레이션하여 프로덕션 레벨 애플리케이션으로 업그레이드합니다.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 📋 마이그레이션 전 체크리스트
|
|
10
|
-
|
|
11
|
-
### ✅ 필수 조건 확인
|
|
12
|
-
- [ ] 프론트엔드 모든 기능이 로컬 스토리지로 완전히 동작
|
|
13
|
-
- [ ] 모든 UI 컴포넌트가 완성되어 사용자 테스트 가능
|
|
14
|
-
- [ ] docs/PRD.md, docs/TODO.md, docs/business-logic.md 문서 존재
|
|
15
|
-
- [ ] TypeScript 에러 0개 상태
|
|
16
|
-
- [ ] 기본적인 테스트 코드 작성 완료
|
|
17
|
-
|
|
18
|
-
### 🚨 중요한 순서
|
|
19
|
-
```
|
|
20
|
-
✅ 프론트엔드 완성 (이미 완료)
|
|
21
|
-
↓
|
|
22
|
-
🔄 현재 단계: Backend 마이그레이션
|
|
23
|
-
↓
|
|
24
|
-
🚀 프로덕션 배포
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 🏗️ 폴더 구조 (Supabase + DrizzleORM)
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
project-root/
|
|
33
|
-
├── docs/
|
|
34
|
-
│ ├── PRD.md # 업데이트 필요
|
|
35
|
-
│ ├── TODO.md # 업데이트 필요
|
|
36
|
-
│ ├── business-logic.md # 업데이트 필요
|
|
37
|
-
│ ├── progress.md
|
|
38
|
-
│ ├── user-flow.md
|
|
39
|
-
│ └── migration-plan.md # 신규 생성
|
|
40
|
-
│
|
|
41
|
-
├── src/
|
|
42
|
-
│ ├── app/ # Next.js 15 App Router
|
|
43
|
-
│ │ ├── (auth)/ # 인증 관련 페이지 그룹
|
|
44
|
-
│ │ │ ├── login/
|
|
45
|
-
│ │ │ ├── signup/
|
|
46
|
-
│ │ │ └── reset-password/
|
|
47
|
-
│ │ ├── (dashboard)/ # 대시보드 페이지 그룹
|
|
48
|
-
│ │ ├── api/ # API Routes
|
|
49
|
-
│ │ │ ├── auth/
|
|
50
|
-
│ │ │ └── webhooks/
|
|
51
|
-
│ │ ├── globals.css
|
|
52
|
-
│ │ ├── layout.tsx
|
|
53
|
-
│ │ └── page.tsx
|
|
54
|
-
│ │
|
|
55
|
-
│ ├── components/ # UI 컴포넌트 (기존 유지)
|
|
56
|
-
│ │ ├── ui/ # shadcn/ui 컴포넌트
|
|
57
|
-
│ │ ├── auth/ # 인증 관련 컴포넌트
|
|
58
|
-
│ │ │ ├── login-form.tsx
|
|
59
|
-
│ │ │ ├── signup-form.tsx
|
|
60
|
-
│ │ │ └── auth-provider.tsx
|
|
61
|
-
│ │ └── common/ # 공통 컴포넌트
|
|
62
|
-
│ │
|
|
63
|
-
│ ├── lib/ # 핵심 라이브러리
|
|
64
|
-
│ │ ├── supabase/
|
|
65
|
-
│ │ │ ├── client.ts # 클라이언트 사이드
|
|
66
|
-
│ │ │ ├── server.ts # 서버 사이드
|
|
67
|
-
│ │ │ ├── middleware.ts # 미들웨어
|
|
68
|
-
│ │ │ └── types.ts # Supabase 타입
|
|
69
|
-
│ │ ├── drizzle/
|
|
70
|
-
│ │ │ ├── schema.ts # DB 스키마 정의
|
|
71
|
-
│ │ │ ├── migrations/ # 마이그레이션 파일들
|
|
72
|
-
│ │ │ ├── queries/ # 쿼리 함수들
|
|
73
|
-
│ │ │ │ ├── users.ts
|
|
74
|
-
│ │ │ │ ├── posts.ts
|
|
75
|
-
│ │ │ │ └── index.ts
|
|
76
|
-
│ │ │ └── db.ts # DB 연결 설정
|
|
77
|
-
│ │ ├── auth/
|
|
78
|
-
│ │ │ ├── config.ts # 인증 설정
|
|
79
|
-
│ │ │ ├── providers.ts # OAuth 프로바이더
|
|
80
|
-
│ │ │ └── utils.ts # 인증 유틸리티
|
|
81
|
-
│ │ └── utils.ts
|
|
82
|
-
│ │
|
|
83
|
-
│ ├── hooks/ # 커스텀 훅
|
|
84
|
-
│ │ ├── use-auth.ts # 인증 관련 훅
|
|
85
|
-
│ │ ├── use-supabase.ts # Supabase 훅
|
|
86
|
-
│ │ └── use-local-storage.ts # 마이그레이션 중 임시 유지
|
|
87
|
-
│ │
|
|
88
|
-
│ ├── actions/ # Server Actions
|
|
89
|
-
│ │ ├── auth/
|
|
90
|
-
│ │ │ ├── login.ts
|
|
91
|
-
│ │ │ ├── signup.ts
|
|
92
|
-
│ │ │ └── logout.ts
|
|
93
|
-
│ │ └── database/
|
|
94
|
-
│ │ ├── users.ts
|
|
95
|
-
│ │ └── posts.ts
|
|
96
|
-
│ │
|
|
97
|
-
│ ├── types/ # 타입 정의
|
|
98
|
-
│ │ ├── auth.ts
|
|
99
|
-
│ │ ├── database.ts
|
|
100
|
-
│ │ └── api.ts
|
|
101
|
-
│ │
|
|
102
|
-
│ └── middleware.ts # Next.js 미들웨어
|
|
103
|
-
│
|
|
104
|
-
├── migrations/ # Drizzle 마이그레이션
|
|
105
|
-
├── .env.local # 환경 변수
|
|
106
|
-
├── .env.example # 환경 변수 예시
|
|
107
|
-
├── drizzle.config.ts # Drizzle 설정
|
|
108
|
-
├── supabase/ # Supabase 설정 (선택사항)
|
|
109
|
-
│ ├── config.toml
|
|
110
|
-
│ └── seed.sql
|
|
111
|
-
└── package.json
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## 🚀 마이그레이션 단계별 가이드
|
|
117
|
-
|
|
118
|
-
### Phase 1: 환경 설정 및 기본 구조 (1-2일)
|
|
119
|
-
|
|
120
|
-
#### 1.1 패키지 설치
|
|
121
|
-
```bash
|
|
122
|
-
# Supabase 관련
|
|
123
|
-
npm install @supabase/supabase-js @supabase/ssr
|
|
124
|
-
|
|
125
|
-
# DrizzleORM 관련
|
|
126
|
-
npm install drizzle-orm drizzle-kit
|
|
127
|
-
npm install -D @types/pg pg
|
|
128
|
-
|
|
129
|
-
# 인증 관련
|
|
130
|
-
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
|
|
131
|
-
|
|
132
|
-
# 기타 유틸리티
|
|
133
|
-
npm install zod react-hook-form @hookform/resolvers
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
#### 1.2 환경 변수 설정
|
|
137
|
-
```bash
|
|
138
|
-
# .env.local
|
|
139
|
-
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
|
|
140
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
|
141
|
-
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
|
|
142
|
-
DATABASE_URL=your_database_url
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
#### 1.3 기본 설정 파일 생성
|
|
146
|
-
```typescript
|
|
147
|
-
// drizzle.config.ts
|
|
148
|
-
import type { Config } from 'drizzle-kit'
|
|
149
|
-
|
|
150
|
-
export default {
|
|
151
|
-
schema: './src/lib/drizzle/schema.ts',
|
|
152
|
-
out: './migrations',
|
|
153
|
-
driver: 'pg',
|
|
154
|
-
dbCredentials: {
|
|
155
|
-
connectionString: process.env.DATABASE_URL!,
|
|
156
|
-
},
|
|
157
|
-
} satisfies Config
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Phase 2: 데이터 모델링 및 스키마 설계 (2-3일)
|
|
161
|
-
|
|
162
|
-
#### 2.1 기존 로컬 데이터 분석
|
|
163
|
-
```typescript
|
|
164
|
-
// 기존 로컬 스토리지 데이터 구조 분석
|
|
165
|
-
const analyzeLocalData = () => {
|
|
166
|
-
const keys = Object.keys(localStorage)
|
|
167
|
-
keys.forEach(key => {
|
|
168
|
-
const data = JSON.parse(localStorage.getItem(key) || '{}')
|
|
169
|
-
console.log(`Key: ${key}`, data)
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
#### 2.2 DrizzleORM 스키마 생성
|
|
175
|
-
```typescript
|
|
176
|
-
// src/lib/drizzle/schema.ts
|
|
177
|
-
import { pgTable, uuid, varchar, timestamp, text, boolean } from 'drizzle-orm/pg-core'
|
|
178
|
-
|
|
179
|
-
export const users = pgTable('users', {
|
|
180
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
181
|
-
email: varchar('email', { length: 255 }).notNull().unique(),
|
|
182
|
-
name: varchar('name', { length: 255 }),
|
|
183
|
-
avatar_url: text('avatar_url'),
|
|
184
|
-
created_at: timestamp('created_at').defaultNow(),
|
|
185
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
export const posts = pgTable('posts', {
|
|
189
|
-
id: uuid('id').primaryKey().defaultRandom(),
|
|
190
|
-
title: varchar('title', { length: 255 }).notNull(),
|
|
191
|
-
content: text('content'),
|
|
192
|
-
author_id: uuid('author_id').references(() => users.id),
|
|
193
|
-
published: boolean('published').default(false),
|
|
194
|
-
created_at: timestamp('created_at').defaultNow(),
|
|
195
|
-
updated_at: timestamp('updated_at').defaultNow(),
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
// 타입 추출
|
|
199
|
-
export type User = typeof users.$inferSelect
|
|
200
|
-
export type NewUser = typeof users.$inferInsert
|
|
201
|
-
export type Post = typeof posts.$inferSelect
|
|
202
|
-
export type NewPost = typeof posts.$inferInsert
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
#### 2.3 Supabase 설정
|
|
206
|
-
```typescript
|
|
207
|
-
// src/lib/supabase/client.ts
|
|
208
|
-
import { createBrowserClient } from '@supabase/ssr'
|
|
209
|
-
|
|
210
|
-
export const createClient = () =>
|
|
211
|
-
createBrowserClient(
|
|
212
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
213
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
// src/lib/supabase/server.ts
|
|
217
|
-
import { createServerClient } from '@supabase/ssr'
|
|
218
|
-
import { cookies } from 'next/headers'
|
|
219
|
-
|
|
220
|
-
export const createClient = () => {
|
|
221
|
-
const cookieStore = cookies()
|
|
222
|
-
|
|
223
|
-
return createServerClient(
|
|
224
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
225
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
226
|
-
{
|
|
227
|
-
cookies: {
|
|
228
|
-
getAll() {
|
|
229
|
-
return cookieStore.getAll()
|
|
230
|
-
},
|
|
231
|
-
setAll(cookiesToSet) {
|
|
232
|
-
try {
|
|
233
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
234
|
-
cookieStore.set(name, value, options)
|
|
235
|
-
)
|
|
236
|
-
} catch {
|
|
237
|
-
// 서버 컴포넌트에서는 쿠키를 설정할 수 없음
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
}
|
|
242
|
-
)
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Phase 3: 인증 시스템 구현 (3-4일)
|
|
247
|
-
|
|
248
|
-
#### 3.1 Supabase Auth 설정
|
|
249
|
-
```typescript
|
|
250
|
-
// src/lib/auth/config.ts
|
|
251
|
-
export const authConfig = {
|
|
252
|
-
providers: ['google', 'github'],
|
|
253
|
-
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
|
254
|
-
appearance: {
|
|
255
|
-
theme: 'default',
|
|
256
|
-
variables: {
|
|
257
|
-
default: {
|
|
258
|
-
colors: {
|
|
259
|
-
brand: '#404040',
|
|
260
|
-
brandAccent: '#52525b',
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
#### 3.2 인증 컴포넌트 구현
|
|
269
|
-
```typescript
|
|
270
|
-
// src/components/auth/login-form.tsx
|
|
271
|
-
'use client'
|
|
272
|
-
|
|
273
|
-
import { createClient } from '@/lib/supabase/client'
|
|
274
|
-
import { useRouter } from 'next/navigation'
|
|
275
|
-
|
|
276
|
-
export function LoginForm() {
|
|
277
|
-
const supabase = createClient()
|
|
278
|
-
const router = useRouter()
|
|
279
|
-
|
|
280
|
-
const handleLogin = async (formData: FormData) => {
|
|
281
|
-
const email = formData.get('email') as string
|
|
282
|
-
const password = formData.get('password') as string
|
|
283
|
-
|
|
284
|
-
const { error } = await supabase.auth.signInWithPassword({
|
|
285
|
-
email,
|
|
286
|
-
password,
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
if (!error) {
|
|
290
|
-
router.push('/dashboard')
|
|
291
|
-
router.refresh()
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<form action={handleLogin}>
|
|
297
|
-
{/* 기존 UI 컴포넌트 재사용 */}
|
|
298
|
-
</form>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
#### 3.3 인증 미들웨어
|
|
304
|
-
```typescript
|
|
305
|
-
// src/middleware.ts
|
|
306
|
-
import { createServerClient } from '@supabase/ssr'
|
|
307
|
-
import { NextResponse } from 'next/server'
|
|
308
|
-
import type { NextRequest } from 'next/server'
|
|
309
|
-
|
|
310
|
-
export async function middleware(request: NextRequest) {
|
|
311
|
-
let response = NextResponse.next({
|
|
312
|
-
request: {
|
|
313
|
-
headers: request.headers,
|
|
314
|
-
},
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
const supabase = createServerClient(
|
|
318
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
319
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
320
|
-
{
|
|
321
|
-
cookies: {
|
|
322
|
-
getAll() {
|
|
323
|
-
return request.cookies.getAll()
|
|
324
|
-
},
|
|
325
|
-
setAll(cookiesToSet) {
|
|
326
|
-
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
|
|
327
|
-
response = NextResponse.next({
|
|
328
|
-
request,
|
|
329
|
-
})
|
|
330
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
331
|
-
response.cookies.set(name, value, options)
|
|
332
|
-
)
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
}
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
const {
|
|
339
|
-
data: { session },
|
|
340
|
-
} = await supabase.auth.getSession()
|
|
341
|
-
|
|
342
|
-
// 인증이 필요한 경로 보호
|
|
343
|
-
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
344
|
-
return NextResponse.redirect(new URL('/login', request.url))
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return response
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export const config = {
|
|
351
|
-
matcher: [
|
|
352
|
-
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
|
|
353
|
-
],
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Phase 4: 데이터 마이그레이션 (2-3일)
|
|
358
|
-
|
|
359
|
-
#### 4.1 기존 로컬 데이터 추출
|
|
360
|
-
```typescript
|
|
361
|
-
// src/lib/migration/extract-local-data.ts
|
|
362
|
-
export const extractLocalData = () => {
|
|
363
|
-
const migrationData = {
|
|
364
|
-
users: [],
|
|
365
|
-
posts: [],
|
|
366
|
-
// 기타 데이터 타입들
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// localStorage에서 데이터 추출
|
|
370
|
-
const userData = localStorage.getItem('users')
|
|
371
|
-
if (userData) {
|
|
372
|
-
migrationData.users = JSON.parse(userData)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return migrationData
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
#### 4.2 데이터 변환 및 검증
|
|
380
|
-
```typescript
|
|
381
|
-
// src/lib/migration/transform-data.ts
|
|
382
|
-
import { z } from 'zod'
|
|
383
|
-
|
|
384
|
-
const UserSchema = z.object({
|
|
385
|
-
id: z.string().uuid(),
|
|
386
|
-
email: z.string().email(),
|
|
387
|
-
name: z.string().min(1),
|
|
388
|
-
created_at: z.string().datetime(),
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
export const transformAndValidateData = (localData: any) => {
|
|
392
|
-
const transformedData = {
|
|
393
|
-
users: localData.users.map((user: any) => ({
|
|
394
|
-
...user,
|
|
395
|
-
id: user.id || crypto.randomUUID(),
|
|
396
|
-
created_at: user.created_at || new Date().toISOString(),
|
|
397
|
-
})),
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 데이터 검증
|
|
401
|
-
transformedData.users.forEach(user => {
|
|
402
|
-
UserSchema.parse(user)
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
return transformedData
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
#### 4.3 Supabase로 데이터 마이그레이션
|
|
410
|
-
```typescript
|
|
411
|
-
// src/lib/migration/migrate-to-supabase.ts
|
|
412
|
-
import { createClient } from '@/lib/supabase/server'
|
|
413
|
-
import { db } from '@/lib/drizzle/db'
|
|
414
|
-
import { users, posts } from '@/lib/drizzle/schema'
|
|
415
|
-
|
|
416
|
-
export const migrateToSupabase = async (transformedData: any) => {
|
|
417
|
-
const supabase = createClient()
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
// 사용자 데이터 마이그레이션
|
|
421
|
-
for (const user of transformedData.users) {
|
|
422
|
-
await db.insert(users).values(user)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// 게시글 데이터 마이그레이션
|
|
426
|
-
for (const post of transformedData.posts) {
|
|
427
|
-
await db.insert(posts).values(post)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
console.log('✅ 데이터 마이그레이션 완료')
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error('❌ 마이그레이션 실패:', error)
|
|
433
|
-
throw error
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Phase 5: Server Actions 업데이트 (2-3일)
|
|
439
|
-
|
|
440
|
-
#### 5.1 기존 로컬 스토리지 로직을 DB 쿼리로 변경
|
|
441
|
-
```typescript
|
|
442
|
-
// src/actions/database/users.ts
|
|
443
|
-
'use server'
|
|
444
|
-
|
|
445
|
-
import { db } from '@/lib/drizzle/db'
|
|
446
|
-
import { users } from '@/lib/drizzle/schema'
|
|
447
|
-
import { eq } from 'drizzle-orm'
|
|
448
|
-
import { revalidatePath } from 'next/cache'
|
|
449
|
-
|
|
450
|
-
export async function createUser(formData: FormData) {
|
|
451
|
-
try {
|
|
452
|
-
const name = formData.get('name') as string
|
|
453
|
-
const email = formData.get('email') as string
|
|
454
|
-
|
|
455
|
-
const [newUser] = await db.insert(users).values({
|
|
456
|
-
name,
|
|
457
|
-
email,
|
|
458
|
-
}).returning()
|
|
459
|
-
|
|
460
|
-
revalidatePath('/users')
|
|
461
|
-
return { success: true, data: newUser }
|
|
462
|
-
} catch (error) {
|
|
463
|
-
return { success: false, error: 'Failed to create user' }
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export async function getUsers() {
|
|
468
|
-
try {
|
|
469
|
-
const allUsers = await db.select().from(users)
|
|
470
|
-
return { success: true, data: allUsers }
|
|
471
|
-
} catch (error) {
|
|
472
|
-
return { success: false, error: 'Failed to fetch users' }
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
### Phase 6: 기존 문서 업데이트 (1일)
|
|
478
|
-
|
|
479
|
-
#### 6.1 docs/PRD.md 업데이트
|
|
480
|
-
```markdown
|
|
481
|
-
# 추가할 내용
|
|
482
|
-
|
|
483
|
-
## 기술 스택 업데이트
|
|
484
|
-
- **Database**: Supabase PostgreSQL
|
|
485
|
-
- **ORM**: DrizzleORM
|
|
486
|
-
- **Authentication**: Supabase Auth
|
|
487
|
-
- **Storage**: Supabase Storage (이미지/파일)
|
|
488
|
-
|
|
489
|
-
## 새로 추가된 기능
|
|
490
|
-
- 🔐 **사용자 인증**: 이메일/소셜 로그인
|
|
491
|
-
- 👤 **사용자 관리**: 프로필, 권한 관리
|
|
492
|
-
- 📊 **실시간 기능**: Supabase Realtime 구독
|
|
493
|
-
- 📁 **파일 업로드**: 프로필 이미지, 첨부파일
|
|
494
|
-
- 🔒 **보안**: Row Level Security (RLS)
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
#### 6.2 docs/TODO.md 업데이트
|
|
498
|
-
```markdown
|
|
499
|
-
# Supabase + DrizzleORM 마이그레이션 TODO
|
|
500
|
-
|
|
501
|
-
## P0 (Critical) - Week 9-10
|
|
502
|
-
- [ ] Supabase 프로젝트 설정 및 환경 변수 구성
|
|
503
|
-
- [ ] DrizzleORM 스키마 정의 및 마이그레이션 실행
|
|
504
|
-
- [ ] 기본 인증 시스템 구현 (이메일/비밀번호)
|
|
505
|
-
- [ ] 기존 로컬 데이터 → Supabase 마이그레이션
|
|
506
|
-
- [ ] 핵심 Server Actions DB 쿼리로 변경
|
|
507
|
-
|
|
508
|
-
## P1 (High) - Week 11-12
|
|
509
|
-
- [ ] 소셜 로그인 구현 (Google, GitHub)
|
|
510
|
-
- [ ] 파일 업로드 기능 (Supabase Storage)
|
|
511
|
-
- [ ] 실시간 기능 구현 (Realtime subscriptions)
|
|
512
|
-
- [ ] Row Level Security (RLS) 정책 설정
|
|
513
|
-
- [ ] 사용자 권한 관리 시스템
|
|
514
|
-
|
|
515
|
-
## P2 (Medium) - Week 13-14
|
|
516
|
-
- [ ] 데이터베이스 최적화 (인덱스, 성능)
|
|
517
|
-
- [ ] 백업 및 복구 시스템
|
|
518
|
-
- [ ] 모니터링 및 로깅 시스템
|
|
519
|
-
- [ ] API Rate Limiting
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
#### 6.3 docs/business-logic.md 업데이트
|
|
523
|
-
```markdown
|
|
524
|
-
# 추가할 내용
|
|
525
|
-
|
|
526
|
-
## Database Design
|
|
527
|
-
|
|
528
|
-
### Core Tables
|
|
529
|
-
- **users**: 사용자 기본 정보
|
|
530
|
-
- **posts**: 게시글 데이터
|
|
531
|
-
- **user_profiles**: 확장 프로필 정보
|
|
532
|
-
- **sessions**: 활성 세션 관리
|
|
533
|
-
|
|
534
|
-
### Relationships
|
|
535
|
-
- users (1) → posts (N)
|
|
536
|
-
- users (1) → user_profiles (1)
|
|
537
|
-
|
|
538
|
-
## Authentication Flow
|
|
539
|
-
|
|
540
|
-
### 이메일 로그인
|
|
541
|
-
1. 사용자 입력 검증 (Zod)
|
|
542
|
-
2. Supabase Auth 호출
|
|
543
|
-
3. 세션 생성 및 쿠키 설정
|
|
544
|
-
4. 대시보드 리다이렉트
|
|
545
|
-
|
|
546
|
-
### 소셜 로그인
|
|
547
|
-
1. OAuth Provider 선택
|
|
548
|
-
2. Supabase Auth Provider 호출
|
|
549
|
-
3. 콜백 처리 및 사용자 정보 저장
|
|
550
|
-
4. 프로필 완성 페이지 (필요시)
|
|
551
|
-
|
|
552
|
-
## Data Access Patterns
|
|
553
|
-
|
|
554
|
-
### Server Components
|
|
555
|
-
```typescript
|
|
556
|
-
// 서버에서 직접 DB 쿼리
|
|
557
|
-
const users = await db.select().from(users)
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
### Client Components
|
|
561
|
-
```typescript
|
|
562
|
-
// Server Actions 호출
|
|
563
|
-
const { data } = await getUsers()
|
|
564
|
-
```
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# For E2E test
|
|
2
|
-
|
|
3
|
-
Installs and configures Playwright MCP server for Claude Code with browser automation capabilities.
|
|
4
|
-
|
|
5
|
-
## What this command does
|
|
6
|
-
|
|
7
|
-
1. **Installs Playwright MCP Server**: Adds browser automation capabilities to Claude Code
|
|
8
|
-
2. **Configures MCP Server**: Sets up the connection for immediate use
|
|
9
|
-
3. **Verifies Installation**: Confirms the setup is working correctly
|
|
10
|
-
|
|
11
|
-
## Installation Steps
|
|
12
|
-
|
|
13
|
-
### Step 1: Install Playwright MCP Server Package
|
|
14
|
-
```bash
|
|
15
|
-
npm install -g @executeautomation/playwright-mcp-server
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Step 2: Add MCP Server to Claude Code
|
|
19
|
-
Run the following command to add Playwright MCP server to your project:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
claude mcp add playwright-server -s project -- npx @executeautomation/playwright-mcp-server
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Step 3: Alternative Installation Methods
|
|
26
|
-
|
|
27
|
-
**Option A: Microsoft's Official Playwright MCP**
|
|
28
|
-
```bash
|
|
29
|
-
claude mcp add playwright-ms -s project -- npx @playwright/mcp@latest
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Option B: Using Smithery (Automatic Configuration)**
|
|
33
|
-
```bash
|
|
34
|
-
npx @smithery/cli install @executeautomation/playwright-mcp-server --client claude
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**Option C: Manual Configuration**
|
|
38
|
-
If you prefer manual setup, add this to your `.mcp.json` file:
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"playwright": {
|
|
42
|
-
"command": "npx",
|
|
43
|
-
"args": ["-y", "@executeautomation/playwright-mcp-server"]
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Step 4: Verify Installation
|
|
49
|
-
After installation, restart Claude Code and check if MCP tools are available:
|
|
50
|
-
```bash
|
|
51
|
-
/mcp
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
You should see Playwright-related tools in the list.
|
|
55
|
-
|
|
56
|
-
## Available Playwright MCP Tools
|
|
57
|
-
|
|
58
|
-
Once installed, you'll have access to:
|
|
59
|
-
- **Browser Navigation**: Navigate to URLs, handle page interactions
|
|
60
|
-
- **Element Interaction**: Click, type, select elements
|
|
61
|
-
- **Screenshot Capture**: Take screenshots for verification
|
|
62
|
-
- **Accessibility Tree**: Access structured page data without screenshots
|
|
63
|
-
- **JavaScript Execution**: Run custom scripts in browser context
|
|
64
|
-
- **API Testing**: Validate endpoints and responses
|
|
65
|
-
|
|
66
|
-
## Usage Examples
|
|
67
|
-
|
|
68
|
-
After installation, you can use commands like:
|
|
69
|
-
- "Navigate to https://example.com and take a screenshot"
|
|
70
|
-
- "Fill the login form and submit"
|
|
71
|
-
- "Take a screenshot of the current page"
|
|
72
|
-
- "Execute this JavaScript on the page"
|
|
73
|
-
|
|
74
|
-
## Browser Support
|
|
75
|
-
|
|
76
|
-
Playwright MCP supports:
|
|
77
|
-
- **Chromium** (default)
|
|
78
|
-
- **Firefox**
|
|
79
|
-
- **WebKit** (Safari)
|
|
80
|
-
|
|
81
|
-
## Configuration Options
|
|
82
|
-
|
|
83
|
-
**Headless Mode** (default):
|
|
84
|
-
```bash
|
|
85
|
-
claude mcp add playwright --browser-headless true
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Headed Mode** (visible browser):
|
|
89
|
-
```bash
|
|
90
|
-
claude mcp add playwright --browser-headless false
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Persistent Profile**:
|
|
94
|
-
```bash
|
|
95
|
-
claude mcp add playwright --user-data-dir ./browser-profile
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Troubleshooting
|
|
99
|
-
|
|
100
|
-
If installation fails:
|
|
101
|
-
1. Ensure Node.js is installed (`node --version`)
|
|
102
|
-
2. Check if you have proper permissions
|
|
103
|
-
3. Try installing without global flag: `npm install @executeautomation/playwright-mcp-server`
|
|
104
|
-
4. Use `claude mcp remove playwright` and reinstall
|
|
105
|
-
|
|
106
|
-
## Security Note
|
|
107
|
-
|
|
108
|
-
Playwright MCP can control browsers and access websites. Use with trusted sources and be mindful of:
|
|
109
|
-
- Credential exposure
|
|
110
|
-
- Automated interactions on production sites
|
|
111
|
-
- Rate limiting and terms of service
|
|
112
|
-
|
|
113
|
-
---
|