# Utility Controllers Guide

This guide explains the new utility controllers that make development easier, simpler, and better.

## Table of Contents

1. [Response Helper](#response-helper)
2. [Pagination Helper](#pagination-helper)
3. [Query Helper](#query-helper)
4. [Validation Helper](#validation-helper)
5. [Audit Log Controller](#audit-log-controller)
6. [Batch Operations Controller](#batch-operations-controller)
7. [Export Helper](#export-helper)
8. [Complete Examples](#complete-examples)

---

## Response Helper

**Purpose:** Standardize all API responses across your application.

### Features
- Consistent response format
- Pre-built status code handlers
- Support for metadata (pagination, etc.)
- Easy error handling

### Usage

```go
import "fotbal-club/internal/controllers"

// Success response
controllers.Respond.Success(c, data, "Operation successful")

// Success with metadata (pagination)
controllers.Respond.SuccessWithMeta(c, articles, paginationMeta, "Articles retrieved")

// Created (201)
controllers.Respond.Created(c, newArticle, "Article created")

// Error responses
controllers.Respond.BadRequest(c, "Invalid input")
controllers.Respond.Unauthorized(c, "Not authenticated")
controllers.Respond.Forbidden(c, "No permission")
controllers.Respond.NotFound(c, "Article not found")
controllers.Respond.InternalError(c, "Database error")
controllers.Respond.ValidationError(c, validationErrors)

// No content (204)
controllers.Respond.NoContent(c)
```

### Response Format

```json
{
  "success": true,
  "message": "Articles retrieved successfully",
  "data": [...],
  "meta": {
    "page": 1,
    "page_size": 20,
    "total": 100,
    "total_pages": 5,
    "has_next": true,
    "has_prev": false
  }
}
```

---

## Pagination Helper

**Purpose:** Add pagination to any list endpoint with one line of code.

### Features
- Auto-extracts page/page_size from query params
- Calculates pagination metadata
- Works with GORM queries
- Supports preloading

### Usage

```go
// Simple pagination
query := db.Model(&models.Article{}).Where("published = ?", true)
var articles []models.Article
meta, err := controllers.Paginator.Paginate(c, query, &articles)
if err != nil {
    controllers.Respond.InternalError(c, "Failed to retrieve articles")
    return
}
controllers.Respond.SuccessWithMeta(c, articles, meta, "Success")

// Pagination with preloading
meta, err := controllers.Paginator.PaginateWithPreload(
    c, query, &articles, "Author", "Category"
)
```

### Query Parameters

```
GET /api/v1/articles?page=1&page_size=20
```

- `page`: Page number (default: 1)
- `page_size`: Items per page (default: 20, max: 100)

---

## Query Helper

**Purpose:** Simplify filtering, sorting, and searching in list endpoints.

### Features
- Search across multiple fields
- Sort by any field
- Boolean filters
- ID filters (comma-separated)
- Date range filters
- Fluent chain builder

### Usage

#### Basic Sorting

```go
// GET /api/v1/articles?sort=created_at:desc
query := controllers.QueryParser.ApplySortFromContext(
    c, db.Model(&models.Article{}), "created_at", "desc"
)
```

#### Basic Search

```go
// GET /api/v1/articles?search=football
query := controllers.QueryParser.ApplySearchFromContext(
    c, db.Model(&models.Article{}), "title", "content"
)
```

#### Fluent Chain Builder (Recommended)

```go
// GET /api/v1/articles?search=football&sort=created_at:desc&published=true&category_ids=1,2,3&from=2024-01-01
query := controllers.QueryParser.BuildQueryChain(c, db.Model(&models.Article{})).
    WithSearch("title", "content").
    WithSort("created_at", "desc").
    WithBoolFilter("published", "published").
    WithBoolFilter("featured", "featured").
    WithIDsFilter("category_ids", "category_id").
    WithDateRange("created_at").
    Build()

var articles []models.Article
meta, err := controllers.Paginator.Paginate(c, query, &articles)
```

### Supported Query Parameters

- `search` or `q`: Search term
- `sort`: field:order (e.g., `created_at:desc`)
- `published`: Boolean filter (true/false)
- `featured`: Boolean filter (true/false)
- `category_ids`: Comma-separated IDs (e.g., `1,2,3`)
- `from`: Start date (YYYY-MM-DD)
- `to`: End date (YYYY-MM-DD)

---

## Validation Helper

**Purpose:** Validate request data and return user-friendly error messages.

### Features
- Struct validation using tags
- Custom validators (slug, color)
- Automatic error responses
- Input sanitization

### Usage

#### Validate Struct

```go
type CreateArticleRequest struct {
    Title   string `validate:"required,min=3,max=200"`
    Content string `validate:"required,min=10"`
    Slug    string `validate:"omitempty,slug"`
    Email   string `validate:"required,email"`
}

var req CreateArticleRequest
if err := c.ShouldBindJSON(&req); err != nil {
    controllers.Respond.BadRequest(c, "Invalid JSON")
    return
}

// Validate and auto-respond if invalid
if !controllers.Validator.ValidateAndRespond(c, req) {
    return // Response already sent
}

// Continue with valid data...
```

#### Sanitization

```go
// Sanitize string (trim, normalize spaces)
title := controllers.Validator.SanitizeString(req.Title)

// Sanitize email (lowercase, trim)
email := controllers.Validator.SanitizeEmail(req.Email)

// Sanitize slug
slug := controllers.Validator.SanitizeSlug(req.Slug)
```

#### Individual Validation

```go
if !controllers.Validator.IsValidEmail(email) {
    controllers.Respond.BadRequest(c, "Invalid email")
    return
}
```

### Validation Tags

- `required`: Field is required
- `email`: Valid email address
- `url`: Valid URL
- `min=n`: Minimum length
- `max=n`: Maximum length
- `slug`: Valid slug (lowercase, alphanumeric, hyphens)
- `color`: Valid hex color (#RGB, #RRGGBB)
- `oneof=val1 val2`: One of the specified values
- `gte=n`, `lte=n`: Greater/less than or equal

---

## Audit Log Controller

**Purpose:** Track all important actions in your application for compliance and debugging.

### Features
- Automatic user tracking
- IP address and user agent logging
- Before/after change tracking
- Search and filter logs
- Statistics dashboard

### Setup

Add to `main.go`:

```go
// Initialize audit logger
controllers.InitAuditLogger(dbInstance)

// Add to AutoMigrate
&models.AuditLog{},
```

### Usage

#### Log Actions

```go
// Log creation
controllers.AuditLogger.LogCreate(c, "Article", article.ID, "Article created: "+article.Title)

// Log update with changes
before := map[string]interface{}{"title": oldTitle, "published": oldPublished}
after := map[string]interface{}{"title": newTitle, "published": newPublished}
controllers.AuditLogger.LogUpdate(c, "Article", article.ID, "Article updated", before, after)

// Log deletion
controllers.AuditLogger.LogDelete(c, "Article", articleID, "Article deleted: "+title)

// Log login
controllers.AuditLogger.LogLogin(c, userID, true) // success=true

// Log custom action
controllers.AuditLogger.LogEntry(c, "EXPORT", "Article", nil, "Exported articles to CSV", nil)
```

#### API Endpoints (Admin Only)

```go
// Add to routes/routes.go admin group:
admin.GET("/audit-logs", auditLogController.GetAuditLogs)
admin.GET("/audit-logs/:id", auditLogController.GetAuditLogByID)
admin.GET("/audit-logs/entity/:entity_type/:entity_id", auditLogController.GetEntityAuditHistory)
admin.GET("/audit-logs/user/:user_id", auditLogController.GetUserActivityLog)
admin.GET("/audit-logs/stats", auditLogController.GetAuditStats)
admin.POST("/audit-logs/cleanup", auditLogController.CleanupOldLogs)
```

#### Query Audit Logs

```
GET /api/v1/admin/audit-logs?action=CREATE&entity_type=Article&user_id=1&from=2024-01-01&page=1
```

---

## Batch Operations Controller

**Purpose:** Perform bulk operations efficiently.

### Features
- Batch delete
- Batch update
- Batch publish/unpublish
- Batch reorder
- Detailed success/failure reporting

### Setup

```go
// Initialize in main.go or controller
controllers.InitBatchOperations(dbInstance)
```

### Usage

#### Batch Delete

```go
// POST /api/v1/articles/batch-delete
// Body: {"ids": [1, 2, 3, 4, 5]}
func (ac *ArticleController) BatchDeleteArticles(c *gin.Context) {
    controllers.BatchOps.BatchDelete(c, &models.Article{}, "articles")
}
```

#### Batch Update

```go
// POST /api/v1/articles/batch-update
// Body: {"ids": [1, 2, 3], "fields": {"published": true, "featured": false}}
func (ac *ArticleController) BatchUpdateArticles(c *gin.Context) {
    allowedFields := []string{"published", "featured", "category_id"}
    controllers.BatchOps.BatchUpdate(c, &models.Article{}, "articles", allowedFields)
}
```

#### Batch Publish/Unpublish

```go
// POST /api/v1/articles/batch-publish
// Body: {"ids": [1, 2, 3]}
func (ac *ArticleController) BatchPublishArticles(c *gin.Context) {
    controllers.BatchOps.BatchPublish(c, &models.Article{}, "articles", true)
}
```

#### Batch Reorder

```go
// POST /api/v1/navigation/batch-reorder
// Body: {"orders": [{"id": 1, "order": 3}, {"id": 2, "order": 1}]}
func (nc *NavigationController) BatchReorderItems(c *gin.Context) {
    controllers.BatchOps.BatchReorder(c, &models.NavigationItem{}, "navigation_items")
}
```

### Response Format

```json
{
  "success": true,
  "data": {
    "success": true,
    "total_items": 5,
    "success_count": 5,
    "failure_count": 0,
    "errors": []
  },
  "message": "Successfully deleted 5 items"
}
```

---

## Export Helper

**Purpose:** Export data to CSV or JSON format.

### Usage

#### Export to CSV

```go
func (ac *ArticleController) ExportArticlesToCSV(c *gin.Context) {
    var articles []models.Article
    if err := db.Find(&articles).Error; err != nil {
        controllers.Respond.InternalError(c, "Failed to retrieve articles")
        return
    }

    headers := []string{"ID", "Title", "Published", "Created At"}
    filename := fmt.Sprintf("articles_%s.csv", time.Now().Format("20060102"))
    
    if err := controllers.Exporter.ExportToCSV(c, articles, filename, headers); err != nil {
        controllers.Respond.InternalError(c, "Export failed")
        return
    }
}
```

#### Export to JSON

```go
func (ac *ArticleController) ExportArticlesToJSON(c *gin.Context) {
    var articles []models.Article
    if err := db.Find(&articles).Error; err != nil {
        controllers.Respond.InternalError(c, "Failed to retrieve articles")
        return
    }

    filename := fmt.Sprintf("articles_%s.json", time.Now().Format("20060102"))
    
    if err := controllers.Exporter.ExportToJSON(c, articles, filename); err != nil {
        controllers.Respond.InternalError(c, "Export failed")
        return
    }
}
```

---

## Complete Examples

### Example 1: Simple CRUD Controller

```go
package controllers

import (
    "fotbal-club/internal/models"
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

type SimpleArticleController struct {
    DB *gorm.DB
}

// List articles with search, filter, sort, and pagination
func (sac *SimpleArticleController) List(c *gin.Context) {
    query := QueryParser.BuildQueryChain(c, sac.DB.Model(&models.Article{})).
        WithSearch("title", "content").
        WithSort("created_at", "desc").
        WithBoolFilter("published", "published").
        Build()

    var articles []models.Article
    meta, err := Paginator.PaginateWithPreload(c, query, &articles, "Author", "Category")
    if err != nil {
        Respond.InternalError(c, "Failed to retrieve articles")
        return
    }

    Respond.SuccessWithMeta(c, articles, meta, "Articles retrieved successfully")
}

// Get single article
func (sac *SimpleArticleController) Get(c *gin.Context) {
    id := c.Param("id")

    var article models.Article
    if err := sac.DB.Preload("Author").Preload("Category").First(&article, id).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            Respond.NotFound(c, "Article not found")
            return
        }
        Respond.InternalError(c, "Failed to retrieve article")
        return
    }

    Respond.Success(c, article, "Article retrieved successfully")
}

// Create article
func (sac *SimpleArticleController) Create(c *gin.Context) {
    var req struct {
        Title   string `json:"title" validate:"required,min=3,max=200"`
        Content string `json:"content" validate:"required,min=10"`
        Slug    string `json:"slug" validate:"omitempty,slug"`
    }

    if err := c.ShouldBindJSON(&req); err != nil {
        Respond.BadRequest(c, "Invalid JSON")
        return
    }

    if !Validator.ValidateAndRespond(c, req) {
        return
    }

    article := models.Article{
        Title:   Validator.SanitizeString(req.Title),
        Content: req.Content,
        Slug:    Validator.SanitizeSlug(req.Slug),
    }

    if err := sac.DB.Create(&article).Error; err != nil {
        Respond.InternalError(c, "Failed to create article")
        return
    }

    AuditLogger.LogCreate(c, "Article", article.ID, "Article created: "+article.Title)
    Respond.Created(c, article, "Article created successfully")
}

// Update article
func (sac *SimpleArticleController) Update(c *gin.Context) {
    id := c.Param("id")

    var article models.Article
    if err := sac.DB.First(&article, id).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            Respond.NotFound(c, "Article not found")
            return
        }
        Respond.InternalError(c, "Failed to retrieve article")
        return
    }

    oldTitle := article.Title

    var req struct {
        Title string `json:"title" validate:"omitempty,min=3,max=200"`
    }

    if err := c.ShouldBindJSON(&req); err != nil {
        Respond.BadRequest(c, "Invalid JSON")
        return
    }

    if !Validator.ValidateAndRespond(c, req) {
        return
    }

    if req.Title != "" {
        article.Title = Validator.SanitizeString(req.Title)
    }

    if err := sac.DB.Save(&article).Error; err != nil {
        Respond.InternalError(c, "Failed to update article")
        return
    }

    AuditLogger.LogUpdate(c, "Article", article.ID, "Article updated",
        map[string]interface{}{"title": oldTitle},
        map[string]interface{}{"title": article.Title})

    Respond.Success(c, article, "Article updated successfully")
}

// Delete article
func (sac *SimpleArticleController) Delete(c *gin.Context) {
    id := c.Param("id")

    var article models.Article
    if err := sac.DB.First(&article, id).Error; err != nil {
        if err == gorm.ErrRecordNotFound {
            Respond.NotFound(c, "Article not found")
            return
        }
        Respond.InternalError(c, "Failed to retrieve article")
        return
    }

    title := article.Title
    articleID := article.ID

    if err := sac.DB.Delete(&article).Error; err != nil {
        Respond.InternalError(c, "Failed to delete article")
        return
    }

    AuditLogger.LogDelete(c, "Article", articleID, "Article deleted: "+title)
    Respond.NoContent(c)
}
```

### Example 2: Route Registration

```go
// In routes/routes.go

func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
    // Initialize helpers
    controllers.InitAuditLogger(db)
    controllers.InitBatchOperations(db)

    // Article routes with new utilities
    articles := api.Group("/articles")
    {
        articleCtrl := &controllers.SimpleArticleController{DB: db}
        
        // Public routes
        articles.GET("", articleCtrl.List)
        articles.GET("/:id", articleCtrl.Get)
        
        // Protected routes
        protected := articles.Group("")
        protected.Use(middleware.JWTAuth(db))
        {
            protected.POST("", articleCtrl.Create)
            protected.PUT("/:id", articleCtrl.Update)
            protected.DELETE("/:id", articleCtrl.Delete)
            
            // Batch operations
            protected.POST("/batch-delete", articleCtrl.BatchDelete)
            protected.POST("/batch-publish", articleCtrl.BatchPublish)
            
            // Export
            protected.GET("/export/csv", articleCtrl.ExportCSV)
            protected.GET("/export/json", articleCtrl.ExportJSON)
        }
    }
    
    // Audit logs (admin only)
    auditLogCtrl := controllers.NewAuditLogController(db)
    admin := api.Group("/admin")
    admin.Use(middleware.JWTAuth(db), middleware.RoleAuth("admin"))
    {
        admin.GET("/audit-logs", auditLogCtrl.GetAuditLogs)
        admin.GET("/audit-logs/:id", auditLogCtrl.GetAuditLogByID)
        admin.GET("/audit-logs/stats", auditLogCtrl.GetAuditStats)
        admin.POST("/audit-logs/cleanup", auditLogCtrl.CleanupOldLogs)
    }
}
```

---

## Benefits

### Before (Old Way)
```go
func GetArticles(c *gin.Context) {
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
    
    if page < 1 {
        page = 1
    }
    if pageSize < 1 || pageSize > 100 {
        pageSize = 20
    }
    
    offset := (page - 1) * pageSize
    
    var articles []models.Article
    var total int64
    
    query := db.Model(&models.Article{})
    query.Count(&total)
    query.Offset(offset).Limit(pageSize).Find(&articles)
    
    totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
    
    c.JSON(200, gin.H{
        "data": articles,
        "page": page,
        "page_size": pageSize,
        "total": total,
        "total_pages": totalPages,
    })
}
```

### After (New Way)
```go
func GetArticles(c *gin.Context) {
    query := QueryParser.BuildQueryChain(c, db.Model(&models.Article{})).
        WithSearch("title", "content").
        WithSort("created_at", "desc").
        Build()

    var articles []models.Article
    meta, _ := Paginator.Paginate(c, query, &articles)
    Respond.SuccessWithMeta(c, articles, meta, "Success")
}
```

### Comparison
- **Lines of code:** 24 → 7 (70% reduction)
- **Consistency:** Standardized across all endpoints
- **Features:** Added search, sorting, filtering
- **Error handling:** Built-in
- **Maintainability:** Single source of truth

---

## Migration Guide

### Step 1: Add Model Migration
```go
// In main.go AutoMigrate
&models.AuditLog{},
```

### Step 2: Initialize in main.go
```go
// After database initialization
controllers.InitAuditLogger(dbInstance)
controllers.InitBatchOperations(dbInstance)
```

### Step 3: Update Existing Controllers
Replace manual pagination, filtering, and response code with the new utilities.

### Step 4: Add go-playground/validator Dependency
```bash
go get github.com/go-playground/validator/v10
```

---

## Best Practices

1. **Always use standardized responses** - Use `Respond.*` methods
2. **Log important actions** - Use `AuditLogger` for CREATE, UPDATE, DELETE
3. **Validate inputs** - Use `Validator.ValidateAndRespond`
4. **Sanitize user input** - Use `Validator.Sanitize*` methods
5. **Use query chains** - Use `QueryParser.BuildQueryChain` for complex queries
6. **Paginate large lists** - Always use `Paginator` for list endpoints
7. **Batch operations** - Use `BatchOps` for bulk actions
8. **Export functionality** - Use `Exporter` for CSV/JSON exports

---

## Testing

```bash
# Test pagination
curl "http://localhost:8080/api/v1/articles?page=1&page_size=10"

# Test search
curl "http://localhost:8080/api/v1/articles?search=football"

# Test sorting
curl "http://localhost:8080/api/v1/articles?sort=created_at:desc"

# Test combined
curl "http://localhost:8080/api/v1/articles?search=football&sort=created_at:desc&published=true&page=1"

# Test batch delete
curl -X POST "http://localhost:8080/api/v1/articles/batch-delete" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": [1, 2, 3]}'

# Test audit logs
curl "http://localhost:8080/api/v1/admin/audit-logs?action=CREATE&entity_type=Article"
```

---

## Conclusion

These utility controllers dramatically reduce boilerplate code, improve consistency, and make your codebase more maintainable. They follow Go best practices and provide a solid foundation for rapid development.

**Key Improvements:**
- 70% less code for common operations
- Consistent API responses
- Built-in validation and sanitization
- Comprehensive audit logging
- Efficient batch operations
- Easy data export
- Better error handling
- Improved developer experience
