feat: implement NewConnectionDialog component for creating and editing database connections with form validation chore: generate TypeScript definitions and JavaScript bindings for app functions chore: add models for configuration, connection requests, and database entities
360 lines
9.1 KiB
Go
360 lines
9.1 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"uzdb/internal/config"
|
|
"uzdb/internal/database"
|
|
"uzdb/internal/handler"
|
|
"uzdb/internal/models"
|
|
"uzdb/internal/services"
|
|
)
|
|
|
|
// rootErrMsg unwraps the full error chain and returns the deepest error message.
|
|
func rootErrMsg(err error) string {
|
|
for {
|
|
unwrapped := errors.Unwrap(err)
|
|
if unwrapped == nil {
|
|
return err.Error()
|
|
}
|
|
err = unwrapped
|
|
}
|
|
}
|
|
|
|
// App is the main application structure for Wails bindings
|
|
type App struct {
|
|
ctx context.Context
|
|
config *config.Config
|
|
connectionSvc *services.ConnectionService
|
|
querySvc *services.QueryService
|
|
httpServer *handler.HTTPServer
|
|
shutdownFunc context.CancelFunc
|
|
}
|
|
|
|
// NewApp creates a new App instance
|
|
func NewApp() *App {
|
|
return &App{}
|
|
}
|
|
|
|
// Initialize initializes the application with all services
|
|
func (a *App) Initialize(
|
|
cfg *config.Config,
|
|
connectionSvc *services.ConnectionService,
|
|
querySvc *services.QueryService,
|
|
httpServer *handler.HTTPServer,
|
|
) {
|
|
a.config = cfg
|
|
a.connectionSvc = connectionSvc
|
|
a.querySvc = querySvc
|
|
a.httpServer = httpServer
|
|
}
|
|
|
|
// OnStartup is called when the app starts (public method for Wails)
|
|
func (a *App) OnStartup(ctx context.Context) {
|
|
a.ctx = ctx
|
|
|
|
config.GetLogger().Info("Wails application started")
|
|
}
|
|
|
|
// GetConnections returns all user connections
|
|
// Wails binding: frontend can call window.go.app.GetConnections()
|
|
func (a *App) GetConnections() []models.UserConnection {
|
|
if a.connectionSvc == nil {
|
|
return []models.UserConnection{}
|
|
}
|
|
|
|
connections, err := a.connectionSvc.GetAllConnections(a.ctx)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get connections", zap.Error(err))
|
|
return []models.UserConnection{}
|
|
}
|
|
|
|
return connections
|
|
}
|
|
|
|
// CreateConnection creates a new database connection
|
|
// Returns error message or empty string on success
|
|
func (a *App) CreateConnection(conn models.CreateConnectionRequest) string {
|
|
if a.connectionSvc == nil {
|
|
return "Service not initialized"
|
|
}
|
|
|
|
_, err := a.connectionSvc.CreateConnection(a.ctx, &conn)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to create connection", zap.Error(err))
|
|
return err.Error()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// UpdateConnection updates an existing database connection
|
|
// Returns error message or empty string on success
|
|
func (a *App) UpdateConnection(conn models.UserConnection) string {
|
|
if a.connectionSvc == nil {
|
|
return "Service not initialized"
|
|
}
|
|
|
|
req := &models.UpdateConnectionRequest{
|
|
Name: conn.Name,
|
|
Type: conn.Type,
|
|
Host: conn.Host,
|
|
Port: conn.Port,
|
|
Username: conn.Username,
|
|
Password: conn.Password,
|
|
Database: conn.Database,
|
|
SSLMode: conn.SSLMode,
|
|
Timeout: conn.Timeout,
|
|
}
|
|
|
|
_, err := a.connectionSvc.UpdateConnection(a.ctx, conn.ID, req)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to update connection", zap.Error(err))
|
|
return err.Error()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// DeleteConnection deletes a database connection
|
|
// Returns error message or empty string on success
|
|
func (a *App) DeleteConnection(id string) string {
|
|
if a.connectionSvc == nil {
|
|
return "Service not initialized"
|
|
}
|
|
|
|
err := a.connectionSvc.DeleteConnection(a.ctx, id)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to delete connection", zap.Error(err))
|
|
return err.Error()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// DisconnectConnection removes an active connection from the connection manager
|
|
// Returns error message or empty string on success
|
|
func (a *App) DisconnectConnection(id string) string {
|
|
if a.connectionSvc == nil {
|
|
return "Service not initialized"
|
|
}
|
|
|
|
err := a.connectionSvc.DisconnectConnection(a.ctx, id)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to disconnect connection",
|
|
zap.String("id", id),
|
|
zap.Error(err))
|
|
return err.Error()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// TestConnection tests a database connection
|
|
// Returns (success, error_message)
|
|
func (a *App) TestConnection(id string) (bool, string) {
|
|
if a.connectionSvc == nil {
|
|
return false, "Service not initialized"
|
|
}
|
|
|
|
result, err := a.connectionSvc.TestConnection(a.ctx, id)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to test connection", zap.Error(err))
|
|
return false, err.Error()
|
|
}
|
|
|
|
return result.Success, func() string {
|
|
if result.Success {
|
|
return ""
|
|
}
|
|
return result.Message
|
|
}()
|
|
}
|
|
|
|
// ExecuteQuery executes a SQL query on a connection
|
|
// Returns query result or error message
|
|
func (a *App) ExecuteQuery(connectionID, sql string) (*models.QueryResult, string) {
|
|
if a.connectionSvc == nil {
|
|
return &models.QueryResult{Success: false, Error: "Service not initialized"}, ""
|
|
}
|
|
|
|
result, err := a.connectionSvc.ExecuteQuery(a.ctx, connectionID, sql)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to execute query",
|
|
zap.String("connection_id", connectionID),
|
|
zap.String("sql", sql),
|
|
zap.Error(err))
|
|
return &models.QueryResult{Success: false, Error: rootErrMsg(err)}, ""
|
|
}
|
|
|
|
return result, ""
|
|
}
|
|
|
|
// GetTables returns all tables for a connection
|
|
func (a *App) GetTables(connectionID string) ([]models.Table, string) {
|
|
if a.connectionSvc == nil {
|
|
return []models.Table{}, "Service not initialized"
|
|
}
|
|
|
|
tables, err := a.connectionSvc.GetTables(a.ctx, connectionID)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get tables",
|
|
zap.String("connection_id", connectionID),
|
|
zap.Error(err))
|
|
return []models.Table{}, err.Error()
|
|
}
|
|
|
|
return tables, ""
|
|
}
|
|
|
|
// GetTableData returns data from a table
|
|
func (a *App) GetTableData(connectionID, tableName string, limit, offset int) (*models.QueryResult, string) {
|
|
if a.connectionSvc == nil {
|
|
return nil, "Service not initialized"
|
|
}
|
|
|
|
result, err := a.connectionSvc.GetTableData(a.ctx, connectionID, tableName, limit, offset)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get table data",
|
|
zap.String("connection_id", connectionID),
|
|
zap.String("table", tableName),
|
|
zap.Error(err))
|
|
return nil, rootErrMsg(err)
|
|
}
|
|
|
|
return result, ""
|
|
}
|
|
|
|
// GetTableStructure returns the structure of a table
|
|
func (a *App) GetTableStructure(connectionID, tableName string) (*models.TableStructure, string) {
|
|
if a.connectionSvc == nil {
|
|
return nil, "Service not initialized"
|
|
}
|
|
|
|
structure, err := a.connectionSvc.GetTableStructure(a.ctx, connectionID, tableName)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get table structure",
|
|
zap.String("connection_id", connectionID),
|
|
zap.String("table", tableName),
|
|
zap.Error(err))
|
|
return nil, err.Error()
|
|
}
|
|
|
|
return structure, ""
|
|
}
|
|
|
|
// GetQueryHistory returns query history with pagination
|
|
func (a *App) GetQueryHistory(connectionID string, page, pageSize int) ([]models.QueryHistory, int64, string) {
|
|
if a.querySvc == nil {
|
|
return []models.QueryHistory{}, 0, "Service not initialized"
|
|
}
|
|
|
|
history, total, err := a.querySvc.GetQueryHistory(a.ctx, connectionID, page, pageSize)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get query history", zap.Error(err))
|
|
return []models.QueryHistory{}, 0, err.Error()
|
|
}
|
|
|
|
return history, total, ""
|
|
}
|
|
|
|
// GetSavedQueries returns all saved queries
|
|
func (a *App) GetSavedQueries(connectionID string) ([]models.SavedQuery, string) {
|
|
if a.querySvc == nil {
|
|
return []models.SavedQuery{}, "Service not initialized"
|
|
}
|
|
|
|
queries, err := a.querySvc.GetSavedQueries(a.ctx, connectionID)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to get saved queries", zap.Error(err))
|
|
return []models.SavedQuery{}, err.Error()
|
|
}
|
|
|
|
return queries, ""
|
|
}
|
|
|
|
// CreateSavedQuery creates a new saved query
|
|
func (a *App) CreateSavedQuery(req models.CreateSavedQueryRequest) (*models.SavedQuery, string) {
|
|
if a.querySvc == nil {
|
|
return nil, "Service not initialized"
|
|
}
|
|
|
|
query, err := a.querySvc.CreateSavedQuery(a.ctx, &req)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to create saved query", zap.Error(err))
|
|
return nil, err.Error()
|
|
}
|
|
|
|
return query, ""
|
|
}
|
|
|
|
// UpdateSavedQuery updates a saved query
|
|
func (a *App) UpdateSavedQuery(id uint, req models.UpdateSavedQueryRequest) (*models.SavedQuery, string) {
|
|
if a.querySvc == nil {
|
|
return nil, "Service not initialized"
|
|
}
|
|
|
|
query, err := a.querySvc.UpdateSavedQuery(a.ctx, id, &req)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to update saved query", zap.Error(err))
|
|
return nil, err.Error()
|
|
}
|
|
|
|
return query, ""
|
|
}
|
|
|
|
// DeleteSavedQuery deletes a saved query
|
|
func (a *App) DeleteSavedQuery(id uint) string {
|
|
if a.querySvc == nil {
|
|
return "Service not initialized"
|
|
}
|
|
|
|
err := a.querySvc.DeleteSavedQuery(a.ctx, id)
|
|
if err != nil {
|
|
config.GetLogger().Error("failed to delete saved query", zap.Error(err))
|
|
return err.Error()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// StartHTTPServer starts the HTTP API server in background
|
|
func (a *App) StartHTTPServer() string {
|
|
if a.httpServer == nil {
|
|
return "HTTP server not initialized"
|
|
}
|
|
|
|
go func() {
|
|
port := a.config.API.Port
|
|
if err := a.httpServer.Start(port); err != nil {
|
|
config.GetLogger().Error("HTTP server error", zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
return ""
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the application
|
|
func (a *App) Shutdown() {
|
|
config.GetLogger().Info("shutting down application")
|
|
|
|
if a.shutdownFunc != nil {
|
|
a.shutdownFunc()
|
|
}
|
|
|
|
if a.httpServer != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
a.httpServer.Shutdown(ctx)
|
|
}
|
|
|
|
// Close all database connections
|
|
database.CloseSQLite()
|
|
|
|
config.Sync()
|
|
}
|