feat: implement core Go application with web server
- Add Go modules with required dependencies (Gin, UUID, JWT, etc.) - Implement main web server with landing page endpoint - Add comprehensive API endpoints for health and status - Include proper error handling and request validation - Set up CORS middleware and security headers
This commit is contained in:
65
output/internal/middleware/error.go
Normal file
65
output/internal/middleware/error.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ErrorHandler middleware for proper error handling
|
||||
func ErrorHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// Log the panic with stack trace
|
||||
log.Printf("Panic recovered: %v\n%s", err, debug.Stack())
|
||||
c.Error(err.(error))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Internal server error",
|
||||
"message": "Something went wrong. Please try again later.",
|
||||
"request_id": c.GetString("request_id"),
|
||||
})
|
||||
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequestID middleware for tracking requests
|
||||
func RequestID() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requestID := c.GetHeader("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
}
|
||||
c.Set("request_id", requestID)
|
||||
c.Header("X-Request-ID", requestID)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func generateRequestID() string {
|
||||
// Simple request ID generation
|
||||
return "req_" + timestamp() + "_" + randomString(8)
|
||||
}
|
||||
|
||||
func timestamp() string {
|
||||
return fmt.Sprintf("%d", time.Now().Unix())
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
180
output/internal/middleware/middleware.go
Normal file
180
output/internal/middleware/middleware.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/ydn/yourdreamnamehere/internal/config"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func AuthMiddleware(config *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
return []byte(config.JWT.Secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("email", claims.Email)
|
||||
c.Set("role", claims.Role)
|
||||
c.Next()
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RequireRole middleware for role-based access control
|
||||
func RequireRole(roles ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userRole, exists := c.Get("role")
|
||||
if !exists {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "User role not found"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userRoleStr, ok := userRole.(string)
|
||||
if !ok {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Invalid user role format"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has required role
|
||||
hasRequiredRole := false
|
||||
for _, role := range roles {
|
||||
if userRoleStr == role || userRoleStr == "admin" {
|
||||
hasRequiredRole = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasRequiredRole {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CORSMiddleware(config *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
|
||||
// Check if origin is allowed
|
||||
allowed := false
|
||||
for _, allowedOrigin := range config.Security.CORSOrigins {
|
||||
if origin == allowedOrigin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allowed {
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func RateLimitMiddleware(config *config.Config) gin.HandlerFunc {
|
||||
type client struct {
|
||||
lastRequest time.Time
|
||||
requests int
|
||||
}
|
||||
|
||||
clients := make(map[string]*client)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
clientIP := c.ClientIP()
|
||||
now := time.Now()
|
||||
|
||||
if cl, exists := clients[clientIP]; exists {
|
||||
// Reset window if expired
|
||||
if now.Sub(cl.lastRequest) > config.Security.RateLimitWindow {
|
||||
cl.requests = 0
|
||||
cl.lastRequest = now
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if cl.requests >= config.Security.RateLimitRequests {
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "Rate limit exceeded",
|
||||
"retry_after": config.Security.RateLimitWindow.Seconds(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
cl.requests++
|
||||
} else {
|
||||
clients[clientIP] = &client{
|
||||
lastRequest: now,
|
||||
requests: 1,
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func LoggingMiddleware() gin.HandlerFunc {
|
||||
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return ""
|
||||
})
|
||||
}
|
||||
|
||||
func ErrorMiddleware() gin.HandlerFunc {
|
||||
return gin.Recovery()
|
||||
}
|
||||
3
output/internal/middleware/middleware_fix.go
Normal file
3
output/internal/middleware/middleware_fix.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package middleware
|
||||
|
||||
// This file can be removed - it's empty and unused
|
||||
Reference in New Issue
Block a user