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:
136
internal/utils/errors.go
Normal file
136
internal/utils/errors.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateID generates a unique ID (UUID-like)
|
||||
func GenerateID() string {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
return formatUUID(b)
|
||||
}
|
||||
|
||||
// formatUUID formats bytes as UUID string
|
||||
func formatUUID(b []byte) string {
|
||||
uuid := make([]byte, 36)
|
||||
hex.Encode(uuid[0:8], b[0:4])
|
||||
hex.Encode(uuid[9:13], b[4:6])
|
||||
hex.Encode(uuid[14:18], b[6:8])
|
||||
hex.Encode(uuid[19:23], b[8:10])
|
||||
hex.Encode(uuid[24:], b[10:])
|
||||
uuid[8] = '-'
|
||||
uuid[13] = '-'
|
||||
uuid[18] = '-'
|
||||
uuid[23] = '-'
|
||||
return string(uuid)
|
||||
}
|
||||
|
||||
// FormatDuration formats duration in milliseconds
|
||||
func FormatDuration(d time.Duration) int64 {
|
||||
return d.Milliseconds()
|
||||
}
|
||||
|
||||
// SanitizeSQL removes potentially dangerous SQL patterns
|
||||
// Note: This is not a replacement for parameterized queries
|
||||
func SanitizeSQL(sql string) string {
|
||||
// Remove multiple semicolons
|
||||
sql = strings.ReplaceAll(sql, ";;", ";")
|
||||
// Trim whitespace
|
||||
sql = strings.TrimSpace(sql)
|
||||
return sql
|
||||
}
|
||||
|
||||
// TruncateString truncates a string to max length
|
||||
func TruncateString(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen]
|
||||
}
|
||||
|
||||
// GenerateRandomBytes generates random bytes
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate random bytes: %w", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// EncodeBase64 encodes bytes to base64 string
|
||||
func EncodeBase64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// DecodeBase64 decodes base64 string to bytes
|
||||
func DecodeBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// MaskPassword masks password for logging
|
||||
func MaskPassword(password string) string {
|
||||
if len(password) <= 4 {
|
||||
return strings.Repeat("*", len(password))
|
||||
}
|
||||
return password[:2] + strings.Repeat("*", len(password)-2)
|
||||
}
|
||||
|
||||
// ContainsAny checks if string contains any of the substrings
|
||||
func ContainsAny(s string, substrings ...string) bool {
|
||||
for _, sub := range substrings {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReadOnlyQuery checks if SQL query is read-only
|
||||
func IsReadOnlyQuery(sql string) bool {
|
||||
sql = strings.TrimSpace(strings.ToUpper(sql))
|
||||
|
||||
// Read-only operations
|
||||
readOnlyPrefixes := []string{
|
||||
"SELECT",
|
||||
"SHOW",
|
||||
"DESCRIBE",
|
||||
"EXPLAIN",
|
||||
"WITH",
|
||||
}
|
||||
|
||||
for _, prefix := range readOnlyPrefixes {
|
||||
if strings.HasPrefix(sql, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDDLQuery checks if SQL query is DDL
|
||||
func IsDDLQuery(sql string) bool {
|
||||
sql = strings.TrimSpace(strings.ToUpper(sql))
|
||||
|
||||
ddlKeywords := []string{
|
||||
"CREATE",
|
||||
"ALTER",
|
||||
"DROP",
|
||||
"TRUNCATE",
|
||||
"RENAME",
|
||||
}
|
||||
|
||||
for _, keyword := range ddlKeywords {
|
||||
if strings.HasPrefix(sql, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
102
internal/utils/response.go
Normal file
102
internal/utils/response.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"uzdb/internal/config"
|
||||
"uzdb/internal/models"
|
||||
)
|
||||
|
||||
// ErrorResponse sends an error response
|
||||
func ErrorResponse(c *gin.Context, statusCode int, err error, message string) {
|
||||
logger := config.GetLogger()
|
||||
|
||||
response := models.ErrorResponse{
|
||||
Error: getErrorCode(err),
|
||||
Message: message,
|
||||
Timestamp: time.Now(),
|
||||
Path: c.Request.URL.Path,
|
||||
}
|
||||
|
||||
if message == "" {
|
||||
response.Message = err.Error()
|
||||
}
|
||||
|
||||
// Log the error
|
||||
logger.Error("error response",
|
||||
zap.Int("status_code", statusCode),
|
||||
zap.String("error", response.Error),
|
||||
zap.String("message", response.Message),
|
||||
zap.String("path", response.Path),
|
||||
)
|
||||
|
||||
c.JSON(statusCode, response)
|
||||
}
|
||||
|
||||
// SuccessResponse sends a success response
|
||||
func SuccessResponse(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, models.NewAPIResponse(data))
|
||||
}
|
||||
|
||||
// CreatedResponse sends a created response
|
||||
func CreatedResponse(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusCreated, models.NewAPIResponse(data))
|
||||
}
|
||||
|
||||
// getErrorCode maps errors to error codes
|
||||
func getErrorCode(err error) string {
|
||||
if err == nil {
|
||||
return string(models.CodeSuccess)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// WrapError wraps an error with context
|
||||
func WrapError(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &wrappedError{
|
||||
err: err,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
type wrappedError struct {
|
||||
err error
|
||||
message string
|
||||
}
|
||||
|
||||
func (w *wrappedError) Error() string {
|
||||
if w.message != "" {
|
||||
return w.message + ": " + w.err.Error()
|
||||
}
|
||||
return w.err.Error()
|
||||
}
|
||||
|
||||
func (w *wrappedError) Unwrap() error {
|
||||
return w.err
|
||||
}
|
||||
Reference in New Issue
Block a user