# CEPI LMS — Backend

> Verified from codebase. Last updated: 2026-06-02.

---

## 1. Backend Module Structure

```
app/
├── Http/
│   ├── Controllers/
│   │   ├── Auth/                      # Auth flow (Breeze + Social + DevicePolicy)
│   │   ├── Admin/                     # Full admin CRUD
│   │   ├── Instructor/                # Instructor self-service
│   │   ├── Student/                   # Lesson player + progress
│   │   ├── CheckoutController.php     # Gateway checkout (stub)
│   │   ├── ManualCourseCheckoutController.php  # Bank transfer (live)
│   │   ├── DashboardController.php    # Role dispatcher
│   │   ├── PublicCourseController.php
│   │   ├── PublicBlogController.php
│   │   ├── PublicPageController.php
│   │   ├── HomeController.php
│   │   └── SitemapController.php
│   ├── Middleware/
│   │   └── CheckRole.php              # Spatie role middleware alias
│   └── Requests/
│       └── Auth/LoginRequest.php      # Validated login request
├── Models/                            # See DATABASE_SCHEMA.md
├── Services/
│   ├── BunnyVideoService.php
│   ├── DeviceBindingService.php
│   ├── AdminMediaService.php
│   ├── SitemapService.php
│   └── Payments/
│       ├── JazzCashGateway.php        # Skeleton
│       └── StripeGateway.php          # Skeleton
├── Contracts/
│   └── PaymentGatewayInterface.php    # Gateway interface
└── Notifications/
    └── Auth/VerifyEmailCodeNotification.php
```

---

## 2. Auth and Role System

### Authentication
- **Laravel Breeze** scaffolding with email/password
- **Google OAuth** via Laravel Socialite (`SocialAuthController`)
- **Custom 6-digit email verification code** (not default link-based). Code stored on `users` table, expires in 2 days.
- **Device binding** enforced at login via `DeviceBindingService`

### Authorization
- **Spatie laravel-permission** (`^7.2`) manages roles: `admin`, `instructor`, `student`
- Redundant `user_type` column (`admin`/`instructor`/`student`) stored on users table for fast dispatch
- Route-level protection:
  - `role:admin` middleware on `/admin/*`
  - `role:instructor` middleware on `/instructor/*`
  - `auth` + `verified` on student routes
- Enrollment-level access check in `Student\LessonController::verifyEnrollment()`:
  - Checks `status = active` AND (`expires_at IS NULL` OR `expires_at > now()`)
- Lesson-course ownership check: `$lesson->module->course_id !== $course->id` → 404

---

## 3. Core Business Logic

### Course Management
- **Admin** can create, edit, publish, archive courses via `Admin\CourseController`
- **Instructor** can create and manage own courses via `Instructor\CourseController`; submit for review via `submitForReview`
- Course status lifecycle: `draft` → `pending_review` → `published` → `archived`
- `Course::refreshDerivedStats()` recalculates `total_duration`, `total_lectures`, `total_students`, `rating` on demand

### Module and Lesson Management
- Managed via `Admin\CourseModuleController` and `Admin\LessonController` as `apiResource` (JSON responses)
- Ordered by `order_index`; updates sent via AJAX from course edit UI
- Lesson `content_type`: `video` | `pdf` | `text`
- Video lessons use `bunny_video_id`; playback via signed embed URL from `BunnyVideoService`

### Enrollment
- **Free course**: auto-enrolled on checkout initiation (no payment record needed)
- **Bank transfer**: `ManualCourseCheckoutController` creates a `pending` payment; admin reviews and approves via `Admin\PaymentController`
- **Gateway (stub)**: `CheckoutController::initiate()` creates pending payment, redirects to gateway; `callback()` and `webhook()` handle return — not yet production-safe
- Admin can manually enroll any user via `Admin\EnrollmentController`
- Duplicate enrollment prevention checked before creation

### Lesson Progress Tracking
`Student\LessonController::toggleProgress()` handles AJAX updates:
- `watched_seconds`: monotonic max (never decreases)
- `last_position_seconds`: resume position
- `watch_count`: incremented on each playback start
- `completed`: set true if `watched_seconds >= duration * 0.9` or explicitly passed `is_completed: true`
- `completed_at`: timestamped on first completion
- After each update, `refreshEnrollmentProgress()` recalculates `enrollments.progress_percent`

### Device Binding (`DeviceBindingService`)
- On login, checks for a trusted cookie (`cepi_trusted_device`)
- Cookie stores an HMAC-SHA256 token hash linked to `user_devices`
- If no match found: checks if another active device exists
  - If active device has active cooldown → throws `ValidationException` (blocks login)
  - If cooldown expired → old device blocked (`is_blocked = true`, `replaced_at` set)
  - New device record created, new cookie issued
- Cooldown period: 24 hours (configurable via `CEPI_DEVICE_BINDING_COOLDOWN_HOURS`)
- Cookie lifetime: 180 days (configurable via `CEPI_DEVICE_BINDING_COOKIE_DAYS`)
- Admin can reset devices via `Admin\UserController::resetDevices()`

### Bunny Video Service (`BunnyVideoService`)
- `generateSignedEmbedUrl(videoId, ttl)`: SHA256 token for Bunny player embed (iframe)
- `generateSignedUrl(videoId, ttl)`: HMAC-SHA256 for raw HLS playlist (for mobile direct playback)
- `createVideo(title)`: POST to Bunny REST API to provision a new video slot, returns GUID
- `extractVideoId(input)`: extracts UUID from URL or raw string
- TTL default: 300 seconds (configurable via `BUNNY_EMBED_TOKEN_TTL`)

---

## 4. Payment Logic

### Bank Transfer (Production-Ready)
1. Student visits `/checkout/manual/{course:slug}`
2. Sees bank account details from `settings` table
3. Submits transfer reference, name, phone → creates `Payment` with `gateway = bank_transfer`, `status = pending`
4. Admin reviews at `/admin/payments` → approves → creates `Enrollment` record, sets `status = completed`
5. Duplicate submission prevention (reuses pending payment record)

### JazzCash (Skeleton — Not Production-Ready)
- `JazzCashGateway::initializeCheckout()`: builds payload array, returns sandbox URL
- `verifyPayment()`: checks `pp_ResponseCode == '000'` (no hash verification yet)
- `handleWebhook()`: returns 200 (no processing)
- **Missing**: merchant ID/password/integrity salt, actual HMAC hash verification, enrollment creation on success

### Stripe (Skeleton — Not Production-Ready)
- Returns a mock URL, no Stripe SDK installed
- **Missing**: `stripe/stripe-php` package, session creation, webhook signature verification, enrollment creation

---

## 5. Validation Strategy

- Form requests used for login (`LoginRequest`)
- Inline `$request->validate()` used in most controllers
- Validation rules are present but not always in dedicated FormRequest classes
- **Recommendation**: Extract all validation to dedicated FormRequest classes for API readiness

---

## 6. Error Handling Strategy

- `abort(403)` for enrollment violations
- `abort(404)` for course/lesson ownership mismatch
- `ValidationException` thrown from `DeviceBindingService` for device conflicts
- Payment errors return redirects with flash messages
- API JSON endpoints return `{ status: 'error', message: '...' }` on exceptions
- No global exception handler customization found — default Laravel behavior

---

## 7. Missing Backend Modules

| Module | Description | Priority |
|---|---|---|
| Laravel Sanctum | API token auth for mobile | 🔴 Critical |
| `routes/api.php` | Versioned REST API | 🔴 Critical |
| API resource controllers | JSON-first controllers | 🔴 Critical |
| JSON resource transformers | `UserResource`, `CourseResource`, etc. | 🔴 Critical |
| JazzCash complete integration | Secure hash, real verification | 🟠 High |
| Stripe complete integration | SDK, webhooks, enrollment | 🟠 High |
| Certificate generation | PDF certificate on course completion | 🟡 Medium |
| Email notifications | Enrollment confirmed, payment approved | 🟡 Medium |
| Admin audit log wiring | Consistent usage across admin actions | 🟡 Medium |
| Quiz grading logic | Backend for quiz evaluation | 🟡 Medium |
| Course review submission UI | Frontend form for CourseReview model | 🟡 Medium |
| API rate limiting | `throttle` middleware on API routes | 🟠 High |
| Push notification service | FCM integration for mobile | 🟡 Medium |

---

## 8. Refactoring Recommendations

1. **FormRequest classes** for all admin and API endpoints
2. **Service layer** for enrollment logic (currently scattered in controllers)
3. **API Resource classes** before any mobile API work
4. **Payment Gateway Interface** is defined — wire it with service container binding for cleaner DI
5. **Consistent admin logging** — AdminLog model exists but is not called consistently
6. **Enrollment creation** in `CheckoutController::callback()` is incomplete — no actual enrollment is created on gateway success
