refactor: Flatten directory structure

Move project files from uzdb/ subdirectory to root directory for cleaner project structure.

Changes:
- Move frontend/ to root
- Move internal/ to root
- Move build/ to root
- Move all config files (go.mod, wails.json, etc.) to root
- Remove redundant uzdb/ subdirectory nesting

Project structure is now:
├── frontend/        # React application
├── internal/        # Go backend
├── build/           # Wails build assets
├── doc/             # Design documentation
├── main.go          # Entry point
└── ...

🤖 Generated with Qoder
This commit is contained in:
loveuer
2026-04-04 07:14:00 -07:00
parent 5a83e86bc9
commit 9874561410
83 changed files with 0 additions and 46 deletions

101
internal/middleware/cors.go Normal file
View File

@@ -0,0 +1,101 @@
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"uzdb/internal/config"
)
// CORSMiddleware returns a CORS middleware
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
cfg := config.Get()
// Set CORS headers
c.Header("Access-Control-Allow-Origin", getAllowedOrigin(c, cfg))
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-Request-ID")
c.Header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, X-Request-ID")
c.Header("Access-Control-Max-Age", "43200") // 12 hours
// Handle preflight requests
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// getAllowedOrigin returns the allowed origin based on configuration
func getAllowedOrigin(c *gin.Context, cfg *config.Config) string {
origin := c.GetHeader("Origin")
// If no origin header, return empty (same-origin)
if origin == "" {
return ""
}
// In development mode, allow all origins
if cfg.IsDevelopment() {
return origin
}
// In production, validate against allowed origins
allowedOrigins := []string{
"http://localhost:3000",
"http://localhost:8080",
"http://127.0.0.1:3000",
"http://127.0.0.1:8080",
}
for _, allowed := range allowedOrigins {
if origin == allowed {
return origin
}
}
// Check for wildcard patterns
for _, allowed := range allowedOrigins {
if strings.HasSuffix(allowed, "*") {
prefix := strings.TrimSuffix(allowed, "*")
if strings.HasPrefix(origin, prefix) {
return origin
}
}
}
// Default to empty (deny) in production if not matched
if cfg.IsProduction() {
return ""
}
return origin
}
// SecureHeadersMiddleware adds security-related headers
func SecureHeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Prevent MIME type sniffing
c.Header("X-Content-Type-Options", "nosniff")
// Enable XSS filter
c.Header("X-XSS-Protection", "1; mode=block")
// Prevent clickjacking
c.Header("X-Frame-Options", "DENY")
// Referrer policy
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
// Content Security Policy (adjust as needed)
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'")
c.Next()
}
}

View File

@@ -0,0 +1,125 @@
package middleware
import (
"errors"
"net/http"
"runtime/debug"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"uzdb/internal/config"
"uzdb/internal/models"
)
// RecoveryMiddleware returns a recovery middleware that handles panics
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
logger := config.GetLogger()
// Log the panic with stack trace
logger.Error("panic recovered",
zap.Any("error", err),
zap.String("stack", string(debug.Stack())),
zap.String("path", c.Request.URL.Path),
zap.String("method", c.Request.Method),
)
// Send error response
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
Error: "INTERNAL_ERROR",
Message: "An unexpected error occurred",
Timestamp: time.Now(),
Path: c.Request.URL.Path,
})
c.Abort()
}
}()
c.Next()
}
}
// ErrorMiddleware returns an error handling middleware
func ErrorMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// Check if there are any errors
if len(c.Errors) > 0 {
handleErrors(c)
}
}
}
// handleErrors processes and formats errors
func handleErrors(c *gin.Context) {
logger := config.GetLogger()
for _, e := range c.Errors {
err := e.Err
// Log the error
logger.Error("request error",
zap.String("error", err.Error()),
zap.Any("type", e.Type),
zap.String("path", c.Request.URL.Path),
)
// Determine status code
statusCode := http.StatusInternalServerError
// Map specific errors to status codes
switch {
case errors.Is(err, models.ErrNotFound):
statusCode = http.StatusNotFound
case errors.Is(err, models.ErrValidationFailed):
statusCode = http.StatusBadRequest
case errors.Is(err, models.ErrUnauthorized):
statusCode = http.StatusUnauthorized
case errors.Is(err, models.ErrForbidden):
statusCode = http.StatusForbidden
case errors.Is(err, models.ErrConnectionFailed):
statusCode = http.StatusBadGateway
}
// Send response if not already sent
if !c.Writer.Written() {
c.JSON(statusCode, models.ErrorResponse{
Error: getErrorCode(err),
Message: err.Error(),
Timestamp: time.Now(),
Path: c.Request.URL.Path,
})
}
// Only handle first error
break
}
}
// getErrorCode maps errors to error codes
func getErrorCode(err error) string {
switch {
case errors.Is(err, models.ErrNotFound):
return string(models.CodeNotFound)
case errors.Is(err, models.ErrValidationFailed):
return string(models.CodeValidation)
case errors.Is(err, models.ErrUnauthorized):
return string(models.CodeUnauthorized)
case errors.Is(err, models.ErrForbidden):
return string(models.CodeForbidden)
case errors.Is(err, models.ErrConnectionFailed):
return string(models.CodeConnection)
case errors.Is(err, models.ErrQueryFailed):
return string(models.CodeQuery)
case errors.Is(err, models.ErrEncryptionFailed):
return string(models.CodeEncryption)
default:
return string(models.CodeInternal)
}
}

View File

@@ -0,0 +1,98 @@
package middleware
import (
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"uzdb/internal/config"
)
// LoggerMiddleware returns a logging middleware using Zap
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
logger := config.GetLogger()
// Start timer
start := time.Now()
// Process request
c.Next()
// Calculate duration
duration := time.Since(start)
// Get client IP
clientIP := c.ClientIP()
// Get method
method := c.Request.Method
// Get path
path := c.Request.URL.Path
// Get status code
statusCode := c.Writer.Status()
// Get body size
bodySize := c.Writer.Size()
// Determine log level based on status code
var level zapcore.Level
switch {
case statusCode >= 500:
level = zapcore.ErrorLevel
case statusCode >= 400:
level = zapcore.WarnLevel
default:
level = zapcore.InfoLevel
}
// Create fields
fields := []zap.Field{
zap.Int("status", statusCode),
zap.String("method", method),
zap.String("path", path),
zap.String("ip", clientIP),
zap.Duration("duration", duration),
zap.Int("body_size", bodySize),
}
// Add error message if any
if len(c.Errors) > 0 {
fields = append(fields, zap.String("error", c.Errors.String()))
}
// Log the request
logger.Log(level, "HTTP request", fields...)
}
}
// RequestIDMiddleware adds a request ID to each request
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Try to get request ID from header
requestID := c.GetHeader("X-Request-ID")
// Generate if not present
if requestID == "" {
requestID = generateRequestID()
}
// Set request ID in context
c.Set("request_id", requestID)
// Add to response header
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// generateRequestID generates a simple request ID
func generateRequestID() string {
return time.Now().Format("20060102150405") + "-" +
time.Now().Format("000000.000000")[7:]
}