mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
784 lines
20 KiB
Markdown
784 lines
20 KiB
Markdown
# 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
|