๐ŸŽ“ ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ - LMS ํ’€์Šคํƒ ๊ตฌํ˜„ with ๋™์˜์ƒ ์ŠคํŠธ๋ฆฌ๋ฐ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
๊ณ ๊ธ‰ PHP Laravel AI: Claude

๐ŸŽ“ ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ - LMS ํ’€์Šคํƒ ๊ตฌํ˜„ with ๋™์˜์ƒ ์ŠคํŠธ๋ฆฌ๋ฐ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… online-course-lms-platform
์กฐํšŒ์ˆ˜ 98 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐Ÿ“š ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์‹ค๋ฌด ์ˆ˜์ค€์˜ ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ(LMS)์„ Laravel 13.x + Blade๋กœ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ•์‚ฌ๋Š” ๊ฐ•์˜๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ˆ˜๊ฐ•์ƒ์€ ์ฝ”์Šค๋ฅผ ๊ตฌ๋งคํ•˜์—ฌ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์˜์ƒ ์ŠคํŠธ๋ฆฌ๋ฐ, ์ง„๋„์œจ ์ถ”์ , ์ˆ˜๋ฃŒ์ฆ ๋ฐœ๊ธ‰, ๊ฒฐ์ œ ์—ฐ๋™ ๋“ฑ ์‹ค์ œ ์„œ๋น„์Šค์— ํ•„์š”ํ•œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ๋ชจ๋‘ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

โœจ ํ•„์ˆ˜ ๊ธฐ๋Šฅ

๐Ÿ‘จโ€๐Ÿซ ๊ฐ•์‚ฌ ๊ธฐ๋Šฅ

  • ๊ฐ•์˜ ์ฝ”์Šค ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ (์ œ๋ชฉ, ์„ค๋ช…, ๊ฐ€๊ฒฉ, ์ธ๋„ค์ผ)
  • ์ฑ•ํ„ฐ๋ณ„ ๋ ˆ์Šจ ๊ตฌ์„ฑ (๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ์œผ๋กœ ์ˆœ์„œ ๋ณ€๊ฒฝ)
  • ๋™์˜์ƒ ์—…๋กœ๋“œ ๋ฐ ์ŠคํŠธ๋ฆฌ๋ฐ (Laravel Media Library + HLS)
  • ํ•™์Šต ์ž๋ฃŒ ์ฒจ๋ถ€ (PDF, ZIP ๋“ฑ)
  • ์ˆ˜๊ฐ•์ƒ ์ง„๋„์œจ ๋ฐ ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ
  • ์ˆ˜์ต ๋ฆฌํฌํŠธ (์›”๋ณ„ ๋งค์ถœ, ํ™˜๋ถˆ ๋‚ด์—ญ)

๐ŸŽ“ ์ˆ˜๊ฐ•์ƒ ๊ธฐ๋Šฅ

  • ๊ฐ•์˜ ๊ฒ€์ƒ‰ ๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํƒ์ƒ‰
  • ๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ (์ปค๋ฆฌํ˜๋Ÿผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ์ƒ˜ํ”Œ ์˜์ƒ)
  • ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฐ ์œ„์‹œ๋ฆฌ์ŠคํŠธ
  • ๊ฒฐ์ œ ์—ฐ๋™ (Stripe/Toss Payments)
  • ํ•™์Šต ํŽ˜์ด์ง€ (๋™์˜์ƒ ํ”Œ๋ ˆ์ด์–ด, ๋ฐฐ์† ์กฐ์ ˆ, ๋ถ๋งˆํฌ)
  • ์ง„๋„์œจ ์ž๋™ ์ถ”์  (์‹œ์ฒญ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜)
  • ์ˆ˜๋ฃŒ์ฆ ์ž๋™ ๋ฐœ๊ธ‰ (100% ์™„๊ฐ• ์‹œ PDF ์ƒ์„ฑ)
  • Q&A ๊ฒŒ์‹œํŒ (๊ฐ•์˜๋ณ„ ์งˆ๋ฌธ/๋‹ต๋ณ€)

๐Ÿ” ์ธ์ฆ ๋ฐ ๊ถŒํ•œ

  • ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ (Laravel Fortify)
  • ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด (๊ฐ•์‚ฌ/์ˆ˜๊ฐ•์ƒ/๊ด€๋ฆฌ์ž)
  • ์ด๋ฉ”์ผ ์ธ์ฆ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •
  • ์†Œ์…œ ๋กœ๊ทธ์ธ (Google, GitHub)

๐Ÿ’ณ ๊ฒฐ์ œ ๋ฐ ํ™˜๋ถˆ

  • Stripe ๊ฒฐ์ œ ์—ฐ๋™ (์นด๋“œ, ๊ฐ„ํŽธ๊ฒฐ์ œ)
  • ์ฃผ๋ฌธ ๋‚ด์—ญ ๋ฐ ์˜์ˆ˜์ฆ ๋ฐœ๊ธ‰
  • ํ™˜๋ถˆ ์ •์ฑ… (7์ผ ์ด๋‚ด ์ „์•ก ํ™˜๋ถˆ)
  • ์ฟ ํฐ ๋ฐ ํ• ์ธ ์ฝ”๋“œ ์‹œ์Šคํ…œ

๐Ÿ“Š ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

  • Redis ์บ์‹ฑ (๊ฐ•์˜ ๋ชฉ๋ก, ์ธ๊ธฐ ์ฝ”์Šค)
  • Laravel Queue๋กœ ๋™์˜์ƒ ์ธ์ฝ”๋”ฉ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ
  • ์•Œ๋ฆผ ์‹œ์Šคํ…œ (์ƒˆ ๋ ˆ์Šจ ์—…๋กœ๋“œ, ์งˆ๋ฌธ ๋‹ต๋ณ€ ์•Œ๋ฆผ)
  • ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™” (Laravel Scout + Meilisearch)
  • ๋‹ค๊ตญ์–ด ์ง€์› (ํ•œ๊ตญ์–ด/์˜์–ด)

๐ŸŽ ๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

  • ๊ฐ•์˜ ๋ฆฌ๋ทฐ ๋ฐ ๋ณ„์  ์‹œ์Šคํ…œ
  • ์ˆ˜๊ฐ•์ƒ ํ•™์Šต ํ†ต๊ณ„ (์ผ์ผ ํ•™์Šต ์‹œ๊ฐ„, ์™„๊ฐ•๋ฅ )
  • ๊ฐ•์‚ฌ ์ธ์ฆ ๋ฑƒ์ง€ (๋ฒ ์ŠคํŠธ ๊ฐ•์‚ฌ)
  • ๋ผ์ด๋ธŒ Q&A ์„ธ์…˜ ์˜ˆ์•ฝ (Laravel Reverb)
  • AI ๊ธฐ๋ฐ˜ ๊ฐ•์˜ ์ถ”์ฒœ (OpenAI API)
  • ์ˆ˜๊ฐ•์ƒ ๋…ธํŠธ ์ž‘์„ฑ (ํƒ€์ž„์Šคํƒฌํ”„๋ณ„ ๋ฉ”๋ชจ)
  • ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (Tailwind CSS)

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • Laravel ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„
  • ํŒŒ์ผ ์—…๋กœ๋“œ ๋ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์ฒ˜๋ฆฌ
  • ๊ฒฐ์ œ ์—ฐ๋™ ๋ฐ ๋ณด์•ˆ ์ฒ˜๋ฆฌ
  • ํ/์žก์„ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์ž‘์—…
  • ์บ์‹ฑ ์ „๋žต ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ๋ณต์žกํ•œ DB ๊ด€๊ณ„ ์„ค๊ณ„ (๋‹ค๋Œ€๋‹ค, ํด๋ฆฌ๋ชจํ”ฝ)
  • ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ (PHPUnit)

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ
  • โ€ข Blade ํ…œํ”Œ๋ฆฟ ์—”์ง„ ์‚ฌ์šฉ (React/Vue ๊ธˆ์ง€)
  • โ€ข Tailwind CSS๋กœ UI ๊ตฌ์„ฑ
  • โ€ข SQLite (๊ฐœ๋ฐœ) / PostgreSQL (ํ”„๋กœ๋•์…˜) ์‚ฌ์šฉ
  • โ€ข Laravel Fortify๋กœ ์ธ์ฆ ๊ตฌํ˜„
  • โ€ข Stripe API ์—ฐ๋™ (ํ…Œ์ŠคํŠธ ๋ชจ๋“œ)
  • โ€ข Laravel Media Library๋กœ ํŒŒ์ผ ๊ด€๋ฆฌ
  • โ€ข Redis ์บ์‹ฑ ํ•„์ˆ˜
  • โ€ข Laravel Queue๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ฒ˜๋ฆฌ
  • โ€ข PHPUnit ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ 70% ์ด์ƒ
  • โ€ข ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ ์šฐ์„ )
  • โ€ข ์ ‘๊ทผ์„ฑ ์ค€์ˆ˜ (ARIA ๋ ˆ์ด๋ธ”)
  • โ€ข ๋ณด์•ˆ: CSRF ํ† ํฐ, XSS ๋ฐฉ์ง€, SQL Injection ๋ฐฉ์ง€

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
mkdir online-course-lms-platform && cd online-course-lms-platform

์ด ํด๋”์—์„œ Laravel 13.x ๊ธฐ๋ฐ˜ ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ
1. Laravel 13.x ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
2. ๊ธฐ๋ณธ ์„ค์ • ํŒŒ์ผ ๊ตฌ์„ฑ:
   - .env ํŒŒ์ผ์— SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •
   - APP_NAME์„ 'CourseHub'๋กœ ๋ณ€๊ฒฝ
   - Redis ์บ์‹œ ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •
   - Queue ๋“œ๋ผ์ด๋ฒ„๋ฅผ 'database'๋กœ ์„ค์ •
3. ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ์„ค์น˜:
   - laravel/fortify (์ธ์ฆ)
   - spatie/laravel-medialibrary (ํŒŒ์ผ ๊ด€๋ฆฌ)
   - spatie/laravel-permission (๊ถŒํ•œ ๊ด€๋ฆฌ)
   - stripe/stripe-php (๊ฒฐ์ œ)
   - barryvdh/laravel-dompdf (PDF ์ƒ์„ฑ)
4. Tailwind CSS ์„ค์ • (Laravel Mix ๋˜๋Š” Vite)
5. ๊ธฐ๋ณธ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์ƒ์„ฑ:
   - app/Models (User, Course, Lesson, Enrollment, Payment ๋“ฑ)
   - app/Http/Controllers (Auth, Course, Lesson, Payment ๋“ฑ)
   - resources/views/layouts (app.blade.php, guest.blade.php)
   - resources/views/components (Blade ์ปดํฌ๋„ŒํŠธ)
6. Git ์ดˆ๊ธฐํ™” ๋ฐ .gitignore ์„ค์ •

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํ›„ README.md์— ์„ค์น˜ ๊ฐ€์ด๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ํ•„์ˆ˜ ํŒจํ‚ค์ง€๊ฐ€ ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. Tailwind CSS๊ฐ€ ์„ค์ •๋˜๊ณ , ๊ธฐ๋ณธ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ์ค€๋น„๋ฉ๋‹ˆ๋‹ค. .env ํŒŒ์ผ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ์บ์‹œ ์„ค์ •์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

2
Step 2
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.

## ํ•„์š”ํ•œ ํ…Œ์ด๋ธ” ๋ฐ ๊ด€๊ณ„

### users ํ…Œ์ด๋ธ”
- id, name, email, password, email_verified_at
- role (enum: 'student', 'instructor', 'admin')
- avatar (nullable)
- bio (text, nullable)
- timestamps

### courses ํ…Œ์ด๋ธ”
- id, instructor_id (foreign key to users)
- title, slug (unique)
- description (text)
- price (decimal)
- thumbnail (nullable)
- category (enum: 'programming', 'design', 'business', 'marketing', 'etc')
- level (enum: 'beginner', 'intermediate', 'advanced')
- is_published (boolean, default false)
- timestamps, soft deletes

### lessons ํ…Œ์ด๋ธ”
- id, course_id (foreign key)
- chapter_id (nullable, foreign key to chapters)
- title, slug
- description (text, nullable)
- video_path (nullable)
- duration (integer, ์ดˆ ๋‹จ์œ„)
- order (integer)
- is_free (boolean, ์ƒ˜ํ”Œ ์˜์ƒ ์—ฌ๋ถ€)
- timestamps

### chapters ํ…Œ์ด๋ธ”
- id, course_id (foreign key)
- title
- order (integer)
- timestamps

### enrollments ํ…Œ์ด๋ธ” (์ˆ˜๊ฐ• ๋“ฑ๋ก)
- id, user_id, course_id
- enrolled_at (timestamp)
- completed_at (nullable timestamp)
- progress (decimal, 0-100)
- unique constraint on (user_id, course_id)
- timestamps

### lesson_progress ํ…Œ์ด๋ธ”
- id, enrollment_id, lesson_id
- watched_duration (integer, ์ดˆ ๋‹จ์œ„)
- is_completed (boolean)
- last_watched_at (timestamp)
- timestamps

### payments ํ…Œ์ด๋ธ”
- id, user_id, course_id
- stripe_payment_id (unique)
- amount (decimal)
- currency (default 'krw')
- status (enum: 'pending', 'completed', 'refunded')
- refunded_at (nullable timestamp)
- timestamps

### reviews ํ…Œ์ด๋ธ”
- id, user_id, course_id
- rating (integer, 1-5)
- comment (text, nullable)
- timestamps

### qna_posts ํ…Œ์ด๋ธ”
- id, user_id, course_id, lesson_id (nullable)
- title, content (text)
- is_answered (boolean, default false)
- timestamps

### qna_answers ํ…Œ์ด๋ธ”
- id, post_id (foreign key), user_id
- content (text)
- is_instructor_answer (boolean)
- timestamps

## ์ถ”๊ฐ€ ์š”๊ตฌ์‚ฌํ•ญ
1. ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด ์„ค์ • (onDelete cascade/restrict)
2. ์ธ๋ฑ์Šค ์ถ”๊ฐ€ (slug, instructor_id, course_id ๋“ฑ)
3. Eloquent ๋ชจ๋ธ ์ƒ์„ฑ ๋ฐ ๊ด€๊ณ„ ์ •์˜:
   - User hasMany Courses (as instructor)
   - User belongsToMany Courses through Enrollments
   - Course hasMany Lessons, Reviews, Enrollments
   - Course belongsTo User (instructor)
   - Lesson belongsTo Course, Chapter
   - Enrollment belongsTo User, Course
   - Enrollment hasMany LessonProgress
4. ๋ชจ๋ธ์— fillable/guarded ์†์„ฑ ์ •์˜
5. ๋ชจ๋ธ์— casts ์ •์˜ (JSON, datetime ๋“ฑ)

php artisan migrate ์‹คํ–‰ ์ „ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ๊ฒ€ํ† ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ชจ๋“  ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๊ณ , Eloquent ๋ชจ๋ธ์ด ๊ด€๊ณ„์™€ ํ•จ๊ป˜ ์ •์˜๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๊ฐ€ ๋…ผ๋ฆฌ์ ์œผ๋กœ ์„ค๊ณ„๋˜์–ด ๊ฐ•์˜ ํ”Œ๋žซํผ์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

3
Step 3
์ธ์ฆ ์‹œ์Šคํ…œ๊ณผ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด(RBAC)๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. Laravel Fortify ์„ค์ •
- Fortify ์„ค์น˜ ๋ฐ ์„ค์ •
- ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๋ผ์šฐํŠธ ํ™œ์„ฑํ™”
- ์ด๋ฉ”์ผ ์ธ์ฆ ํ™œ์„ฑํ™”
- ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ํ™œ์„ฑํ™”
- 2๋‹จ๊ณ„ ์ธ์ฆ (์„ ํƒ ์‚ฌํ•ญ)

### 2. Spatie Permission ์„ค์ •
- Role ์ƒ์„ฑ: 'student', 'instructor', 'admin'
- Permission ์ •์˜:
  - 'create-course', 'edit-course', 'delete-course'
  - 'view-analytics', 'manage-users'
  - 'refund-payment'
- Seeder ์ƒ์„ฑ ๋ฐ ๊ธฐ๋ณธ ์—ญํ• /๊ถŒํ•œ ํ• ๋‹น

### 3. ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด
- EnsureUserIsInstructor ๋ฏธ๋“ค์›จ์–ด ์ƒ์„ฑ
- EnsureUserIsStudent ๋ฏธ๋“ค์›จ์–ด ์ƒ์„ฑ
- ๋ผ์šฐํŠธ์— ๋ฏธ๋“ค์›จ์–ด ์ ์šฉ

### 4. ์ธ์ฆ ๋ทฐ (Blade)
- resources/views/auth/login.blade.php (Tailwind CSS)
- resources/views/auth/register.blade.php
  - ํšŒ์›๊ฐ€์ž… ์‹œ ์—ญํ•  ์„ ํƒ (๊ฐ•์‚ฌ/์ˆ˜๊ฐ•์ƒ)
- resources/views/auth/forgot-password.blade.php
- resources/views/auth/verify-email.blade.php

### 5. ๋ ˆ์ด์•„์›ƒ ๋ฐ ๋„ค๋น„๊ฒŒ์ด์…˜
- resources/views/layouts/app.blade.php
  - ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ์— ๋”ฐ๋ฅธ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”
  - ์—ญํ• ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฉ”๋‰ด ํ‘œ์‹œ (๊ฐ•์‚ฌ๋Š” '๋‚ด ๊ฐ•์˜', ์ˆ˜๊ฐ•์ƒ์€ '๋‚ด ํ•™์Šต')
- resources/views/layouts/guest.blade.php (๋น„๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž์šฉ)

### 6. UserSeeder ์ƒ์„ฑ
- ํ…Œ์ŠคํŠธ์šฉ ๊ด€๋ฆฌ์ž ๊ณ„์ • (admin@example.com)
- ํ…Œ์ŠคํŠธ์šฉ ๊ฐ•์‚ฌ ๊ณ„์ • (instructor@example.com)
- ํ…Œ์ŠคํŠธ์šฉ ์ˆ˜๊ฐ•์ƒ ๊ณ„์ • (student@example.com)

### 7. ๋ผ์šฐํŠธ ์ •์˜
- routes/web.php์— ์ธ์ฆ ๊ด€๋ จ ๋ผ์šฐํŠธ ๊ทธ๋ฃน ์ƒ์„ฑ
- ๋ฏธ๋“ค์›จ์–ด๋กœ ๋ณดํ˜ธ๋œ ๋ผ์šฐํŠธ ์„ค์ •

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- Tailwind CSS๋กœ ๊น”๋”ํ•œ ํผ ๋””์ž์ธ
- ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ
- ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
- ์„ฑ๊ณต/์‹คํŒจ ํ”Œ๋ž˜์‹œ ๋ฉ”์‹œ์ง€ (session flash)

Seeder ์‹คํ–‰ ํ›„ ํ…Œ์ŠคํŠธ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•œ์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel Fortify ๊ธฐ๋ฐ˜ ์ธ์ฆ ์‹œ์Šคํ…œ์ด ๊ตฌ์ถ•๋˜๊ณ , Spatie Permission์œผ๋กœ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด๊ฐ€ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๊ฐ€ Tailwind CSS๋กœ ๋””์ž์ธ๋˜๊ณ , ์—ญํ• ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋„ค๋น„๊ฒŒ์ด์…˜์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
๊ฐ•์‚ฌ์šฉ ๊ฐ•์˜ ๊ด€๋ฆฌ CRUD ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. CourseController ์ƒ์„ฑ
- InstructorCourseController (๊ฐ•์‚ฌ ์ „์šฉ)
- ๋ฉ”์„œ๋“œ: index, create, store, edit, update, destroy
- Policy ์ƒ์„ฑ: CoursePolicy (๊ฐ•์‚ฌ๋งŒ ์ž์‹ ์˜ ๊ฐ•์˜ ์ˆ˜์ • ๊ฐ€๋Šฅ)

### 2. ๊ฐ•์˜ ๋ชฉ๋ก ํŽ˜์ด์ง€
- resources/views/instructor/courses/index.blade.php
- ๊ฐ•์‚ฌ๊ฐ€ ์ƒ์„ฑํ•œ ๋ชจ๋“  ๊ฐ•์˜ ํ‘œ์‹œ (ํ…Œ์ด๋ธ” ๋˜๋Š” ์นด๋“œ ํ˜•์‹)
- ๊ฐ ๊ฐ•์˜๋ณ„ ์ •๋ณด:
  - ์ธ๋„ค์ผ, ์ œ๋ชฉ, ์นดํ…Œ๊ณ ๋ฆฌ, ๊ฐ€๊ฒฉ
  - ์ˆ˜๊ฐ•์ƒ ์ˆ˜, ์ด ๋งค์ถœ
  - ์ƒํƒœ (๊ณต๊ฐœ/๋น„๊ณต๊ฐœ)
  - ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ
- '์ƒˆ ๊ฐ•์˜ ๋งŒ๋“ค๊ธฐ' ๋ฒ„ํŠผ
- ํŽ˜์ด์ง€๋„ค์ด์…˜ (10๊ฐœ์”ฉ)

### 3. ๊ฐ•์˜ ์ƒ์„ฑ ํŽ˜์ด์ง€
- resources/views/instructor/courses/create.blade.php
- ํผ ํ•„๋“œ:
  - ์ œ๋ชฉ (required)
  - ์„ค๋ช… (Markdown ์—๋””ํ„ฐ ๋˜๋Š” Textarea)
  - ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ (๋“œ๋กญ๋‹ค์šด)
  - ๋‚œ์ด๋„ ์„ ํƒ (๋ผ๋””์˜ค ๋ฒ„ํŠผ)
  - ๊ฐ€๊ฒฉ (์ˆซ์ž ์ž…๋ ฅ, KRW)
  - ์ธ๋„ค์ผ ์—…๋กœ๋“œ (์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ)
- ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: FormRequest ํด๋ž˜์Šค ์ƒ์„ฑ
- ์ธ๋„ค์ผ ์—…๋กœ๋“œ: Spatie Media Library ์‚ฌ์šฉ

### 4. ๊ฐ•์˜ ์ˆ˜์ • ํŽ˜์ด์ง€
- resources/views/instructor/courses/edit.blade.php
- ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋กœ๋“œํ•˜์—ฌ ํผ์— ํ‘œ์‹œ
- ์ธ๋„ค์ผ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ (๊ธฐ์กด ์ด๋ฏธ์ง€ ์œ ์ง€ ๋˜๋Š” ์ƒˆ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ)
- Slug ์ž๋™ ์ƒ์„ฑ (์ œ๋ชฉ ๊ธฐ๋ฐ˜, ์ค‘๋ณต ๋ฐฉ์ง€)

### 5. ๊ฐ•์˜ ์‚ญ์ œ
- Soft Delete ์‚ฌ์šฉ (๊ฐ•์˜ ๋ณต๊ตฌ ๊ฐ€๋Šฅ)
- ์‚ญ์ œ ์ „ ํ™•์ธ ๋ชจ๋‹ฌ (Alpine.js ๋˜๋Š” Blade ์ปดํฌ๋„ŒํŠธ)
- ์ˆ˜๊ฐ•์ƒ์ด ์žˆ๋Š” ๊ฐ•์˜๋Š” ์‚ญ์ œ ๋ถˆ๊ฐ€ (์ •์ฑ… ๊ฒ€์ฆ)

### 6. ๋ผ์šฐํŠธ ์ •์˜
- routes/web.php์— instructor ๊ทธ๋ฃน ์ถ”๊ฐ€
- Route::middleware(['auth', 'instructor'])->group()
- ๋ฆฌ์†Œ์Šค ๋ผ์šฐํŠธ: Route::resource('courses', InstructorCourseController::class)

### 7. Blade ์ปดํฌ๋„ŒํŠธ
- <x-course-card> ์ปดํฌ๋„ŒํŠธ (๊ฐ•์˜ ์นด๋“œ UI)
- <x-form-input> ์ปดํฌ๋„ŒํŠธ (์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž…๋ ฅ ํ•„๋“œ)
- <x-alert> ์ปดํฌ๋„ŒํŠธ (์„ฑ๊ณต/์—๋Ÿฌ ๋ฉ”์‹œ์ง€)

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- Tailwind CSS๋กœ ๋ชจ๋˜ํ•œ ๋Œ€์‹œ๋ณด๋“œ ๋””์ž์ธ
- ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ (๋ชจ๋ฐ”์ผ์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
- ์ธ๋„ค์ผ ์—…๋กœ๋“œ ์‹œ ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ์ง€์›
- ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ (ํผ ์ œ์ถœ ์‹œ)

CRUD ์™„์„ฑ ํ›„ ๊ฐ•์‚ฌ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ ๊ฐ•์˜ ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๊ฐ•์‚ฌ๊ฐ€ ๊ฐ•์˜๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š” CRUD ์‹œ์Šคํ…œ์ด ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. Spatie Media Library๋กœ ์ธ๋„ค์ผ์„ ๊ด€๋ฆฌํ•˜๊ณ , Policy๋กœ ๊ถŒํ•œ์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. Tailwind CSS๋กœ ๊น”๋”ํ•œ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

5
Step 5
์ฑ•ํ„ฐ ๋ฐ ๋ ˆ์Šจ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š” (๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ์ˆœ์„œ ๋ณ€๊ฒฝ ํฌํ•จ).

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. ChapterController ๋ฐ LessonController ์ƒ์„ฑ
- InstructorChapterController
- InstructorLessonController
- ์ค‘์ฒฉ ๋ผ์šฐํŠธ ์‚ฌ์šฉ: courses/{course}/chapters, courses/{course}/lessons

### 2. ์ฑ•ํ„ฐ ๊ด€๋ฆฌ
- ์ฑ•ํ„ฐ CRUD (์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ)
- ์ฑ•ํ„ฐ ์ˆœ์„œ ๋ณ€๊ฒฝ (๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ)
- ์ฑ•ํ„ฐ๋ณ„ ๋ ˆ์Šจ ๊ทธ๋ฃนํ•‘

### 3. ๋ ˆ์Šจ ๊ด€๋ฆฌ ํŽ˜์ด์ง€
- resources/views/instructor/courses/lessons/index.blade.php
- ์ฑ•ํ„ฐ๋ณ„๋กœ ๋ ˆ์Šจ ๋ชฉ๋ก ํ‘œ์‹œ (์•„์ฝ”๋””์–ธ ๋˜๋Š” ํƒญ)
- ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ์œผ๋กœ ๋ ˆ์Šจ ์ˆœ์„œ ๋ณ€๊ฒฝ (SortableJS ๋˜๋Š” Alpine.js)
- ๊ฐ ๋ ˆ์Šจ ์ •๋ณด:
  - ์ œ๋ชฉ, ์‹œ๊ฐ„, ๋ฌด๋ฃŒ ์—ฌ๋ถ€
  - ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ

### 4. ๋ ˆ์Šจ ์ƒ์„ฑ/์ˆ˜์ • ํŽ˜์ด์ง€
- resources/views/instructor/courses/lessons/create.blade.php
- resources/views/instructor/courses/lessons/edit.blade.php
- ํผ ํ•„๋“œ:
  - ์ œ๋ชฉ (required)
  - ์„ค๋ช… (optional)
  - ์ฑ•ํ„ฐ ์„ ํƒ (๋“œ๋กญ๋‹ค์šด, ์—†์œผ๋ฉด ๋ฏธ์ง€์ •)
  - ๋™์˜์ƒ ์—…๋กœ๋“œ (MP4, ์ตœ๋Œ€ 500MB)
  - ๋ฌด๋ฃŒ ๊ณต๊ฐœ ์—ฌ๋ถ€ (์ฒดํฌ๋ฐ•์Šค)
  - ํ•™์Šต ์ž๋ฃŒ ์ฒจ๋ถ€ (PDF, ZIP ๋“ฑ)

### 5. ๋™์˜์ƒ ์—…๋กœ๋“œ ๋ฐ ์ฒ˜๋ฆฌ
- Spatie Media Library๋กœ ๋™์˜์ƒ ์ €์žฅ
- Laravel Queue๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋™์˜์ƒ ์ธ์ฝ”๋”ฉ
  - Job ํด๋ž˜์Šค: ProcessVideoUpload
  - FFmpeg ์‚ฌ์šฉํ•˜์—ฌ HLS ํฌ๋งท์œผ๋กœ ๋ณ€ํ™˜ (์„ ํƒ ์‚ฌํ•ญ)
  - ์ธ๋„ค์ผ ์ž๋™ ์ƒ์„ฑ
  - ์˜์ƒ ๊ธธ์ด(duration) ์ž๋™ ์ถ”์ถœ
- ์—…๋กœ๋“œ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ (Livewire ๋˜๋Š” JavaScript fetch)

### 6. ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ์ˆœ์„œ ๋ณ€๊ฒฝ
- SortableJS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ
- AJAX ์š”์ฒญ์œผ๋กœ ์ˆœ์„œ ์—…๋ฐ์ดํŠธ
  - POST /courses/{course}/lessons/reorder
  - JSON payload: [{ id: 1, order: 1 }, { id: 2, order: 2 }]
- Optimistic UI ์—…๋ฐ์ดํŠธ (์ฆ‰์‹œ ๋ฐ˜์˜)

### 7. ํ•™์Šต ์ž๋ฃŒ ๊ด€๋ฆฌ
- ๋ ˆ์Šจ๋ณ„ ํŒŒ์ผ ์ฒจ๋ถ€ (PDF, ZIP, ์ด๋ฏธ์ง€ ๋“ฑ)
- ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ๋ผ์šฐํŠธ (์ธ์ฆ ํ•„์š”)
- ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ (10MB)

### 8. ๋ผ์šฐํŠธ ์ •์˜
- Route::resource('courses.chapters', InstructorChapterController::class)
- Route::resource('courses.lessons', InstructorLessonController::class)
- Route::post('courses/{course}/lessons/reorder', [InstructorLessonController::class, 'reorder'])

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ (๊ทธ๋ฆผ์ž, ์ปค์„œ ๋ณ€๊ฒฝ)
- ๋™์˜์ƒ ์—…๋กœ๋“œ ์ค‘ ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”
- ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ

๋™์˜์ƒ ์—…๋กœ๋“œ ํ›„ Queue Worker๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ธ์ฝ”๋”ฉ์ด ์ •์ƒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š” (php artisan queue:work).

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ฑ•ํ„ฐ์™€ ๋ ˆ์Šจ์„ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ์œผ๋กœ ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์˜์ƒ ์—…๋กœ๋“œ ์‹œ Laravel Queue๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ธ์ฝ”๋”ฉ์ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. SortableJS๋กœ ์ง๊ด€์ ์ธ UI๊ฐ€ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

6
Step 6
์ˆ˜๊ฐ•์ƒ์šฉ ๊ฐ•์˜ ํƒ์ƒ‰ ๋ฐ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. CourseController (์ˆ˜๊ฐ•์ƒ์šฉ)
- PublicCourseController ์ƒ์„ฑ
- ๋ฉ”์„œ๋“œ: index, show

### 2. ๊ฐ•์˜ ๋ชฉ๋ก ํŽ˜์ด์ง€
- resources/views/courses/index.blade.php
- ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ:
  - ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ (์‚ฌ์ด๋“œ๋ฐ” ๋˜๋Š” ํƒญ)
  - ๋‚œ์ด๋„๋ณ„ ํ•„ํ„ฐ (์ฒดํฌ๋ฐ•์Šค)
  - ๊ฐ€๊ฒฉ ๋ฒ”์œ„ ํ•„ํ„ฐ (์Šฌ๋ผ์ด๋”)
  - ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ (์ œ๋ชฉ, ์„ค๋ช…)
- ์ •๋ ฌ ์˜ต์…˜:
  - ์ตœ์‹ ์ˆœ, ์ธ๊ธฐ์ˆœ (์ˆ˜๊ฐ•์ƒ ์ˆ˜), ํ‰์  ๋†’์€ ์ˆœ, ๊ฐ€๊ฒฉ ๋‚ฎ์€ ์ˆœ
- ๊ฐ•์˜ ์นด๋“œ ํ‘œ์‹œ:
  - ์ธ๋„ค์ผ, ์ œ๋ชฉ, ๊ฐ•์‚ฌ๋ช…
  - ๊ฐ€๊ฒฉ, ํ‰์  (๋ณ„ ์•„์ด์ฝ˜)
  - ์ˆ˜๊ฐ•์ƒ ์ˆ˜, ๋ ˆ์Šจ ์ˆ˜
- ํŽ˜์ด์ง€๋„ค์ด์…˜ (12๊ฐœ์”ฉ)
- ๋ฌดํ•œ ์Šคํฌ๋กค ์˜ต์…˜ (์„ ํƒ ์‚ฌํ•ญ)

### 3. ๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€
- resources/views/courses/show.blade.php
- ์ƒ๋‹จ ์„น์…˜:
  - ์ธ๋„ค์ผ (๋˜๋Š” ์ƒ˜ํ”Œ ์˜์ƒ ํ”Œ๋ ˆ์ด์–ด)
  - ์ œ๋ชฉ, ๊ฐ•์‚ฌ ์ •๋ณด (์•„๋ฐ”ํƒ€, ์ด๋ฆ„, ์†Œ๊ฐœ)
  - ๊ฐ€๊ฒฉ, ํ‰์ , ์ˆ˜๊ฐ•์ƒ ์ˆ˜
  - '์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋‹ด๊ธฐ', '์œ„์‹œ๋ฆฌ์ŠคํŠธ ์ถ”๊ฐ€', '๋ฐ”๋กœ ๊ตฌ๋งค' ๋ฒ„ํŠผ
- ํƒญ ๋ฉ”๋‰ด:
  - ๊ฐ•์˜ ์†Œ๊ฐœ (description)
  - ์ปค๋ฆฌํ˜๋Ÿผ (์ฑ•ํ„ฐ ๋ฐ ๋ ˆ์Šจ ๋ชฉ๋ก, ๋ฌด๋ฃŒ ๋ ˆ์Šจ ํ‘œ์‹œ)
  - ๋ฆฌ๋ทฐ (๋ณ„์  ๋ถ„ํฌ ์ฐจํŠธ, ์ตœ์‹  ๋ฆฌ๋ทฐ 5๊ฐœ)
  - ๊ฐ•์‚ฌ ์ •๋ณด
  - Q&A (์งˆ๋ฌธ ๋ชฉ๋ก)
- ์ปค๋ฆฌํ˜๋Ÿผ ์„น์…˜:
  - ์•„์ฝ”๋””์–ธ์œผ๋กœ ์ฑ•ํ„ฐ ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ
  - ๊ฐ ๋ ˆ์Šจ: ์ œ๋ชฉ, ์‹œ๊ฐ„, ๋ฌด๋ฃŒ ์—ฌ๋ถ€
  - ๋ฌด๋ฃŒ ๋ ˆ์Šจ์€ '๋ฏธ๋ฆฌ๋ณด๊ธฐ' ๋ฒ„ํŠผ (๋ชจ๋‹ฌ๋กœ ์˜์ƒ ์žฌ์ƒ)
- ์ƒ˜ํ”Œ ์˜์ƒ ๋ชจ๋‹ฌ:
  - Video.js ๋˜๋Š” Plyr.js ํ”Œ๋ ˆ์ด์–ด
  - ์ „์ฒดํ™”๋ฉด ์ง€์›

### 4. ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
- Laravel Scout + Meilisearch ์„ค์ •
- Course ๋ชจ๋ธ์— Searchable trait ์ถ”๊ฐ€
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€ (resources/views/courses/search.blade.php)
- ์ž๋™์™„์„ฑ (Alpine.js ๋˜๋Š” JavaScript fetch)

### 5. ์œ„์‹œ๋ฆฌ์ŠคํŠธ ๊ธฐ๋Šฅ
- wishlist ํ…Œ์ด๋ธ” ์ƒ์„ฑ (user_id, course_id, unique constraint)
- WishlistController (add, remove)
- ์œ„์‹œ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€ (resources/views/wishlist/index.blade.php)
- ํ•˜ํŠธ ์•„์ด์ฝ˜ ํ† ๊ธ€ (AJAX)

### 6. ๋ผ์šฐํŠธ ์ •์˜
- Route::get('/courses', [PublicCourseController::class, 'index'])->name('courses.index')
- Route::get('/courses/{course:slug}', [PublicCourseController::class, 'show'])->name('courses.show')
- Route::post('/wishlist/{course}', [WishlistController::class, 'add'])->middleware('auth')
- Route::delete('/wishlist/{course}', [WishlistController::class, 'remove'])->middleware('auth')

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- Tailwind CSS๋กœ ์ด์ปค๋จธ์Šค ์Šคํƒ€์ผ ๋””์ž์ธ
- ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ (๋ชจ๋ฐ”์ผ์—์„œ ํƒญ์€ ๋“œ๋กญ๋‹ค์šด์œผ๋กœ)
- ์ด๋ฏธ์ง€ Lazy Loading
- ์Šค์ผˆ๋ ˆํ†ค ๋กœ๋”ฉ (๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ „)

Meilisearch ์„ค์ • ๋ฐ ์ธ๋ฑ์‹ฑ ํ›„ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ˆ˜๊ฐ•์ƒ์ด ๊ฐ•์˜๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ , ํ•„ํ„ฐ๋ง/๊ฒ€์ƒ‰ํ•˜๊ณ , ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. Laravel Scout๋กœ ๋น ๋ฅธ ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•˜๊ณ , ์œ„์‹œ๋ฆฌ์ŠคํŠธ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. Tailwind CSS๋กœ ๊น”๋”ํ•œ ์ด์ปค๋จธ์Šค UI๊ฐ€ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

7
Step 7
Stripe ๊ฒฐ์ œ ์—ฐ๋™ ๋ฐ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. Stripe ์„ค์ •
- Stripe PHP SDK ์„ค์น˜ (composer require stripe/stripe-php)
- .env์— Stripe API ํ‚ค ์ถ”๊ฐ€ (ํ…Œ์ŠคํŠธ ๋ชจ๋“œ)
  - STRIPE_PUBLIC_KEY
  - STRIPE_SECRET_KEY
- config/services.php์— Stripe ์„ค์ • ์ถ”๊ฐ€

### 2. ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๊ธฐ๋Šฅ
- cart ํ…Œ์ด๋ธ” ์ƒ์„ฑ (user_id, course_id, added_at)
- CartController (index, add, remove, clear)
- resources/views/cart/index.blade.php
  - ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ๋‹ด๊ธด ๊ฐ•์˜ ๋ชฉ๋ก
  - ๊ฐ ๊ฐ•์˜: ์ธ๋„ค์ผ, ์ œ๋ชฉ, ๊ฐ€๊ฒฉ, ์‚ญ์ œ ๋ฒ„ํŠผ
  - ์ด ๊ธˆ์•ก ํ‘œ์‹œ
  - '๊ฒฐ์ œํ•˜๊ธฐ' ๋ฒ„ํŠผ

### 3. ๊ฒฐ์ œ ํŽ˜์ด์ง€
- resources/views/checkout/index.blade.php
- Stripe Checkout Session ์ƒ์„ฑ
  - PaymentController::createCheckoutSession()
  - ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ์„ Stripe Line Items๋กœ ๋ณ€ํ™˜
  - ์„ฑ๊ณต/์‹คํŒจ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URL ์„ค์ •
- Stripe Elements ํ†ตํ•ฉ (์นด๋“œ ์ž…๋ ฅ ํผ)
- ๊ฒฐ์ œ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ Stripe Checkout ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

### 4. ๊ฒฐ์ œ ์™„๋ฃŒ ์ฒ˜๋ฆฌ
- Stripe Webhook ์„ค์ •
  - Route::post('/webhook/stripe', [WebhookController::class, 'handleStripe'])
  - Webhook ์„œ๋ช… ๊ฒ€์ฆ
- checkout.session.completed ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ:
  - Payment ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ (status: 'completed')
  - Enrollment ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ (์ˆ˜๊ฐ• ๋“ฑ๋ก)
  - ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋น„์šฐ๊ธฐ
  - ์ด๋ฉ”์ผ ์•Œ๋ฆผ ๋ฐœ์†ก (๊ตฌ๋งค ํ™•์ธ ๋ฉ”์ผ)

### 5. ๊ฒฐ์ œ ์„ฑ๊ณต/์‹คํŒจ ํŽ˜์ด์ง€
- resources/views/checkout/success.blade.php
  - ๊ตฌ๋งค ์™„๋ฃŒ ๋ฉ”์‹œ์ง€
  - ๊ตฌ๋งคํ•œ ๊ฐ•์˜ ๋ชฉ๋ก
  - '๋‚ด ํ•™์Šต ํŽ˜์ด์ง€๋กœ ์ด๋™' ๋ฒ„ํŠผ
- resources/views/checkout/cancel.blade.php
  - ๊ฒฐ์ œ ์ทจ์†Œ ๋ฉ”์‹œ์ง€
  - '์žฅ๋ฐ”๊ตฌ๋‹ˆ๋กœ ๋Œ์•„๊ฐ€๊ธฐ' ๋ฒ„ํŠผ

### 6. ํ™˜๋ถˆ ๊ธฐ๋Šฅ
- PaymentController::refund()
- 7์ผ ์ด๋‚ด ์ „์•ก ํ™˜๋ถˆ ์ •์ฑ… ๊ฒ€์ฆ
- Stripe Refund API ํ˜ธ์ถœ
- Payment ์ƒํƒœ ์—…๋ฐ์ดํŠธ (status: 'refunded')
- Enrollment ์‚ญ์ œ (ํ•™์Šต ์ง„๋„์œจ 10% ์ดํ•˜์ผ ๊ฒฝ์šฐ๋งŒ)
- ํ™˜๋ถˆ ์•Œ๋ฆผ ์ด๋ฉ”์ผ ๋ฐœ์†ก

### 7. ์ฃผ๋ฌธ ๋‚ด์—ญ ํŽ˜์ด์ง€
- resources/views/orders/index.blade.php
- ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ฃผ๋ฌธ ๋‚ด์—ญ ํ‘œ์‹œ
- ๊ฐ ์ฃผ๋ฌธ: ๊ฐ•์˜๋ช…, ๊ฒฐ์ œ์ผ, ๊ธˆ์•ก, ์ƒํƒœ
- ์˜์ˆ˜์ฆ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ (PDF)
- ํ™˜๋ถˆ ์š”์ฒญ ๋ฒ„ํŠผ (์กฐ๊ฑด ์ถฉ์กฑ ์‹œ)

### 8. ์ฟ ํฐ ์‹œ์Šคํ…œ
- coupons ํ…Œ์ด๋ธ” ์ƒ์„ฑ (code, discount_type, discount_value, expires_at)
- ์žฅ๋ฐ”๊ตฌ๋‹ˆ ํŽ˜์ด์ง€์— ์ฟ ํฐ ์ž…๋ ฅ ํ•„๋“œ
- ์ฟ ํฐ ๊ฒ€์ฆ ๋ฐ ํ• ์ธ ์ ์šฉ
- Stripe Checkout์— ์ฟ ํฐ ์ •๋ณด ์ „๋‹ฌ

## ๋ณด์•ˆ ์š”๊ตฌ์‚ฌํ•ญ
- CSRF ํ† ํฐ ๊ฒ€์ฆ
- Webhook ์„œ๋ช… ๊ฒ€์ฆ (Stripe::constructEvent)
- ์ค‘๋ณต ๊ฒฐ์ œ ๋ฐฉ์ง€ (idempotency key)
- ๊ฒฐ์ œ ๊ธˆ์•ก ์„œ๋ฒ„ ๊ฒ€์ฆ (ํด๋ผ์ด์–ธํŠธ ์กฐ์ž‘ ๋ฐฉ์ง€)

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- Tailwind CSS๋กœ ์ฒดํฌ์•„์›ƒ ํŽ˜์ด์ง€ ๋””์ž์ธ
- ๊ฒฐ์ œ ์ง„ํ–‰ ๋‹จ๊ณ„ ํ‘œ์‹œ (ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”)
- ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ (๊ฒฐ์ œ ์ฒ˜๋ฆฌ ์ค‘)

Stripe CLI๋กœ Webhook ํ…Œ์ŠคํŠธ ํ›„ ์‹ค์ œ ๊ฒฐ์ œ ํ”Œ๋กœ์šฐ๋ฅผ ํ™•์ธํ•˜์„ธ์š” (stripe listen --forward-to localhost:8000/webhook/stripe).

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Stripe ๊ฒฐ์ œ๊ฐ€ ์™„์ „ํžˆ ํ†ตํ•ฉ๋˜๊ณ , ์žฅ๋ฐ”๊ตฌ๋‹ˆ, ์ฒดํฌ์•„์›ƒ, ๊ฒฐ์ œ ์™„๋ฃŒ, ํ™˜๋ถˆ ๊ธฐ๋Šฅ์ด ๋ชจ๋‘ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. Webhook์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒฐ์ œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ฟ ํฐ ์‹œ์Šคํ…œ๋„ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ ๊ฒ€์ฆ์ด ์ฒ ์ €ํžˆ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

8
Step 8
์ˆ˜๊ฐ•์ƒ์šฉ ํ•™์Šต ํŽ˜์ด์ง€์™€ ์ง„๋„์œจ ์ถ”์  ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. ํ•™์Šต ํŽ˜์ด์ง€
- resources/views/learn/show.blade.php
- ๋ ˆ์ด์•„์›ƒ:
  - ์™ผ์ชฝ: ๋™์˜์ƒ ํ”Œ๋ ˆ์ด์–ด (Video.js ๋˜๋Š” Plyr.js)
  - ์˜ค๋ฅธ์ชฝ ์‚ฌ์ด๋“œ๋ฐ”: ์ปค๋ฆฌํ˜๋Ÿผ ๋ชฉ๋ก (์ฑ•ํ„ฐ ๋ฐ ๋ ˆ์Šจ)
  - ํ•˜๋‹จ: ๋ ˆ์Šจ ์„ค๋ช…, ํ•™์Šต ์ž๋ฃŒ ๋‹ค์šด๋กœ๋“œ ๋งํฌ
- ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ:
  - ์žฌ์ƒ/์ผ์‹œ์ •์ง€, ๋ณผ๋ฅจ ์กฐ์ ˆ
  - ๋ฐฐ์† ์กฐ์ ˆ (0.5x, 1x, 1.25x, 1.5x, 2x)
  - ์ „์ฒดํ™”๋ฉด ๋ชจ๋“œ
  - ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค (์ŠคํŽ˜์ด์Šค๋ฐ”: ์žฌ์ƒ/์ผ์‹œ์ •์ง€, ํ™”์‚ดํ‘œ: ์•ž๋’ค ์ด๋™)
  - PIP (Picture-in-Picture) ๋ชจ๋“œ
- ์ปค๋ฆฌํ˜๋Ÿผ ์‚ฌ์ด๋“œ๋ฐ”:
  - ์™„๋ฃŒํ•œ ๋ ˆ์Šจ ์ฒดํฌ ํ‘œ์‹œ
  - ํ˜„์žฌ ์žฌ์ƒ ์ค‘์ธ ๋ ˆ์Šจ ๊ฐ•์กฐ
  - ๋‹ค์Œ ๋ ˆ์Šจ ์ž๋™ ์žฌ์ƒ ์˜ต์…˜ (ํ† ๊ธ€)

### 2. ์ง„๋„์œจ ์ถ”์ 
- LessonProgressController ์ƒ์„ฑ
- ์‹œ์ฒญ ์‹œ๊ฐ„ ๊ธฐ๋ก:
  - JavaScript๋กœ 10์ดˆ๋งˆ๋‹ค ํ˜„์žฌ ์žฌ์ƒ ์‹œ๊ฐ„์„ ์„œ๋ฒ„์— ์ „์†ก
  - POST /lessons/{lesson}/progress
  - JSON payload: { watched_duration: 120 }
- ๋ ˆ์Šจ ์™„๋ฃŒ ์กฐ๊ฑด:
  - ์‹œ์ฒญ ์‹œ๊ฐ„์ด ์ „์ฒด ๊ธธ์ด์˜ 90% ์ด์ƒ์ผ ๋•Œ ์ž๋™ ์™„๋ฃŒ
  - is_completed ํ”Œ๋ž˜๊ทธ ์—…๋ฐ์ดํŠธ
- ๊ฐ•์˜ ์ „์ฒด ์ง„๋„์œจ ๊ณ„์‚ฐ:
  - (์™„๋ฃŒํ•œ ๋ ˆ์Šจ ์ˆ˜ / ์ „์ฒด ๋ ˆ์Šจ ์ˆ˜) * 100
  - Enrollment ๋ชจ๋ธ์˜ progress ํ•„๋“œ ์—…๋ฐ์ดํŠธ

### 3. ๋ถ๋งˆํฌ ๊ธฐ๋Šฅ
- lesson_bookmarks ํ…Œ์ด๋ธ” ์ƒ์„ฑ (user_id, lesson_id, timestamp, note)
- ํ”Œ๋ ˆ์ด์–ด์—์„œ '๋ถ๋งˆํฌ ์ถ”๊ฐ€' ๋ฒ„ํŠผ
- ํ˜„์žฌ ์žฌ์ƒ ์‹œ๊ฐ„๊ณผ ๋ฉ”๋ชจ ์ €์žฅ
- ๋ถ๋งˆํฌ ํด๋ฆญ ์‹œ ํ•ด๋‹น ์‹œ๊ฐ„์œผ๋กœ ์ด๋™

### 4. ๋…ธํŠธ ์ž‘์„ฑ ๊ธฐ๋Šฅ
- lesson_notes ํ…Œ์ด๋ธ” ์ƒ์„ฑ (user_id, lesson_id, timestamp, content)
- ํ•™์Šต ํŽ˜์ด์ง€ ํ•˜๋‹จ์— ๋…ธํŠธ ์—๋””ํ„ฐ (Markdown ์ง€์›)
- ํƒ€์ž„์Šคํƒฌํ”„๋ณ„ ๋…ธํŠธ ๋ชฉ๋ก ํ‘œ์‹œ
- ๋…ธํŠธ ํด๋ฆญ ์‹œ ํ•ด๋‹น ์‹œ๊ฐ„์œผ๋กœ ์ด๋™

### 5. ๋‚ด ํ•™์Šต ๋Œ€์‹œ๋ณด๋“œ
- resources/views/dashboard/learning.blade.php
- ์ˆ˜๊ฐ• ์ค‘์ธ ๊ฐ•์˜ ๋ชฉ๋ก:
  - ๊ฐ ๊ฐ•์˜: ์ธ๋„ค์ผ, ์ œ๋ชฉ, ์ง„๋„์œจ (ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”)
  - '์ด์–ด์„œ ํ•™์Šตํ•˜๊ธฐ' ๋ฒ„ํŠผ (๋งˆ์ง€๋ง‰ ์‹œ์ฒญ ๋ ˆ์Šจ์œผ๋กœ ์ด๋™)
- ํ•™์Šต ํ†ต๊ณ„:
  - ์ด ํ•™์Šต ์‹œ๊ฐ„ (์ฃผ๋ณ„, ์›”๋ณ„ ์ฐจํŠธ)
  - ์™„๊ฐ•ํ•œ ๊ฐ•์˜ ์ˆ˜
  - ์ง„ํ–‰ ์ค‘์ธ ๊ฐ•์˜ ์ˆ˜
- ์ตœ๊ทผ ์‹œ์ฒญํ•œ ๋ ˆ์Šจ (5๊ฐœ)

### 6. ์ˆ˜๋ฃŒ์ฆ ๋ฐœ๊ธ‰
- CertificateController::generate()
- ์ง„๋„์œจ 100% ๋‹ฌ์„ฑ ์‹œ ์ž๋™ ๋ฐœ๊ธ‰
- Laravel DomPDF๋กœ PDF ์ƒ์„ฑ
  - ํ…œํ”Œ๋ฆฟ: resources/views/certificates/template.blade.php
  - ๋‚ด์šฉ: ์ˆ˜๊ฐ•์ƒ ์ด๋ฆ„, ๊ฐ•์˜๋ช…, ์™„๊ฐ•์ผ, ๊ฐ•์‚ฌ ์„œ๋ช…
- ์ˆ˜๋ฃŒ์ฆ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ (ํ•™์Šต ๋Œ€์‹œ๋ณด๋“œ)

### 7. ๋ผ์šฐํŠธ ์ •์˜
- Route::get('/learn/{enrollment}', [LearnController::class, 'show'])->middleware('auth')
- Route::post('/lessons/{lesson}/progress', [LessonProgressController::class, 'update'])
- Route::post('/lessons/{lesson}/bookmarks', [BookmarkController::class, 'store'])
- Route::get('/certificates/{enrollment}', [CertificateController::class, 'download'])

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- ํ”Œ๋ ˆ์ด์–ด ์ค‘์‹ฌ์˜ ๋ชฐ์ž…ํ˜• ๋ ˆ์ด์•„์›ƒ
- ๋‹คํฌ ๋ชจ๋“œ ์ง€์› (ํ† ๊ธ€ ๋ฒ„ํŠผ)
- ๋ฐ˜์‘ํ˜• (๋ชจ๋ฐ”์ผ์—์„œ๋Š” ์‚ฌ์ด๋“œ๋ฐ”๊ฐ€ ํ•˜๋‹จ์œผ๋กœ ์ด๋™)

๋™์˜์ƒ ์žฌ์ƒ ์ค‘ ์ง„๋„์œจ์ด ์ •์ƒ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ˆ˜๊ฐ•์ƒ์ด ๋™์˜์ƒ์„ ์‹œ์ฒญํ•˜๊ณ , ์ง„๋„์œจ์ด ์ž๋™์œผ๋กœ ์ถ”์ ๋˜๋ฉฐ, ๋ถ๋งˆํฌ์™€ ๋…ธํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํ•™์Šต ํŽ˜์ด์ง€๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. 100% ์™„๊ฐ• ์‹œ ์ˆ˜๋ฃŒ์ฆ์ด ์ž๋™ ๋ฐœ๊ธ‰๋ฉ๋‹ˆ๋‹ค. Video.js๋กœ ํ’๋ถ€ํ•œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ๋Šฅ์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

9
Step 9
๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ๊ณผ Q&A ๊ฒŒ์‹œํŒ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ
- ReviewController (store, update, destroy)
- ๋ฆฌ๋ทฐ ์ž‘์„ฑ ์กฐ๊ฑด:
  - ๊ฐ•์˜๋ฅผ ๊ตฌ๋งคํ•œ ์ˆ˜๊ฐ•์ƒ๋งŒ ๊ฐ€๋Šฅ
  - ์ง„๋„์œจ 10% ์ด์ƒ์ผ ๋•Œ๋งŒ ๊ฐ€๋Šฅ
  - 1์ธ๋‹น 1๊ฐœ์˜ ๋ฆฌ๋ทฐ๋งŒ ์ž‘์„ฑ ๊ฐ€๋Šฅ (์ˆ˜์ •์€ ๊ฐ€๋Šฅ)
- ๋ฆฌ๋ทฐ ํผ (๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€):
  - ๋ณ„์  (1-5, ๋ณ„ ์•„์ด์ฝ˜ ํด๋ฆญ)
  - ์ฝ”๋ฉ˜ํŠธ (Textarea, ์ตœ์†Œ 20์ž)
  - '๋ฆฌ๋ทฐ ์ž‘์„ฑ' ๋ฒ„ํŠผ
- ๋ฆฌ๋ทฐ ๋ชฉ๋ก (๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ ํƒญ):
  - ์ตœ์‹ ์ˆœ, ํ‰์  ๋†’์€ ์ˆœ, ํ‰์  ๋‚ฎ์€ ์ˆœ ์ •๋ ฌ
  - ๊ฐ ๋ฆฌ๋ทฐ: ์ž‘์„ฑ์ž ์•„๋ฐ”ํƒ€, ์ด๋ฆ„, ๋ณ„์ , ์ž‘์„ฑ์ผ, ์ฝ”๋ฉ˜ํŠธ
  - ๋ณธ์ธ ๋ฆฌ๋ทฐ๋Š” ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ ํ‘œ์‹œ
  - ํŽ˜์ด์ง€๋„ค์ด์…˜ (10๊ฐœ์”ฉ)
- ํ‰์  ํ†ต๊ณ„:
  - ํ‰๊ท  ํ‰์  (์†Œ์ˆ˜์  1์ž๋ฆฌ)
  - ๋ณ„์ ๋ณ„ ๋ถ„ํฌ (5์ : 50%, 4์ : 30%, ...)
  - ์ด ๋ฆฌ๋ทฐ ์ˆ˜

### 2. Q&A ๊ฒŒ์‹œํŒ
- QnaController (index, create, store, show, update, destroy)
- QnaAnswerController (store, update, destroy)
- ์งˆ๋ฌธ ์ž‘์„ฑ (๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ ๋˜๋Š” ํ•™์Šต ํŽ˜์ด์ง€):
  - ์ œ๋ชฉ, ๋‚ด์šฉ (Markdown ์ง€์›)
  - ๋ ˆ์Šจ ์„ ํƒ (์„ ํƒ ์‚ฌํ•ญ)
  - '์งˆ๋ฌธํ•˜๊ธฐ' ๋ฒ„ํŠผ
- ์งˆ๋ฌธ ๋ชฉ๋ก (๊ฐ•์˜ ์ƒ์„ธ ํŽ˜์ด์ง€ ํƒญ):
  - resources/views/qna/index.blade.php
  - ๊ฐ ์งˆ๋ฌธ: ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ, ๋‹ต๋ณ€ ์ˆ˜, ํ•ด๊ฒฐ ์—ฌ๋ถ€
  - ๋ฏธํ•ด๊ฒฐ ์งˆ๋ฌธ ์šฐ์„  ํ‘œ์‹œ
  - ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ (์ œ๋ชฉ, ๋‚ด์šฉ)
  - ํŽ˜์ด์ง€๋„ค์ด์…˜
- ์งˆ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€:
  - resources/views/qna/show.blade.php
  - ์งˆ๋ฌธ ๋‚ด์šฉ (Markdown ๋ Œ๋”๋ง)
  - ๋‹ต๋ณ€ ๋ชฉ๋ก (์ตœ์‹ ์ˆœ)
  - ๋‹ต๋ณ€ ์ž‘์„ฑ ํผ (Textarea)
  - ๊ฐ•์‚ฌ ๋‹ต๋ณ€์€ '๊ฐ•์‚ฌ' ๋ฑƒ์ง€ ํ‘œ์‹œ
  - ์งˆ๋ฌธ ์ž‘์„ฑ์ž๋Š” ๋‹ต๋ณ€์„ '์ฑ„ํƒ'ํ•  ์ˆ˜ ์žˆ์Œ (is_answered ์—…๋ฐ์ดํŠธ)
- ๊ฐ•์‚ฌ ์ „์šฉ ๊ธฐ๋Šฅ:
  - ๋ฏธ๋‹ต๋ณ€ ์งˆ๋ฌธ ์•Œ๋ฆผ (๋Œ€์‹œ๋ณด๋“œ)
  - ๋‹ต๋ณ€ ์ž‘์„ฑ ์‹œ ์ด๋ฉ”์ผ ์•Œ๋ฆผ ๋ฐœ์†ก

### 3. ์•Œ๋ฆผ ์‹œ์Šคํ…œ
- notifications ํ…Œ์ด๋ธ” ์ƒ์„ฑ (Laravel ๊ธฐ๋ณธ ์•Œ๋ฆผ)
- ์•Œ๋ฆผ ํƒ€์ž…:
  - ์ƒˆ ๋ฆฌ๋ทฐ ์ž‘์„ฑ (๊ฐ•์‚ฌ์—๊ฒŒ)
  - ์ƒˆ ์งˆ๋ฌธ ์ž‘์„ฑ (๊ฐ•์‚ฌ์—๊ฒŒ)
  - ๋‹ต๋ณ€ ์ž‘์„ฑ (์งˆ๋ฌธ ์ž‘์„ฑ์ž์—๊ฒŒ)
  - ์ƒˆ ๋ ˆ์Šจ ์—…๋กœ๋“œ (์ˆ˜๊ฐ•์ƒ์—๊ฒŒ)
- ์•Œ๋ฆผ ํ‘œ์‹œ:
  - ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”์— ์•Œ๋ฆผ ์•„์ด์ฝ˜ (์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ ์ˆ˜ ํ‘œ์‹œ)
  - ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ์ตœ๊ทผ ์•Œ๋ฆผ 5๊ฐœ ํ‘œ์‹œ
  - '๋ชจ๋‘ ์ฝ์Œ' ๋ฒ„ํŠผ
- ์•Œ๋ฆผ ํŽ˜์ด์ง€:
  - resources/views/notifications/index.blade.php
  - ๋ชจ๋“  ์•Œ๋ฆผ ๋ชฉ๋ก (์ฝ์Œ/์ฝ์ง€ ์•Š์Œ ํ•„ํ„ฐ)

### 4. ์ด๋ฉ”์ผ ์•Œ๋ฆผ
- Laravel Mail ์„ค์ • (Mailtrap ๋˜๋Š” SMTP)
- Mailable ํด๋ž˜์Šค ์ƒ์„ฑ:
  - NewReviewNotification (๊ฐ•์‚ฌ์—๊ฒŒ)
  - NewQuestionNotification (๊ฐ•์‚ฌ์—๊ฒŒ)
  - NewAnswerNotification (์งˆ๋ฌธ ์ž‘์„ฑ์ž์—๊ฒŒ)
- Queue๋กœ ์ด๋ฉ”์ผ ๋ฐœ์†ก (๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ)

### 5. ๋ผ์šฐํŠธ ์ •์˜
- Route::resource('courses.reviews', ReviewController::class)->middleware('auth')
- Route::resource('courses.qna', QnaController::class)->middleware('auth')
- Route::post('qna/{qna}/answers', [QnaAnswerController::class, 'store'])
- Route::post('qna/{qna}/mark-answered', [QnaController::class, 'markAnswered'])
- Route::get('/notifications', [NotificationController::class, 'index'])
- Route::post('/notifications/mark-read', [NotificationController::class, 'markAllRead'])

## ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ
- ๋ณ„์ ์€ ๋ณ„ ์•„์ด์ฝ˜์œผ๋กœ ์‹œ๊ฐํ™” (ํด๋ฆญ ๊ฐ€๋Šฅ)
- Q&A๋Š” ํฌ๋Ÿผ ์Šคํƒ€์ผ ๋””์ž์ธ
- ์•Œ๋ฆผ ๋“œ๋กญ๋‹ค์šด์€ ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜
- Markdown ๋ Œ๋”๋ง (league/commonmark)

๋ฆฌ๋ทฐ ์ž‘์„ฑ ํ›„ ํ‰๊ท  ํ‰์ ์ด ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ๊ณผ Q&A ๊ฒŒ์‹œํŒ์ด ์™„์„ฑ๋˜๊ณ , ์•Œ๋ฆผ ์‹œ์Šคํ…œ์ด ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ•์‚ฌ์™€ ์ˆ˜๊ฐ•์ƒ ๊ฐ„ ์†Œํ†ต์ด ์›ํ™œํ•˜๊ฒŒ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ด๋ฉ”์ผ ์•Œ๋ฆผ์ด Queue๋กœ ๋น„๋™๊ธฐ ๋ฐœ์†ก๋ฉ๋‹ˆ๋‹ค. Markdown์œผ๋กœ ํ’๋ถ€ํ•œ ์ฝ˜ํ…์ธ  ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

10
Step 10
Redis ์บ์‹ฑ, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์„ ์™„๋ฃŒํ•˜์„ธ์š”.

## ์š”๊ตฌ์‚ฌํ•ญ

### 1. Redis ์บ์‹ฑ
- Redis ์„ค์น˜ ๋ฐ Laravel ์„ค์ •
- ์บ์‹ฑ ์ „๋žต:
  - ๊ฐ•์˜ ๋ชฉ๋ก (1์‹œ๊ฐ„ ์บ์‹ฑ)
    - Cache::remember('courses.all', 3600, fn() => Course::with('instructor')->get())
  - ์ธ๊ธฐ ๊ฐ•์˜ (1์ผ ์บ์‹ฑ)
    - Cache::remember('courses.popular', 86400, fn() => Course::orderBy('enrollments_count', 'desc')->take(10)->get())
  - ๊ฐ•์˜ ์ƒ์„ธ ์ •๋ณด (30๋ถ„ ์บ์‹ฑ)
    - Cache::remember("course.{$id}", 1800, fn() => Course::with('lessons', 'reviews')->find($id))
- ์บ์‹œ ๋ฌดํšจํ™”:
  - ๊ฐ•์˜ ์ˆ˜์ •/์‚ญ์ œ ์‹œ ์บ์‹œ ์‚ญ์ œ
  - Cache::forget("course.{$id}")
  - Cache::tags(['courses'])->flush()

### 2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”
- Eager Loading์œผ๋กœ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ ํ•ด๊ฒฐ:
  - Course::with('instructor', 'lessons', 'reviews')->get()
- ์ธ๋ฑ์Šค ์ถ”๊ฐ€:
  - courses ํ…Œ์ด๋ธ”: slug, instructor_id, category
  - lessons ํ…Œ์ด๋ธ”: course_id, order
  - enrollments ํ…Œ์ด๋ธ”: user_id, course_id
- ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ตœ์ ํ™” (Cursor Pagination)

### 3. ์ด๋ฏธ์ง€ ์ตœ์ ํ™”
- Spatie Media Library๋กœ ์ธ๋„ค์ผ ์ž๋™ ์ƒ์„ฑ
  - ์—ฌ๋Ÿฌ ํฌ๊ธฐ (small, medium, large)
- Lazy Loading ์ ์šฉ (loading="lazy")
- WebP ํฌ๋งท ๋ณ€ํ™˜ (์„ ํƒ ์‚ฌํ•ญ)

### 4. ๋ณด์•ˆ ๊ฐ•ํ™”
- CSRF ํ† ํฐ ๊ฒ€์ฆ (๋ชจ๋“  POST ์š”์ฒญ)
- XSS ๋ฐฉ์ง€ ({{ }} Blade ์ด์Šค์ผ€์ดํ•‘)
- SQL Injection ๋ฐฉ์ง€ (Eloquent ORM ์‚ฌ์šฉ)
- Rate Limiting:
  - ๋กœ๊ทธ์ธ ์‹œ๋„: 5ํšŒ/๋ถ„
  - ๋ฆฌ๋ทฐ ์ž‘์„ฑ: 3ํšŒ/์‹œ๊ฐ„
  - API ์š”์ฒญ: 60ํšŒ/๋ถ„
- HTTPS ๊ฐ•์ œ (ํ”„๋กœ๋•์…˜)

### 5. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (PHPUnit)
- Feature Tests:
  - CourseTest (CRUD ํ…Œ์ŠคํŠธ)
  - EnrollmentTest (์ˆ˜๊ฐ• ๋“ฑ๋ก ํ…Œ์ŠคํŠธ)
  - PaymentTest (๊ฒฐ์ œ ํ”Œ๋กœ์šฐ ํ…Œ์ŠคํŠธ)
  - ReviewTest (๋ฆฌ๋ทฐ ์ž‘์„ฑ ํ…Œ์ŠคํŠธ)
- Unit Tests:
  - CoursePolicy (๊ถŒํ•œ ํ…Œ์ŠคํŠธ)
  - ProgressCalculation (์ง„๋„์œจ ๊ณ„์‚ฐ ํ…Œ์ŠคํŠธ)
- Database Factories ์ƒ์„ฑ:
  - UserFactory, CourseFactory, LessonFactory
- Seeder ์ž‘์„ฑ:
  - DatabaseSeeder (ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ)

### 6. ์—๋Ÿฌ ํ•ธ๋“ค๋ง
- ์ปค์Šคํ…€ ์—๋Ÿฌ ํŽ˜์ด์ง€:
  - resources/views/errors/404.blade.php
  - resources/views/errors/500.blade.php
- ์˜ˆ์™ธ ๋กœ๊น… (Sentry ๋˜๋Š” Laravel Log)
- ์‚ฌ์šฉ์ž ์นœํ™”์  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€

### 7. ํ”„๋กœ๋•์…˜ ์ค€๋น„
- .env.example ์—…๋ฐ์ดํŠธ
- README.md ์ž‘์„ฑ:
  - ์„ค์น˜ ๊ฐ€์ด๋“œ
  - ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
  - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ์‹œ๋”ฉ
  - Queue Worker ์‹คํ–‰ ๋ฐฉ๋ฒ•
- Composer ์ตœ์ ํ™”:
  - composer install --optimize-autoloader --no-dev
- ์บ์‹œ ์ƒ์„ฑ:
  - php artisan config:cache
  - php artisan route:cache
  - php artisan view:cache

### 8. ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- APP_ENV=production ์„ค์ •
- APP_DEBUG=false ์„ค์ •
- Stripe ํ”„๋กœ๋•์…˜ ํ‚ค๋กœ ๋ณ€๊ฒฝ
- HTTPS ๊ฐ•์ œ (AppServiceProvider)
- Queue Worker ๋ฐ๋ชฌ ์‹คํ–‰ (Supervisor)
- ์Šค์ผ€์ค„๋Ÿฌ ์„ค์ • (Cron)
- ๋ฐฑ์—… ์„ค์ • (Laravel Backup)

## ํ…Œ์ŠคํŠธ ์‹คํ–‰
- php artisan test --coverage
- ์ปค๋ฒ„๋ฆฌ์ง€ 70% ์ด์ƒ ๋ชฉํ‘œ

๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๊ณ , ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ํ”„๋กœ์ ํŠธ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค!

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Redis ์บ์‹ฑ์œผ๋กœ ์„ฑ๋Šฅ์ด ์ตœ์ ํ™”๋˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๊ฐ€ ํšจ์œจ์ ์œผ๋กœ ๊ฐœ์„ ๋ฉ๋‹ˆ๋‹ค. PHPUnit ํ…Œ์ŠคํŠธ๋กœ ์ฝ”๋“œ ํ’ˆ์งˆ์ด ๊ฒ€์ฆ๋˜๊ณ , ๋ณด์•ˆ์ด ๊ฐ•ํ™”๋ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์–ด ์‹ค์ œ ์„œ๋น„์Šค๋กœ ๋Ÿฐ์นญํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด ๋ฉ๋‹ˆ๋‹ค.

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!