From 333fe03de0693dbbdc7c9ece7193f0d7bc602a46 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:02:19 -0500 Subject: [PATCH] fix: historial por usuario (user_id en query_history) --- pkg/models/query_history.go | 24 +++++++-- pkg/services/query_runner_service.go | 55 +++++++++++++-------- rest/controllers/query_runner_controller.go | 51 ++++++++++++++++--- 3 files changed, 98 insertions(+), 32 deletions(-) diff --git a/pkg/models/query_history.go b/pkg/models/query_history.go index 57a88b8..6505782 100644 --- a/pkg/models/query_history.go +++ b/pkg/models/query_history.go @@ -12,6 +12,7 @@ type QueryHistory struct { gorm.Model ConxDbID uint `json:"conx_db_id" gorm:"column:conx_db_id;index"` ConxDb ConxDb `json:"conx_db" gorm:"foreignKey:ConxDbID"` + UserID uint `json:"user_id" gorm:"column:user_id;index;default:0"` SQL string `json:"sql" gorm:"column:sql;type:text"` Status string `json:"status" gorm:"column:status"` // ok | error ErrorMsg string `json:"error_msg" gorm:"column:error_msg;type:text"` @@ -29,8 +30,23 @@ func SaveQueryHistory(h QueryHistory) error { return app.Http.Database.DB.Create(&h).Error } -// GetQueryHistory devuelve el historial de una conexión con paginación. -func GetQueryHistory(conxDbID uint, limit, offset int) ([]QueryHistory, int64, error) { +// GetQueryHistory devuelve el historial de una conexión con paginación, filtrado por usuario. +func GetQueryHistory(conxDbID, userID uint, limit, offset int) ([]QueryHistory, int64, error) { + var items []QueryHistory + var total int64 + db := app.Http.Database.DB.Model(&QueryHistory{}).Where("conx_db_id = ? AND user_id = ?", conxDbID, userID) + db.Count(&total) + err := db.Order("executed_at DESC").Limit(limit).Offset(offset).Find(&items).Error + return items, total, err +} + +// DeleteQueryHistory elimina el historial de una conexión para un usuario. +func DeleteQueryHistory(conxDbID, userID uint) error { + return app.Http.Database.DB.Where("conx_db_id = ? AND user_id = ?", conxDbID, userID).Delete(&QueryHistory{}).Error +} + +// GetQueryHistoryAdmin devuelve todo el historial de una conexión (solo admin). +func GetQueryHistoryAdmin(conxDbID uint, limit, offset int) ([]QueryHistory, int64, error) { var items []QueryHistory var total int64 db := app.Http.Database.DB.Model(&QueryHistory{}).Where("conx_db_id = ?", conxDbID) @@ -39,7 +55,7 @@ func GetQueryHistory(conxDbID uint, limit, offset int) ([]QueryHistory, int64, e return items, total, err } -// DeleteQueryHistory elimina todo el historial de una conexión. -func DeleteQueryHistory(conxDbID uint) error { +// DeleteQueryHistoryAdmin elimina todo el historial de una conexión (solo admin). +func DeleteQueryHistoryAdmin(conxDbID uint) error { return app.Http.Database.DB.Where("conx_db_id = ?", conxDbID).Delete(&QueryHistory{}).Error } diff --git a/pkg/services/query_runner_service.go b/pkg/services/query_runner_service.go index 3ec5bba..c200d6b 100644 --- a/pkg/services/query_runner_service.go +++ b/pkg/services/query_runner_service.go @@ -82,18 +82,22 @@ func openDynamicDB(c models.ConxDb) (*sql.DB, error) { // ExecuteSQL ejecuta SQL arbitrario contra la conexión y devuelve QueryResult. // También guarda en query_history. -func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { +func ExecuteSQL(conx models.ConxDb, database, sqlText string, userID ...uint) QueryResult { + uid := uint(0) + if len(userID) > 0 { + uid = userID[0] + } if isMongoDriver(strings.ToLower(conx.TipoDb.Nombre)) { - return mongoExecuteSQL(conx, database, sqlText) + return mongoExecuteSQL(conx, database, sqlText, uid) } if isRedisDriver(strings.ToLower(conx.TipoDb.Nombre)) { - return redisExecuteCommand(conx, database, sqlText) + return redisExecuteCommand(conx, database, sqlText, uid) } start := time.Now() db, err := openDynamicDB(conx) if err != nil { - saveHistory(conx.ID, sqlText, "error", err.Error(), 0, time.Since(start).Milliseconds()) + saveHistory(conx.ID, uid, sqlText, "error", err.Error(), 0, time.Since(start).Milliseconds()) return QueryResult{Error: err.Error()} } defer db.Close() @@ -110,7 +114,7 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { } } else { if _, err2 := db.Exec("USE " + quoteIdentifier(database, conx.TipoDb.Nombre)); err2 != nil { - saveHistory(conx.ID, sqlText, "error", err2.Error(), 0, time.Since(start).Milliseconds()) + saveHistory(conx.ID, uid, sqlText, "error", err2.Error(), 0, time.Since(start).Milliseconds()) return QueryResult{Error: err2.Error()} } } @@ -126,7 +130,7 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { rows, err := db.Query(trimmed) if err != nil { elapsed := time.Since(start).Milliseconds() - saveHistory(conx.ID, sqlText, "error", err.Error(), 0, elapsed) + saveHistory(conx.ID, uid, sqlText, "error", err.Error(), 0, elapsed) return QueryResult{Error: err.Error(), IsSelect: true} } defer rows.Close() @@ -170,7 +174,7 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { if len(preview) > 80 { preview = preview[:80] + "..." } - saveHistory(conx.ID, sqlText, "error", execErr.Error(), 0, elapsed) + saveHistory(conx.ID, uid, sqlText, "error", execErr.Error(), 0, elapsed) return QueryResult{Error: fmt.Sprintf("[%s]: %s", preview, execErr.Error())} } if affected, err2 := res.RowsAffected(); err2 == nil { @@ -180,7 +184,7 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { elapsed := time.Since(start).Milliseconds() result.AffectedRows = totalAffected result.DurationMs = elapsed - saveHistory(conx.ID, sqlText, "ok", "", totalAffected, elapsed) + saveHistory(conx.ID, uid, sqlText, "ok", "", totalAffected, elapsed) return result } } @@ -188,18 +192,18 @@ func ExecuteSQL(conx models.ConxDb, database, sqlText string) QueryResult { res, err := db.Exec(trimmed) elapsed := time.Since(start).Milliseconds() if err != nil { - saveHistory(conx.ID, sqlText, "error", err.Error(), 0, elapsed) + saveHistory(conx.ID, uid, sqlText, "error", err.Error(), 0, elapsed) return QueryResult{Error: err.Error()} } affected, _ := res.RowsAffected() result.AffectedRows = affected result.DurationMs = elapsed - saveHistory(conx.ID, sqlText, "ok", "", affected, elapsed) + saveHistory(conx.ID, uid, sqlText, "ok", "", affected, elapsed) return result } result.DurationMs = time.Since(start).Milliseconds() - saveHistory(conx.ID, sqlText, "ok", "", int64(result.RowCount), result.DurationMs) + saveHistory(conx.ID, uid, sqlText, "ok", "", int64(result.RowCount), result.DurationMs) return result } @@ -559,9 +563,10 @@ func quoteIdentifier(name, driver string) string { return "`" + strings.ReplaceAll(name, "`", "``") + "`" } -func saveHistory(conxID uint, sqlText, status, errMsg string, rows, durationMs int64) { +func saveHistory(conxID, userID uint, sqlText, status, errMsg string, rows, durationMs int64) { models.SaveQueryHistory(models.QueryHistory{ ConxDbID: conxID, + UserID: userID, SQL: sqlText, Status: status, ErrorMsg: errMsg, @@ -670,7 +675,11 @@ func redisListKeys(c models.ConxDb, database string) ([]string, error) { // redisExecuteCommand parsea y ejecuta un comando Redis. // Los comandos se escriben como en redis-cli: GET key / SET key value / etc. // Soporta múltiples líneas: cada línea no vacía es un comando independiente. -func redisExecuteCommand(conx models.ConxDb, database, cmdText string) QueryResult { +func redisExecuteCommand(conx models.ConxDb, database, cmdText string, userID ...uint) QueryResult { + uid := uint(0) + if len(userID) > 0 { + uid = userID[0] + } start := time.Now() dbIndex := 0 @@ -682,7 +691,7 @@ func redisExecuteCommand(conx models.ConxDb, database, cmdText string) QueryResu client, err := redisConnect(conx, dbIndex) if err != nil { - saveHistory(conx.ID, cmdText, "error", err.Error(), 0, time.Since(start).Milliseconds()) + saveHistory(conx.ID, uid, cmdText, "error", err.Error(), 0, time.Since(start).Milliseconds()) return QueryResult{Error: err.Error()} } defer client.Close() @@ -735,7 +744,7 @@ func redisExecuteCommand(conx models.ConxDb, database, cmdText string) QueryResu IsSelect: true, DurationMs: elapsed, } - saveHistory(conx.ID, cmdText, "ok", "", int64(len(rows)), elapsed) + saveHistory(conx.ID, uid, cmdText, "ok", "", int64(len(rows)), elapsed) return result } @@ -753,13 +762,13 @@ func redisExecuteCommand(conx models.ConxDb, database, cmdText string) QueryResu elapsed := time.Since(start).Milliseconds() if execErr != nil && execErr != goredis.Nil { - saveHistory(conx.ID, cmdText, "error", execErr.Error(), 0, elapsed) + saveHistory(conx.ID, uid, cmdText, "error", execErr.Error(), 0, elapsed) return QueryResult{Error: execErr.Error(), DurationMs: elapsed} } result := redisResultToQueryResult(val, lines[0]) result.DurationMs = elapsed - saveHistory(conx.ID, cmdText, "ok", "", int64(result.RowCount), elapsed) + saveHistory(conx.ID, uid, cmdText, "ok", "", int64(result.RowCount), elapsed) return result } @@ -1052,11 +1061,15 @@ func mongoSingleToDoubleQuotes(s string) string { return b.String() } -func mongoExecuteSQL(conx models.ConxDb, database, queryText string) QueryResult { +func mongoExecuteSQL(conx models.ConxDb, database, queryText string, userID ...uint) QueryResult { + uid := uint(0) + if len(userID) > 0 { + uid = userID[0] + } start := time.Now() client, err := mongoConnect(conx) if err != nil { - saveHistory(conx.ID, queryText, "error", err.Error(), 0, time.Since(start).Milliseconds()) + saveHistory(conx.ID, uid, queryText, "error", err.Error(), 0, time.Since(start).Milliseconds()) return QueryResult{Error: err.Error()} } defer client.Disconnect(context.Background()) //nolint @@ -1066,11 +1079,11 @@ func mongoExecuteSQL(conx models.ConxDb, database, queryText string) QueryResult result, err := mongoRunQuery(ctx, db, strings.TrimSpace(queryText)) elapsed := time.Since(start).Milliseconds() if err != nil { - saveHistory(conx.ID, queryText, "error", err.Error(), 0, elapsed) + saveHistory(conx.ID, uid, queryText, "error", err.Error(), 0, elapsed) return QueryResult{Error: err.Error()} } result.DurationMs = elapsed - saveHistory(conx.ID, queryText, "ok", "", int64(result.RowCount)+result.AffectedRows, elapsed) + saveHistory(conx.ID, uid, queryText, "ok", "", int64(result.RowCount)+result.AffectedRows, elapsed) return *result } diff --git a/rest/controllers/query_runner_controller.go b/rest/controllers/query_runner_controller.go index 70417c2..bfc8700 100644 --- a/rest/controllers/query_runner_controller.go +++ b/rest/controllers/query_runner_controller.go @@ -119,7 +119,8 @@ func RunQuery(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } - result := services.ExecuteSQL(conx, body.Database, body.SQL) + uid := extractUserID(c) + result := services.ExecuteSQL(conx, body.Database, body.SQL, uid) return c.JSON(result) } @@ -128,6 +129,7 @@ func RunQuery(c *fiber.Ctx) error { // Body: { conx_db_id, database, sqls: ["...", "..."] } // También soporta multipart/form-data con file .sql func RunBatchQuery(c *fiber.Ctx) error { + uid := extractUserID(c) conxDbIDStr := c.FormValue("conx_db_id", c.Query("conx_db_id")) database := c.FormValue("database", c.Query("database")) @@ -195,7 +197,7 @@ func RunBatchQuery(c *fiber.Ctx) error { results := make([]batchResult, 0, len(statements)) for i, stmt := range statements { - r := services.ExecuteSQL(conx, database, stmt) + r := services.ExecuteSQL(conx, database, stmt, uid) br := batchResult{ Index: i, SQL: stmt, @@ -244,7 +246,19 @@ func GetHistory(c *fiber.Ctx) error { limit := 50 offset := (page - 1) * limit - items, total, err := models.GetQueryHistory(uint(conxID), limit, offset) + user, _ := auth.User(c) + var items []models.QueryHistory + var total int64 + var err error + if user != nil && user.IsAdmin { + items, total, err = models.GetQueryHistoryAdmin(uint(conxID), limit, offset) + } else { + uid := uint(0) + if user != nil { + uid = user.ID + } + items, total, err = models.GetQueryHistory(uint(conxID), uid, limit, offset) + } if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } @@ -262,7 +276,19 @@ func GetHistory(c *fiber.Ctx) error { 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 { + + user, _ := auth.User(c) + var err error + if user != nil && user.IsAdmin { + err = models.DeleteQueryHistoryAdmin(uint(conxID)) + } else { + uid := uint(0) + if user != nil { + uid = user.ID + } + err = models.DeleteQueryHistory(uint(conxID), uid) + } + if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{"ok": true}) @@ -284,7 +310,8 @@ func ExportCSV(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } - result := services.ExecuteSQL(conx, body.Database, body.SQL) + uid := extractUserID(c) + result := services.ExecuteSQL(conx, body.Database, body.SQL, uid) if result.Error != "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error}) } @@ -328,7 +355,8 @@ func ExportJSON(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } - result := services.ExecuteSQL(conx, body.Database, body.SQL) + uid := extractUserID(c) + result := services.ExecuteSQL(conx, body.Database, body.SQL, uid) if result.Error != "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": result.Error}) } @@ -423,7 +451,8 @@ func UpdateCellHandler(c *fiber.Ctx) error { sqlText := fmt.Sprintf("UPDATE %s SET %s = %s WHERE %s = %s", qTable, qCol, valueSQL, qPkCol, pkValueSQL) - result := services.ExecuteSQL(conx, body.Database, sqlText) + uid := extractUserID(c) + result := services.ExecuteSQL(conx, body.Database, sqlText, uid) if result.Error != "" { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": result.Error}) } @@ -792,3 +821,11 @@ func loadConxDb(idStr string) (models.ConxDb, error) { } return conx, nil } + +func extractUserID(c *fiber.Ctx) uint { + user, err := auth.User(c) + if err != nil || user == nil { + return 0 + } + return user.ID +}