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

View 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
View 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)
}
}

View 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)
}