mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools
Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
* GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
* GET /api/v1/users/me/organizations - User organizations
* POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue
API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules
Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
9.5 KiB
9.5 KiB
Gin Framework Development Guide
Library: github.com/gin-gonic/gin
Used In: MVP - HTTP API server
Purpose: HTTP web framework for building RESTful APIs
Where It's Used
- Primary HTTP framework for API endpoints
- Business registration, resource flow CRUD operations
- Match retrieval endpoints
- Authentication middleware
Official Documentation
- GitHub: https://github.com/gin-gonic/gin
- Official Docs: https://gin-gonic.com/docs/
- GoDoc: https://pkg.go.dev/github.com/gin-gonic/gin
- Examples: https://github.com/gin-gonic/gin/tree/master/examples
Installation
go get github.com/gin-gonic/gin
Key Concepts
1. Basic Server Setup
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // Includes Logger and Recovery middleware
// Or minimal setup
// r := gin.New()
// r.Use(gin.Logger())
// r.Use(gin.Recovery())
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
r.Run(":8080")
}
2. Routes and Handlers
// GET request
r.GET("/api/businesses/:id", getBusiness)
// POST request
r.POST("/api/businesses", createBusiness)
// PUT request
r.PUT("/api/businesses/:id", updateBusiness)
// DELETE request
r.DELETE("/api/businesses/:id", deleteBusiness)
// Handler function
func getBusiness(c *gin.Context) {
id := c.Param("id") // Get path parameter
// ... business logic
c.JSON(200, business)
}
3. Request Binding
// JSON binding
type BusinessRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
func createBusiness(c *gin.Context) {
var req BusinessRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// ... process request
c.JSON(201, business)
}
// Query parameters
func searchBusinesses(c *gin.Context) {
name := c.Query("name") // ?name=...
page := c.DefaultQuery("page", "1") // ?page=... (default: "1")
// ...
}
// Path parameters
func getBusiness(c *gin.Context) {
id := c.Param("id") // /businesses/:id
// ...
}
4. Middleware
// Global middleware
r.Use(corsMiddleware())
r.Use(authMiddleware())
// Route-specific middleware
r.GET("/protected", authMiddleware(), protectedHandler)
// Custom middleware
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
// Validate token
c.Set("userID", userID) // Store in context
c.Next() // Continue to next handler
}
}
// Logging middleware (built-in)
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
5. Error Handling
func handleBusiness(c *gin.Context) {
business, err := service.GetBusiness(c.Param("id"))
if err != nil {
if err == ErrNotFound {
c.JSON(404, gin.H{"error": "Business not found"})
return
}
c.JSON(500, gin.H{"error": "Internal server error"})
return
}
c.JSON(200, business)
}
// Custom error handler
func errorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
c.JSON(500, gin.H{"errors": c.Errors})
}
}
}
6. Grouping Routes
api := r.Group("/api")
{
// All routes prefixed with /api
api.GET("/businesses", listBusinesses)
// Nested groups
v1 := api.Group("/v1")
{
v1.GET("/businesses", listBusinessesV1)
v1.POST("/businesses", createBusinessV1)
}
v2 := api.Group("/v2")
{
v2.GET("/businesses", listBusinessesV2)
}
}
// With middleware
authenticated := r.Group("/api")
authenticated.Use(authMiddleware())
{
authenticated.GET("/profile", getProfile)
authenticated.POST("/resources", createResource)
}
7. Context Usage
// Get values from context (set by middleware)
userID := c.MustGet("userID").(string)
// Set value in context
c.Set("businessID", businessID)
// Get with default
page, exists := c.Get("page")
if !exists {
page = 1
}
// Request context (for cancellation, timeouts)
ctx := c.Request.Context()
8. File Upload
func uploadFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Save file
if err := c.SaveUploadedFile(file, "/uploads/"+file.Filename); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "File uploaded"})
}
MVP-Specific Patterns
Resource Flow Handler Example
type ResourceFlowHandler struct {
service *ResourceFlowService
}
func (h *ResourceFlowHandler) Create(c *gin.Context) {
var req CreateResourceFlowRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Validate business belongs to user
userID := c.MustGet("userID").(string)
if !h.service.ValidateOwnership(req.BusinessID, userID) {
c.JSON(403, gin.H{"error": "Forbidden"})
return
}
flow, err := h.service.Create(c.Request.Context(), req)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(201, flow)
}
func (h *ResourceFlowHandler) FindMatches(c *gin.Context) {
flowID := c.Param("id")
matches, err := h.service.FindMatches(c.Request.Context(), flowID)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"matches": matches})
}
// Register routes
func setupRoutes(r *gin.Engine, handlers *Handlers) {
api := r.Group("/api/v1")
resources := api.Group("/resource-flows")
resources.Use(authMiddleware())
{
resources.POST("", handlers.ResourceFlow.Create)
resources.GET("/:id/matches", handlers.ResourceFlow.FindMatches)
}
}
Middleware for Our MVP
// CORS middleware
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// Rate limiting (simple in-memory)
func rateLimitMiddleware() gin.HandlerFunc {
// Use token bucket or sliding window
return func(c *gin.Context) {
// Check rate limit
c.Next()
}
}
Performance Tips
- Use
gin.New()instead ofgin.Default()if you don't need logger/recovery - Bind only what you need - avoid binding large structs
- Use context for cancellation -
c.Request.Context()for timeouts - Reuse gin.Engine - don't create new engine per request
- Use
c.Set()andc.Get()for request-scoped values
Tutorials & Resources
- Official Examples: https://github.com/gin-gonic/gin/tree/master/examples
- Go by Example - Gin: https://gobyexample.com/web-servers (Gin section)
- Building REST APIs: https://www.alexedwards.net/blog/go-rest-api-structure
- Testing Gin Handlers: https://gin-gonic.com/docs/testing/
Common Patterns
Graceful Shutdown
func main() {
r := gin.Default()
// ... setup routes ...
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}
Structured Logging with Gin
import "github.com/rs/zerolog/log"
func loggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
log.Info().
Str("method", c.Request.Method).
Str("path", path).
Int("status", c.Writer.Status()).
Dur("latency", time.Since(start)).
Msg("HTTP request")
}
}
Best Practices
- Don't use global variables - pass dependencies via handlers
- Use dependency injection - inject services into handlers
- Validate early - use binding validation
- Handle errors consistently - create error response helpers
- Use middleware for cross-cutting concerns - auth, logging, CORS
- Version your API - use route groups (
/api/v1,/api/v2) - Document your API - consider OpenAPI/Swagger integration