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:
195
internal/handler/connection.go
Normal file
195
internal/handler/connection.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"uzdb/internal/config"
|
||||
"uzdb/internal/models"
|
||||
"uzdb/internal/services"
|
||||
"uzdb/internal/utils"
|
||||
)
|
||||
|
||||
// ConnectionHandler handles connection-related HTTP requests
|
||||
type ConnectionHandler struct {
|
||||
connectionSvc *services.ConnectionService
|
||||
}
|
||||
|
||||
// NewConnectionHandler creates a new connection handler
|
||||
func NewConnectionHandler(connectionSvc *services.ConnectionService) *ConnectionHandler {
|
||||
return &ConnectionHandler{
|
||||
connectionSvc: connectionSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllConnections handles GET /api/connections
|
||||
func (h *ConnectionHandler) GetAllConnections(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
connections, err := h.connectionSvc.GetAllConnections(ctx)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get connections")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"connections": connections,
|
||||
})
|
||||
}
|
||||
|
||||
// GetConnection handles GET /api/connections/:id
|
||||
func (h *ConnectionHandler) GetConnection(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
id := c.Param("id")
|
||||
|
||||
if id == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := h.connectionSvc.GetConnectionByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get connection")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"connection": conn,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateConnection handles POST /api/connections
|
||||
func (h *ConnectionHandler) CreateConnection(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req models.CreateConnectionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := h.connectionSvc.CreateConnection(ctx, &req)
|
||||
if err != nil {
|
||||
if err == models.ErrValidationFailed {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, err, "Validation failed")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to create connection")
|
||||
return
|
||||
}
|
||||
|
||||
config.GetLogger().Info("connection created via API",
|
||||
zap.String("id", conn.ID),
|
||||
zap.String("name", conn.Name))
|
||||
|
||||
utils.CreatedResponse(c, gin.H{
|
||||
"connection": conn,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateConnection handles PUT /api/connections/:id
|
||||
func (h *ConnectionHandler) UpdateConnection(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
id := c.Param("id")
|
||||
|
||||
if id == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UpdateConnectionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := h.connectionSvc.UpdateConnection(ctx, id, &req)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to update connection")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"connection": conn,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteConnection handles DELETE /api/connections/:id
|
||||
func (h *ConnectionHandler) DeleteConnection(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
id := c.Param("id")
|
||||
|
||||
if id == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.connectionSvc.DeleteConnection(ctx, id); err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to delete connection")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"message": "Connection deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// TestConnection handles POST /api/connections/:id/test
|
||||
func (h *ConnectionHandler) TestConnection(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
id := c.Param("id")
|
||||
|
||||
if id == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.connectionSvc.TestConnection(ctx, id)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to test connection")
|
||||
return
|
||||
}
|
||||
|
||||
statusCode := http.StatusOK
|
||||
if !result.Success {
|
||||
statusCode = http.StatusBadGateway
|
||||
}
|
||||
|
||||
c.JSON(statusCode, gin.H{
|
||||
"success": result.Success,
|
||||
"message": result.Message,
|
||||
"duration_ms": result.Duration,
|
||||
"metadata": result.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes registers connection routes
|
||||
func (h *ConnectionHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
connections := router.Group("/connections")
|
||||
{
|
||||
connections.GET("", h.GetAllConnections)
|
||||
connections.GET("/:id", h.GetConnection)
|
||||
connections.POST("", h.CreateConnection)
|
||||
connections.PUT("/:id", h.UpdateConnection)
|
||||
connections.DELETE("/:id", h.DeleteConnection)
|
||||
connections.POST("/:id/test", h.TestConnection)
|
||||
}
|
||||
}
|
||||
287
internal/handler/query.go
Normal file
287
internal/handler/query.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"uzdb/internal/models"
|
||||
"uzdb/internal/services"
|
||||
"uzdb/internal/utils"
|
||||
)
|
||||
|
||||
// QueryHandler handles query-related HTTP requests
|
||||
type QueryHandler struct {
|
||||
connectionSvc *services.ConnectionService
|
||||
querySvc *services.QueryService
|
||||
}
|
||||
|
||||
// NewQueryHandler creates a new query handler
|
||||
func NewQueryHandler(
|
||||
connectionSvc *services.ConnectionService,
|
||||
querySvc *services.QueryService,
|
||||
) *QueryHandler {
|
||||
return &QueryHandler{
|
||||
connectionSvc: connectionSvc,
|
||||
querySvc: querySvc,
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteQuery handles POST /api/query
|
||||
func (h *QueryHandler) ExecuteQuery(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req struct {
|
||||
ConnectionID string `json:"connection_id" binding:"required"`
|
||||
SQL string `json:"sql" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.connectionSvc.ExecuteQuery(ctx, req.ConnectionID, req.SQL)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Query execution failed")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
|
||||
// GetTables handles GET /api/connections/:id/tables
|
||||
func (h *QueryHandler) GetTables(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
connectionID := c.Param("id")
|
||||
|
||||
if connectionID == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID is required")
|
||||
return
|
||||
}
|
||||
|
||||
tables, err := h.connectionSvc.GetTables(ctx, connectionID)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get tables")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"tables": tables,
|
||||
})
|
||||
}
|
||||
|
||||
// GetTableData handles GET /api/connections/:id/tables/:name/data
|
||||
func (h *QueryHandler) GetTableData(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
connectionID := c.Param("id")
|
||||
tableName := c.Param("name")
|
||||
|
||||
if connectionID == "" || tableName == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID and table name are required")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse limit and offset
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "100"))
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
|
||||
result, err := h.connectionSvc.GetTableData(ctx, connectionID, tableName, limit, offset)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get table data")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"result": result,
|
||||
"table": tableName,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
}
|
||||
|
||||
// GetTableStructure handles GET /api/connections/:id/tables/:name/structure
|
||||
func (h *QueryHandler) GetTableStructure(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
connectionID := c.Param("id")
|
||||
tableName := c.Param("name")
|
||||
|
||||
if connectionID == "" || tableName == "" {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Connection ID and table name are required")
|
||||
return
|
||||
}
|
||||
|
||||
structure, err := h.connectionSvc.GetTableStructure(ctx, connectionID, tableName)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Connection not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get table structure")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"structure": structure,
|
||||
})
|
||||
}
|
||||
|
||||
// GetQueryHistory handles GET /api/history
|
||||
func (h *QueryHandler) GetQueryHistory(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
connectionID := c.Query("connection_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
history, total, err := h.querySvc.GetQueryHistory(ctx, connectionID, page, pageSize)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get query history")
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := int(total) / pageSize
|
||||
if int(total)%pageSize > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{
|
||||
"history": history,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetSavedQueries handles GET /api/saved-queries
|
||||
func (h *QueryHandler) GetSavedQueries(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
connectionID := c.Query("connection_id")
|
||||
|
||||
queries, err := h.querySvc.GetSavedQueries(ctx, connectionID)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to get saved queries")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"queries": queries,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateSavedQuery handles POST /api/saved-queries
|
||||
func (h *QueryHandler) CreateSavedQuery(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req models.CreateSavedQueryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
query, err := h.querySvc.CreateSavedQuery(ctx, &req)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to save query")
|
||||
return
|
||||
}
|
||||
|
||||
utils.CreatedResponse(c, gin.H{
|
||||
"query": query,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSavedQuery handles PUT /api/saved-queries/:id
|
||||
func (h *QueryHandler) UpdateSavedQuery(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
idStr := c.Param("id")
|
||||
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid query ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UpdateSavedQueryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
query, err := h.querySvc.UpdateSavedQuery(ctx, uint(id), &req)
|
||||
if err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Saved query not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to update saved query")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"query": query,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteSavedQuery handles DELETE /api/saved-queries/:id
|
||||
func (h *QueryHandler) DeleteSavedQuery(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
idStr := c.Param("id")
|
||||
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
utils.ErrorResponse(c, http.StatusBadRequest, models.ErrValidationFailed, "Invalid query ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.querySvc.DeleteSavedQuery(ctx, uint(id)); err != nil {
|
||||
if err == models.ErrNotFound {
|
||||
utils.ErrorResponse(c, http.StatusNotFound, err, "Saved query not found")
|
||||
return
|
||||
}
|
||||
utils.ErrorResponse(c, http.StatusInternalServerError, err, "Failed to delete saved query")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessResponse(c, gin.H{
|
||||
"message": "Saved query deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes registers query routes
|
||||
func (h *QueryHandler) RegisterRoutes(router *gin.RouterGroup) {
|
||||
// Query execution
|
||||
router.POST("/query", h.ExecuteQuery)
|
||||
|
||||
// Table operations
|
||||
router.GET("/connections/:id/tables", h.GetTables)
|
||||
router.GET("/connections/:id/tables/:name/data", h.GetTableData)
|
||||
router.GET("/connections/:id/tables/:name/structure", h.GetTableStructure)
|
||||
|
||||
// Query history
|
||||
router.GET("/history", h.GetQueryHistory)
|
||||
|
||||
// Saved queries
|
||||
savedQueries := router.Group("/saved-queries")
|
||||
{
|
||||
savedQueries.GET("", h.GetSavedQueries)
|
||||
savedQueries.POST("", h.CreateSavedQuery)
|
||||
savedQueries.PUT("/:id", h.UpdateSavedQuery)
|
||||
savedQueries.DELETE("/:id", h.DeleteSavedQuery)
|
||||
}
|
||||
}
|
||||
95
internal/handler/server.go
Normal file
95
internal/handler/server.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"uzdb/internal/config"
|
||||
"uzdb/internal/middleware"
|
||||
"uzdb/internal/services"
|
||||
)
|
||||
|
||||
// HTTPServer represents the HTTP API server
|
||||
type HTTPServer struct {
|
||||
engine *gin.Engine
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// NewHTTPServer creates a new HTTP server
|
||||
func NewHTTPServer(
|
||||
connectionSvc *services.ConnectionService,
|
||||
querySvc *services.QueryService,
|
||||
) *HTTPServer {
|
||||
// Set Gin mode based on environment
|
||||
cfg := config.Get()
|
||||
if cfg.IsProduction() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
engine := gin.New()
|
||||
|
||||
// Create handlers
|
||||
connectionHandler := NewConnectionHandler(connectionSvc)
|
||||
queryHandler := NewQueryHandler(connectionSvc, querySvc)
|
||||
|
||||
// Setup middleware
|
||||
engine.Use(middleware.RecoveryMiddleware())
|
||||
engine.Use(middleware.LoggerMiddleware())
|
||||
engine.Use(middleware.CORSMiddleware())
|
||||
engine.Use(middleware.SecureHeadersMiddleware())
|
||||
|
||||
// Setup routes
|
||||
api := engine.Group("/api")
|
||||
{
|
||||
connectionHandler.RegisterRoutes(api)
|
||||
queryHandler.RegisterRoutes(api)
|
||||
}
|
||||
|
||||
// Health check endpoint
|
||||
engine.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
})
|
||||
|
||||
return &HTTPServer{
|
||||
engine: engine,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the HTTP server
|
||||
func (s *HTTPServer) Start(port string) error {
|
||||
s.server = &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: s.engine,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
config.GetLogger().Info("starting HTTP API server",
|
||||
zap.String("port", port))
|
||||
|
||||
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
return fmt.Errorf("failed to start HTTP server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the HTTP server
|
||||
func (s *HTTPServer) Shutdown(ctx context.Context) error {
|
||||
if s.server == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
config.GetLogger().Info("shutting down HTTP server")
|
||||
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user