238 lines
7.6 KiB
Go
238 lines
7.6 KiB
Go
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/app"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
|
|
)
|
|
|
|
// QueryRunnerPage renderiza la vista del editor SQL.
|
|
// GET /app/query-runner?conx_db_id=1
|
|
func QueryRunnerPage(c *fiber.Ctx) error {
|
|
conxIDStr := c.Query("conx_db_id", "")
|
|
var conx models.ConxDb
|
|
if conxIDStr != "" {
|
|
id, _ := strconv.ParseUint(conxIDStr, 10, 32)
|
|
app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").First(&conx, id)
|
|
}
|
|
|
|
data := fiber.Map{
|
|
"user": c.Locals("user").(map[string]interface{}),
|
|
"modules": c.Locals("userModules"),
|
|
"conx_db_id": conxIDStr,
|
|
"conx": conx,
|
|
}
|
|
if err := c.Render("query_runner", data, "layouts/main"); err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetConxDbList devuelve todas las conexiones DB disponibles (para el selector).
|
|
func GetConxDbList(c *fiber.Ctx) error {
|
|
var items []models.ConxDb
|
|
app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").Find(&items)
|
|
return c.JSON(fiber.Map{"data": items})
|
|
}
|
|
|
|
// GetDatabases lista las bases de datos de una conexión.
|
|
// GET /app/query-runner/databases?conx_db_id=1
|
|
func GetDatabases(c *fiber.Ctx) error {
|
|
conx, err := loadConxDb(c.Query("conx_db_id", "0"))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
dbs, err := services.ListDatabases(conx)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"data": dbs})
|
|
}
|
|
|
|
// GetTables lista las tablas de una base de datos.
|
|
// GET /app/query-runner/tables?conx_db_id=1&db=mydb
|
|
func GetTables(c *fiber.Ctx) error {
|
|
conx, err := loadConxDb(c.Query("conx_db_id", "0"))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
dbName := c.Query("db", "")
|
|
tables, err := services.ListTables(conx, dbName)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"data": tables})
|
|
}
|
|
|
|
// RunQuery ejecuta una consulta SQL.
|
|
// POST /app/query-runner/run
|
|
// Body: { conx_db_id, database, sql }
|
|
func RunQuery(c *fiber.Ctx) error {
|
|
var body struct {
|
|
ConxDbID uint `json:"conx_db_id"`
|
|
Database string `json:"database"`
|
|
SQL string `json:"sql"`
|
|
}
|
|
if err := c.BodyParser(&body); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
|
|
}
|
|
if strings.TrimSpace(body.SQL) == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La consulta está vacía"})
|
|
}
|
|
|
|
conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
result := services.ExecuteSQL(conx, body.Database, body.SQL)
|
|
return c.JSON(result)
|
|
}
|
|
|
|
// TestConnection verifica que la conexión funciona.
|
|
// GET /app/query-runner/test?conx_db_id=1
|
|
func TestConnection(c *fiber.Ctx) error {
|
|
conx, err := loadConxDb(c.Query("conx_db_id", "0"))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
if err := services.TestDBConnection(conx); err != nil {
|
|
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"ok": false, "error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// GetHistory devuelve el historial de una conexión.
|
|
// GET /app/query-runner/history?conx_db_id=1&page=1
|
|
func GetHistory(c *fiber.Ctx) error {
|
|
conxIDStr := c.Query("conx_db_id", "0")
|
|
conxID, _ := strconv.ParseUint(conxIDStr, 10, 32)
|
|
page, _ := strconv.Atoi(c.Query("page", "1"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
limit := 50
|
|
offset := (page - 1) * limit
|
|
|
|
items, total, err := models.GetQueryHistory(uint(conxID), limit, offset)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
totalPages := int(math.Ceil(float64(total) / float64(limit)))
|
|
return c.JSON(fiber.Map{
|
|
"data": items,
|
|
"total": total,
|
|
"totalPages": totalPages,
|
|
"page": page,
|
|
})
|
|
}
|
|
|
|
// ClearHistory borra el historial de una conexión.
|
|
// DELETE /app/query-runner/history?conx_db_id=1
|
|
func ClearHistory(c *fiber.Ctx) error {
|
|
conxIDStr := c.Query("conx_db_id", "0")
|
|
conxID, _ := strconv.ParseUint(conxIDStr, 10, 32)
|
|
if err := models.DeleteQueryHistory(uint(conxID)); err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true})
|
|
}
|
|
|
|
// ExportCSV exporta el resultado de una consulta como CSV.
|
|
// POST /app/query-runner/export/csv
|
|
func ExportCSV(c *fiber.Ctx) error {
|
|
var body struct {
|
|
ConxDbID uint `json:"conx_db_id"`
|
|
Database string `json:"database"`
|
|
SQL string `json:"sql"`
|
|
}
|
|
if err := c.BodyParser(&body); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
|
|
}
|
|
conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
result := services.ExecuteSQL(conx, body.Database, body.SQL)
|
|
if result.Error != "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error})
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
w := csv.NewWriter(&buf)
|
|
w.Write(result.Columns)
|
|
for _, row := range result.Rows {
|
|
rec := make([]string, len(result.Columns))
|
|
for i, col := range result.Columns {
|
|
v := row[col]
|
|
if v == nil {
|
|
rec[i] = ""
|
|
} else {
|
|
rec[i] = fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
w.Write(rec)
|
|
}
|
|
w.Flush()
|
|
|
|
filename := fmt.Sprintf("query_%s.csv", time.Now().Format("20060102_150405"))
|
|
c.Set("Content-Disposition", "attachment; filename="+filename)
|
|
c.Set("Content-Type", "text/csv; charset=utf-8")
|
|
return c.SendStream(bytes.NewReader(buf.Bytes()), buf.Len())
|
|
}
|
|
|
|
// ExportJSON exporta el resultado como JSON.
|
|
// POST /app/query-runner/export/json
|
|
func ExportJSON(c *fiber.Ctx) error {
|
|
var body struct {
|
|
ConxDbID uint `json:"conx_db_id"`
|
|
Database string `json:"database"`
|
|
SQL string `json:"sql"`
|
|
}
|
|
if err := c.BodyParser(&body); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cuerpo inválido"})
|
|
}
|
|
conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID)))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
result := services.ExecuteSQL(conx, body.Database, body.SQL)
|
|
if result.Error != "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error})
|
|
}
|
|
|
|
data, _ := json.MarshalIndent(result.Rows, "", " ")
|
|
filename := fmt.Sprintf("query_%s.json", time.Now().Format("20060102_150405"))
|
|
c.Set("Content-Disposition", "attachment; filename="+filename)
|
|
c.Set("Content-Type", "application/json; charset=utf-8")
|
|
return c.SendStream(bytes.NewReader(data), len(data))
|
|
}
|
|
|
|
// ── helper ───────────────────────────────────────────────────────────────────
|
|
|
|
func loadConxDb(idStr string) (models.ConxDb, error) {
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil || id == 0 {
|
|
return models.ConxDb{}, fmt.Errorf("conx_db_id inválido")
|
|
}
|
|
var conx models.ConxDb
|
|
if err := app.Http.Database.DB.Preload("TipoDb").Preload("Servidor").First(&conx, id).Error; err != nil {
|
|
return models.ConxDb{}, fmt.Errorf("conexión no encontrada: %w", err)
|
|
}
|
|
return conx, nil
|
|
}
|