---
trigger: manual
---

# Backend Development Rules

> Rules and guidelines for developing the E-Learning Platform Backend

---

## 🎯 General Principles

### 1. Code Organization
- Follow MVC pattern: Models (Prisma) → Controllers → Routes
- Keep controllers thin, business logic in services
- One file per route group (e.g., `auth.routes.js`, `courses.routes.js`)
- Maximum 200 lines per file (split if larger)

### 2. Naming Conventions
```javascript
// Files: kebab-case
user-service.js
course-controller.js

// Functions: camelCase
getUserById()
createCourse()

// Classes: PascalCase
UserService
CourseController

// Constants: UPPER_SNAKE_CASE
MAX_FILE_SIZE
JWT_SECRET

// Database: snake_case
user_id
created_at
```

### 3. Multi-Language Support
**ALWAYS** use JSON structure for user-facing text:
```javascript
// ✅ Correct
{
  title: { th: "หลักสูตร", en: "Course" }
}

// ❌ Wrong
{
  title: "หลักสูตร"
}
```

---

## 🔐 Security Rules

### 1. Authentication
- **ALWAYS** validate JWT token in protected routes
- **NEVER** trust client-side data
- **ALWAYS** hash passwords with bcrypt (salt rounds: 10)
- Token expiry: 24h (access), 7d (refresh)

### 2. Authorization
```javascript
// Check authentication first
if (!req.user) return res.status(401).json({ error: "Unauthorized" });

// Then check role
if (!["ADMIN", "INSTRUCTOR"].includes(req.user.role.code)) {
  return res.status(403).json({ error: "Forbidden" });
}

// Finally check ownership
if (course.instructorId !== req.user.id && req.user.role.code !== "ADMIN") {
  return res.status(403).json({ error: "Forbidden" });
}
```

### 3. Input Validation
- **ALWAYS** validate with Joi or Zod
- **NEVER** trust user input
- Sanitize HTML content
- Validate file types and sizes

---

## 📁 File Upload Rules

### 1. File Validation
```javascript
// Check file type
const allowedTypes = ['video/mp4', 'application/pdf', ...];
if (!allowedTypes.includes(file.mimetype)) {
  return error("Invalid file type");
}

// Check file size
if (file.size > MAX_FILE_SIZE) {
  return error("File too large");
}
```

### 2. File Storage (MinIO/S3)
```javascript
// Use consistent path structure
const videoPath = `courses/${courseId}/lessons/${lessonId}/video.mp4`;
const attachmentPath = `lessons/${lessonId}/attachments/${uniqueFilename}`;

// Generate unique filenames
const uniqueFilename = `${Date.now()}-${uuidv4()}-${originalFilename}`;
```

### 3. File Limits
- Video: 500 MB max
- Attachment: 100 MB max per file
- Max 10 attachments per lesson
- Total 500 MB per lesson

---

## 🗄️ Database Rules

### 1. Prisma Best Practices
```javascript
// ✅ Use transactions for related operations
await prisma.$transaction([
  prisma.lesson.create(...),
  prisma.attachment.createMany(...)
]);

// ✅ Include relations when needed
const course = await prisma.course.findUnique({
  where: { id },
  include: { chapters: { include: { lessons: true } } }
});

// ✅ Use select to limit fields
const user = await prisma.user.findUnique({
  where: { id },
  select: { id: true, username: true, email: true }
});
```

### 2. Soft Delete Pattern
```javascript
// ✅ Use soft delete
await prisma.course.update({
  where: { id },
  data: { isDeleted: true, deletedAt: new Date() }
});

// ❌ Don't hard delete
await prisma.course.delete({ where: { id } }); // Avoid this
```

### 3. Query Optimization
- Use `select` to fetch only needed fields
- Use `include` wisely (avoid N+1 queries)
- Add indexes for frequently queried fields
- Use pagination for large datasets

---

## 🎯 API Response Format

### 1. Success Response
```javascript
// Single resource
res.status(200).json({
  id: 1,
  title: { th: "...", en: "..." },
  ...
});

// List with pagination
res.status(200).json({
  data: [...],
  pagination: {
    page: 1,
    limit: 20,
    total: 150,
    totalPages: 8
  }
});
```

### 2. Error Response
```javascript
res.status(400).json({
  error: {
    code: "VALIDATION_ERROR",
    message: "Invalid input data",
    details: [
      { field: "email", message: "Email is required" }
    ]
  }
});
```

### 3. Status Codes
- `200` OK - Success
- `201` Created - Resource created
- `204` No Content - Success with no response body
- `400` Bad Request - Validation error
- `401` Unauthorized - Not authenticated
- `403` Forbidden - Not authorized
- `404` Not Found - Resource not found
- `409` Conflict - Duplicate entry
- `422` Unprocessable Entity - Business logic error
- `500` Internal Server Error - Server error

---

## 🔄 Business Logic Rules

### 1. Course Management
- New courses start as `DRAFT`
- Must have ≥1 chapter and ≥3 lessons before submission
- Only course owner or primary instructor can submit
- Admin can approve/reject

### 2. Lesson Prerequisites
```javascript
// Check prerequisites before allowing access
const canAccess = await checkLessonAccess(userId, lessonId);
if (!canAccess.allowed) {
  return res.status(403).json({
    error: {
      code: "LESSON_LOCKED",
      message: canAccess.reason,
      required_lessons: canAccess.requiredLessons
    }
  });
}
```

### 3. Quiz Attempts
```javascript
// Validate before allowing quiz attempt
if (attempts >= quiz.maxAttempts) {
  return error("MAX_ATTEMPTS_EXCEEDED");
}

if (lastAttempt && (now - lastAttempt) < cooldown) {
  return error("COOLDOWN_ACTIVE");
}
```

### 4. Video Progress
- Save progress every 5 seconds
- Auto-complete at ≥90% watched
- Use `last_watched_at` for concurrent sessions

### 5. Multi-Instructor
- Cannot remove last instructor
- Only primary instructor can manage other instructors
- Course owner always has full access

---

## 🧪 Testing Rules

### 1. Test Coverage
- Write tests for all business logic
- Test happy path AND error cases
- Test authentication and authorization
- Test file uploads

### 2. Test Structure
```javascript
describe('Course API', () => {
  describe('POST /api/instructor/courses', () => {
    it('should create course with valid data', async () => {
      // Arrange
      const courseData = {...};
      
      // Act
      const response = await request(app)
        .post('/api/instructor/courses')
        .set('Authorization', `Bearer ${token}`)
        .send(courseData);
      
      // Assert
      expect(response.status).toBe(201);
      expect(response.body).toHaveProperty('id');
    });
    
    it('should return 401 without authentication', async () => {
      // Test unauthorized access
    });
  });
});
```

---

## 📝 Code Documentation

### 1. JSDoc Comments
```javascript
/**
 * Create a new course
 * @param {Object} req - Express request object
 * @param {Object} req.body - Course data
 * @param {Object} req.body.title - Course title (th/en)
 * @param {number} req.body.category_id - Category ID
 * @param {Object} res - Express response object
 * @returns {Promise<Object>} Created course
 */
async function createCourse(req, res) {
  // Implementation
}
```

### 2. Inline Comments
```javascript
// Only for complex logic
// Explain WHY, not WHAT

// ✅ Good
// Use highest score to determine pass/fail (per quiz settings)
const finalScore = Math.max(...attemptScores);

// ❌ Bad
// Get max score
const finalScore = Math.max(...attemptScores);
```

---

## 🚨 Error Handling

### 1. Try-Catch Pattern
```javascript
async function createCourse(req, res) {
  try {
    // Validate input
    const { error, value } = courseSchema.validate(req.body);
    if (error) {
      return res.status(400).json({
        error: {
          code: "VALIDATION_ERROR",
          message: error.details[0].message
        }
      });
    }
    
    // Business logic
    const course = await courseService.create(value, req.user.id);
    
    return res.status(201).json(course);
  } catch (err) {
    console.error('Create course error:', err);
    return res.status(500).json({
      error: {
        code: "INTERNAL_ERROR",
        message: "Failed to create course"
      }
    });
  }
}
```

### 2. Custom Error Classes
```javascript
class ValidationError extends Error {
  constructor(message, details) {
    super(message);
    this.name = 'ValidationError';
    this.statusCode = 400;
    this.details = details;
  }
}

class UnauthorizedError extends Error {
  constructor(message = 'Unauthorized') {
    super(message);
    this.name = 'UnauthorizedError';
    this.statusCode = 401;
  }
}
```

---

## 🔍 Logging Rules

### 1. What to Log
```javascript
// ✅ Log important events
logger.info('User logged in', { userId, timestamp });
logger.info('Course created', { courseId, instructorId });

// ✅ Log errors with context
logger.error('File upload failed', { 
  error: err.message, 
  userId, 
  filename 
});

// ❌ Don't log sensitive data
logger.info('User logged in', { password }); // NEVER!
```

### 2. Log Levels
- `error` - Errors that need attention
- `warn` - Warnings (deprecated features, etc.)
- `info` - Important events (login, course creation)
- `debug` - Detailed debugging (development only)

---

## ⚡ Performance Rules

### 1. Database Queries
```javascript
// ✅ Use pagination
const courses = await prisma.course.findMany({
  skip: (page - 1) * limit,
  take: limit
});

// ✅ Limit relations depth
const course = await prisma.course.findUnique({
  include: {
    chapters: {
      include: { lessons: true }
      // Don't go deeper unless necessary
    }
  }
});
```

### 2. Caching (Redis)
```javascript
// Cache frequently accessed data
const cacheKey = `course:${courseId}`;
const cached = await redis.get(cacheKey);

if (cached) {
  return JSON.parse(cached);
}

const course = await prisma.course.findUnique(...);
await redis.setex(cacheKey, 3600, JSON.stringify(course));
return course;
```

### 3. File Processing
```javascript
// Process large files asynchronously
const job = await queue.add('process-video', {
  videoPath,
  lessonId
});

return res.status(202).json({
  message: 'Video processing started',
  jobId: job.id
});
```

---

## 🔒 Environment Variables

### Required Variables
```bash
# Application
NODE_ENV=development
PORT=4000
APP_URL=http://localhost:4000

# Database
DATABASE_URL=postgresql://...

# Redis
REDIS_URL=redis://...

# MinIO/S3
S3_ENDPOINT=http://...
S3_ACCESS_KEY=...
S3_SECRET_KEY=...

# JWT
JWT_SECRET=...
JWT_EXPIRES_IN=24h

# Email (Mailhog in dev)
SMTP_HOST=...
SMTP_PORT=1025
```

### Loading Environment Variables
```javascript
// ✅ Use dotenv
require('dotenv').config();

// ✅ Validate required variables
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', ...];
requiredEnvVars.forEach(key => {
  if (!process.env[key]) {
    throw new Error(`Missing required environment variable: ${key}`);
  }
});
```

---

## 📦 Dependencies

### Core Dependencies
- `express` - Web framework
- `@prisma/client` - Database ORM
- `bcrypt` - Password hashing
- `jsonwebtoken` - JWT authentication
- `joi` or `zod` - Input validation
- `multer` - File uploads
- `aws-sdk` or `minio` - S3 storage
- `redis` - Caching
- `winston` - Logging

### Dev Dependencies
- `nodemon` - Auto-restart
- `jest` - Testing
- `supertest` - API testing
- `eslint` - Linting
- `prettier` - Code formatting

---

## 🚀 Deployment Checklist

- [ ] All environment variables set
- [ ] Database migrations run
- [ ] Redis connection tested
- [ ] MinIO/S3 buckets created
- [ ] JWT secret is strong and unique
- [ ] CORS configured correctly
- [ ] Rate limiting enabled
- [ ] Logging configured
- [ ] Error tracking setup (Sentry, etc.)
- [ ] Health check endpoint working

---

**Remember**: Security first, code quality second, features third!