feat: funciones Go para API Metabase y tipo MetabaseClient

Añade funciones Go stub para la API de Metabase en dominio infra:
auth, CRUD de cards, dashboards y users, execute_query y execute_card.
Incluye tipo MetabaseClient y helper HTTP compartido.
Todas las funciones son impuras con stubs not-implemented.
This commit is contained in:
2026-03-28 20:32:24 +01:00
parent 49eecd0c87
commit 9e6bea681f
40 changed files with 1640 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
package infra
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// MetabaseAuth autentica con email y password contra una instancia Metabase.
// Retorna un MetabaseClient con el session token listo para usar.
// baseURL es la URL base sin trailing slash (ej: "http://localhost:3000").
func MetabaseAuth(baseURL, email, password string) (MetabaseClient, error) {
payload, _ := json.Marshal(map[string]string{
"username": email,
"password": password,
})
resp, err := http.Post(baseURL+"/api/session", "application/json", bytes.NewReader(payload))
if err != nil {
return MetabaseClient{}, fmt.Errorf("metabase auth: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return MetabaseClient{}, fmt.Errorf("read auth response: %w", err)
}
if resp.StatusCode != 200 {
return MetabaseClient{}, fmt.Errorf("metabase auth: status %d: %s", resp.StatusCode, string(body))
}
var result struct {
ID string `json:"id"`
}
if err := json.Unmarshal(body, &result); err != nil {
return MetabaseClient{}, fmt.Errorf("parse auth response: %w", err)
}
return MetabaseClient{BaseURL: baseURL, Token: result.ID}, nil
}
// MetabaseNewClient crea un MetabaseClient usando una API key en lugar de session token.
// Las API keys se crean en Settings > Authentication > API Keys del admin de Metabase.
func MetabaseNewClient(baseURL, apiKey string) MetabaseClient {
return MetabaseClient{BaseURL: baseURL, Token: apiKey}
}
+50
View File
@@ -0,0 +1,50 @@
---
name: metabase_auth
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseAuth(baseURL, email, password string) (MetabaseClient, error)"
description: "Autentica contra la API de Metabase con email y password. Retorna un MetabaseClient con session token valido por 14 dias (configurable con MAX_SESSION_AGE en Metabase). Endpoint: POST /api/session."
tags: [metabase, auth, session, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: [MetabaseClient_go_infra]
returns_optional: false
error_type: "error_go_core"
imports: [bytes, encoding/json, fmt, io, net/http]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_auth.go"
---
## Ejemplo
```go
// Autenticar con credenciales
client, err := MetabaseAuth("http://localhost:3000", "admin@example.com", "password123")
if err != nil {
log.Fatal(err)
}
// client.Token contiene el session token
// Alternativa: usar API key directamente
client := MetabaseNewClient("http://localhost:3000", "mb_api_key_xxxxx")
```
## Notas
Dos formas de obtener un MetabaseClient:
- `MetabaseAuth`: login con email/password, obtiene session token via POST /api/session. Token expira en 14 dias por defecto.
- `MetabaseNewClient`: usa una API key creada en el admin UI. No expira. Recomendado para automatizacion.
El token se envia como header `X-Metabase-Session` en todas las llamadas subsiguientes.
### Para un LLM que use estas funciones
1. Primero obtener un client con `MetabaseAuth()` o `MetabaseNewClient()`
2. Pasar el client a todas las funciones CRUD (usuarios, cards, dashboards)
3. Si recibes error 401, el token expiro — re-autenticar
4. Rate limiting: Metabase limita intentos de login fallidos
+35
View File
@@ -0,0 +1,35 @@
package infra
import "fmt"
// MetabaseCreateCard crea una nueva card/pregunta en Metabase.
// name: nombre de la pregunta (obligatorio).
// datasetQuery: query de la card (obligatorio). Estructura:
//
// SQL nativo: {"database": 1, "type": "native", "native": {"query": "SELECT ..."}}
// MBQL: {"database": 1, "type": "query", "query": {"source-table": 4, ...}}
//
// display: tipo de visualizacion ("table", "bar", "line", "pie", "scalar", etc.).
// collectionID: ID de la coleccion/carpeta (0 = root).
// description: descripcion opcional (vacio = sin descripcion).
func MetabaseCreateCard(client MetabaseClient, name string, datasetQuery map[string]any, display string, collectionID int, description string) (map[string]any, error) {
body := map[string]any{
"name": name,
"dataset_query": datasetQuery,
"display": display,
"visualization_settings": map[string]any{},
}
if collectionID > 0 {
body["collection_id"] = collectionID
}
if description != "" {
body["description"] = description
}
result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/card", body)
if err != nil {
return nil, fmt.Errorf("metabase create card: %w", err)
}
return result, nil
}
+86
View File
@@ -0,0 +1,86 @@
---
name: metabase_create_card
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseCreateCard(client MetabaseClient, name string, datasetQuery map[string]any, display string, collectionID int, description string) (map[string]any, error)"
description: "Crea una nueva card/pregunta en Metabase con query SQL nativa o MBQL. Endpoint: POST /api/card."
tags: [metabase, card, question, create, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_create_card.go"
---
## Ejemplo
```go
// Crear pregunta con SQL nativo
card, err := MetabaseCreateCard(client, "Revenue by Month", map[string]any{
"database": 1,
"type": "native",
"native": map[string]any{
"query": "SELECT date_trunc('month', created_at) as month, SUM(total) as revenue FROM orders GROUP BY 1 ORDER BY 1",
},
}, "line", 5, "Monthly revenue trend")
// Crear pregunta con MBQL (structured query)
card, err := MetabaseCreateCard(client, "Order Count", map[string]any{
"database": 1,
"type": "query",
"query": map[string]any{
"source-table": 4,
"aggregation": []any{[]any{"count"}},
},
}, "scalar", 0, "Total number of orders")
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| name | string | si | Nombre de la pregunta |
| datasetQuery | map[string]any | si | Query. Ver estructura abajo |
| display | string | si | Tipo de visualizacion |
| collectionID | int | no | ID de coleccion. 0 = root collection |
| description | string | no | Descripcion. Vacio = sin descripcion |
### Estructura de datasetQuery
**SQL nativo:**
```json
{
"database": <database_id>,
"type": "native",
"native": {"query": "SELECT ..."}
}
```
**MBQL (structured):**
```json
{
"database": <database_id>,
"type": "query",
"query": {
"source-table": <table_id>,
"aggregation": [["count"]],
"breakout": [["field", <field_id>, {"temporal-unit": "month"}]],
"filter": ["=", ["field", <field_id>, null], "value"]
}
}
```
### Valores de display
table, bar, line, pie, scalar, area, row, combo, funnel, map, scatter, waterfall, progress, gauge
@@ -0,0 +1,26 @@
package infra
import "fmt"
// MetabaseCreateDashboard crea un nuevo dashboard en Metabase.
// name: nombre del dashboard (obligatorio).
// description: descripcion opcional (vacio = sin descripcion).
// collectionID: ID de la coleccion/carpeta (0 = root).
func MetabaseCreateDashboard(client MetabaseClient, name, description string, collectionID int) (map[string]any, error) {
body := map[string]any{
"name": name,
}
if description != "" {
body["description"] = description
}
if collectionID > 0 {
body["collection_id"] = collectionID
}
result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/dashboard", body)
if err != nil {
return nil, fmt.Errorf("metabase create dashboard: %w", err)
}
return result, nil
}
@@ -0,0 +1,53 @@
---
name: metabase_create_dashboard
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseCreateDashboard(client MetabaseClient, name, description string, collectionID int) (map[string]any, error)"
description: "Crea un nuevo dashboard vacio en Metabase. Para agregar cards usar MetabaseUpdateDashboard con el campo dashcards. Endpoint: POST /api/dashboard."
tags: [metabase, dashboard, create, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_create_dashboard.go"
---
## Ejemplo
```go
// Crear dashboard vacio
dashboard, err := MetabaseCreateDashboard(client, "Sales Overview", "KPIs de ventas", 5)
if err != nil {
log.Fatal(err)
}
dashboardID := int(dashboard["id"].(float64))
// Luego agregar cards con MetabaseUpdateDashboard
MetabaseUpdateDashboard(client, dashboardID, map[string]any{
"dashcards": []map[string]any{
{"id": -1, "card_id": 42, "size_x": 6, "size_y": 4, "col": 0, "row": 0},
},
})
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| name | string | si | Nombre del dashboard |
| description | string | no | Descripcion. Vacio = sin descripcion |
| collectionID | int | no | Coleccion destino. 0 = root |
El dashboard se crea vacio. Para agregar cards, usar MetabaseUpdateDashboard con el array dashcards.
Retorna el objeto dashboard creado.
+28
View File
@@ -0,0 +1,28 @@
package infra
import "fmt"
// MetabaseCreateUser crea un nuevo usuario en Metabase.
// firstName, lastName y email son obligatorios.
// password es opcional: si esta vacio, Metabase envia email de invitacion.
// groupIDs es opcional: IDs de grupos a asignar (nil = solo grupo default).
func MetabaseCreateUser(client MetabaseClient, firstName, lastName, email, password string, groupIDs []int) (map[string]any, error) {
body := map[string]any{
"first_name": firstName,
"last_name": lastName,
"email": email,
}
if password != "" {
body["password"] = password
}
if len(groupIDs) > 0 {
body["group_ids"] = groupIDs
}
result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/user", body)
if err != nil {
return nil, fmt.Errorf("metabase create user: %w", err)
}
return result, nil
}
+47
View File
@@ -0,0 +1,47 @@
---
name: metabase_create_user
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseCreateUser(client MetabaseClient, firstName, lastName, email, password string, groupIDs []int) (map[string]any, error)"
description: "Crea un nuevo usuario en Metabase. Si no se provee password, Metabase envia email de invitacion. Requiere permisos de superusuario. Endpoint: POST /api/user."
tags: [metabase, user, create, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_create_user.go"
---
## Ejemplo
```go
// Crear usuario con password
user, err := MetabaseCreateUser(client, "John", "Doe", "john@example.com", "securePass123", nil)
// Crear usuario sin password (envia invitacion por email)
user, err := MetabaseCreateUser(client, "Jane", "Smith", "jane@example.com", "", []int{1, 3})
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado con permisos admin |
| firstName | string | si | Nombre del usuario |
| lastName | string | si | Apellido del usuario |
| email | string | si | Email unico del usuario |
| password | string | no | Password. Vacio = Metabase envia invitacion |
| groupIDs | []int | no | IDs de grupos. nil = solo grupo default |
El email debe ser unico. Si ya existe, retorna error 400.
Retorna el objeto usuario creado como map (mismos campos que MetabaseGetUser).
@@ -0,0 +1,17 @@
package infra
import "fmt"
// MetabaseDeactivateUser desactiva (soft-delete) un usuario en Metabase.
// El usuario no se elimina permanentemente, solo se marca como inactivo.
// Requiere permisos de superusuario.
func MetabaseDeactivateUser(client MetabaseClient, userID int) error {
path := fmt.Sprintf("/api/user/%d", userID)
_, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil)
if err != nil {
return fmt.Errorf("metabase deactivate user %d: %w", userID, err)
}
return nil
}
@@ -0,0 +1,40 @@
---
name: metabase_deactivate_user
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseDeactivateUser(client MetabaseClient, userID int) error"
description: "Desactiva (soft-delete) un usuario en Metabase. El usuario no se elimina permanentemente, solo se marca como inactivo. Para reactivar, usar PUT /api/user/:id/reactivate. Endpoint: DELETE /api/user/:id."
tags: [metabase, user, delete, deactivate, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_deactivate_user.go"
---
## Ejemplo
```go
err := MetabaseDeactivateUser(client, 5)
if err != nil {
log.Fatal(err)
}
// Usuario 5 ahora esta inactivo
// Para ver desactivados: MetabaseListUsers(client, "deactivated", "", 0, 0)
```
## Notas
Es un soft-delete: el usuario se desactiva pero no se borra. Se puede reactivar con PUT /api/user/:id/reactivate.
Para listar usuarios desactivados, usar `MetabaseListUsers` con status "deactivated".
Requiere permisos de superusuario. Error 403 si no eres admin.
+16
View File
@@ -0,0 +1,16 @@
package infra
import "fmt"
// MetabaseDeleteCard elimina permanentemente una card/pregunta de Metabase.
// Para soft-delete, usar MetabaseUpdateCard con archived: true.
func MetabaseDeleteCard(client MetabaseClient, cardID int) error {
path := fmt.Sprintf("/api/card/%d", cardID)
_, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil)
if err != nil {
return fmt.Errorf("metabase delete card %d: %w", cardID, err)
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
---
name: metabase_delete_card
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseDeleteCard(client MetabaseClient, cardID int) error"
description: "Elimina permanentemente una card/pregunta de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateCard con archived:true. Endpoint: DELETE /api/card/:id."
tags: [metabase, card, question, delete, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_delete_card.go"
---
## Ejemplo
```go
// Eliminar permanentemente
err := MetabaseDeleteCard(client, 42)
// Preferir soft-delete cuando sea posible:
// MetabaseUpdateCard(client, 42, map[string]any{"archived": true})
```
## Notas
**ATENCION**: Esta operacion es irreversible. La card se elimina permanentemente.
Para un borrado seguro, preferir archivar con `MetabaseUpdateCard(client, cardID, map[string]any{"archived": true})` que permite recuperar la card despues.
@@ -0,0 +1,16 @@
package infra
import "fmt"
// MetabaseDeleteDashboard elimina permanentemente un dashboard de Metabase.
// Para soft-delete, usar MetabaseUpdateDashboard con archived: true.
func MetabaseDeleteDashboard(client MetabaseClient, dashboardID int) error {
path := fmt.Sprintf("/api/dashboard/%d", dashboardID)
_, err := metabaseRequest("DELETE", client.BaseURL, client.Token, path, nil)
if err != nil {
return fmt.Errorf("metabase delete dashboard %d: %w", dashboardID, err)
}
return nil
}
@@ -0,0 +1,37 @@
---
name: metabase_delete_dashboard
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseDeleteDashboard(client MetabaseClient, dashboardID int) error"
description: "Elimina permanentemente un dashboard de Metabase. Accion irreversible. Para soft-delete usar MetabaseUpdateDashboard con archived:true. Endpoint: DELETE /api/dashboard/:id."
tags: [metabase, dashboard, delete, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_delete_dashboard.go"
---
## Ejemplo
```go
// Eliminar permanentemente
err := MetabaseDeleteDashboard(client, 1)
// Preferir soft-delete:
// MetabaseUpdateDashboard(client, 1, map[string]any{"archived": true})
```
## Notas
**ATENCION**: Esta operacion es irreversible. El dashboard y todas sus dashcards se eliminan permanentemente.
Para un borrado seguro, preferir archivar con `MetabaseUpdateDashboard(client, dashboardID, map[string]any{"archived": true})`.
+22
View File
@@ -0,0 +1,22 @@
package infra
import "fmt"
// MetabaseExecuteCard ejecuta la query de una card/pregunta guardada.
// parameters: parametros de la query (nil si no tiene parametros).
// Retorna los resultados con columnas y filas.
func MetabaseExecuteCard(client MetabaseClient, cardID int, parameters []map[string]any) (map[string]any, error) {
path := fmt.Sprintf("/api/card/%d/query", cardID)
var body map[string]any
if len(parameters) > 0 {
body = map[string]any{"parameters": parameters}
}
result, err := metabaseRequest("POST", client.BaseURL, client.Token, path, body)
if err != nil {
return nil, fmt.Errorf("metabase execute card %d: %w", cardID, err)
}
return result, nil
}
+71
View File
@@ -0,0 +1,71 @@
---
name: metabase_execute_card
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseExecuteCard(client MetabaseClient, cardID int, parameters []map[string]any) (map[string]any, error)"
description: "Ejecuta la query de una card/pregunta guardada en Metabase y retorna los resultados. Soporta parametros para queries parametrizadas. Endpoint: POST /api/card/:id/query."
tags: [metabase, card, question, execute, query, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_execute_card.go"
---
## Ejemplo
```go
// Ejecutar sin parametros
result, err := MetabaseExecuteCard(client, 42, nil)
if err != nil {
log.Fatal(err)
}
data := result["data"].(map[string]any)
rows := data["rows"].([]any)
fmt.Printf("Filas: %d\n", len(rows))
// Ejecutar con parametros
result, err := MetabaseExecuteCard(client, 42, []map[string]any{
{
"type": "category",
"target": []any{"variable", []any{"template-tag", "status"}},
"value": "active",
},
})
```
## Notas
### Estructura de la respuesta
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| status | string | "completed" o "failed" |
| row_count | float64 | Numero de filas |
| running_time | float64 | Tiempo de ejecucion en ms |
| data.columns | []string | Nombres de columnas |
| data.rows | [][]any | Filas de datos |
| data.cols | []map | Metadata de columnas (name, base_type, display_name) |
| data.native_form.query | string | SQL ejecutado |
### Parametros para queries parametrizadas
```go
[]map[string]any{
{
"type": "category", // tipo del parametro
"target": []any{"variable", []any{"template-tag", "tag"}}, // referencia al template-tag
"value": "valor", // valor a inyectar
},
}
```
Limite por defecto: 2000 filas. Para queries ad-hoc sin card, usar MetabaseExecuteQuery.
+28
View File
@@ -0,0 +1,28 @@
package infra
import "fmt"
// MetabaseExecuteQuery ejecuta una query ad-hoc (sin guardar como card) en Metabase.
// databaseID: ID de la base de datos en Metabase.
// sql: query SQL a ejecutar.
// maxResults: limite de filas (0 = default 2000 de Metabase).
func MetabaseExecuteQuery(client MetabaseClient, databaseID int, sql string, maxResults int) (map[string]any, error) {
body := map[string]any{
"database": databaseID,
"type": "native",
"native": map[string]any{"query": sql},
}
if maxResults > 0 {
body["constraints"] = map[string]any{
"max-results": maxResults,
"max-results-bare-rows": maxResults,
}
}
result, err := metabaseRequest("POST", client.BaseURL, client.Token, "/api/dataset", body)
if err != nil {
return nil, fmt.Errorf("metabase execute query: %w", err)
}
return result, nil
}
+63
View File
@@ -0,0 +1,63 @@
---
name: metabase_execute_query
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseExecuteQuery(client MetabaseClient, databaseID int, sql string, maxResults int) (map[string]any, error)"
description: "Ejecuta una query SQL ad-hoc contra una database de Metabase sin guardarla como card. Util para consultas rapidas y exploracion. Endpoint: POST /api/dataset."
tags: [metabase, query, execute, sql, dataset, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_execute_query.go"
---
## Ejemplo
```go
// Query simple
result, err := MetabaseExecuteQuery(client, 1, "SELECT * FROM users LIMIT 10", 0)
if err != nil {
log.Fatal(err)
}
data := result["data"].(map[string]any)
rows := data["rows"].([]any)
// Query con limite custom
result, err := MetabaseExecuteQuery(client, 1, "SELECT * FROM orders", 5000)
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| databaseID | int | si | ID de la database en Metabase (obtener con GET /api/database) |
| sql | string | si | Query SQL a ejecutar |
| maxResults | int | no | Limite de filas. 0 = default 2000 |
### Diferencia con MetabaseExecuteCard
- `MetabaseExecuteQuery`: query ad-hoc, no se guarda. Usa POST /api/dataset.
- `MetabaseExecuteCard`: ejecuta una card ya guardada. Usa POST /api/card/:id/query.
Usar esta funcion para exploracion rapida. Si la query se va a reutilizar, crear una card con MetabaseCreateCard.
### Estructura de la respuesta
Misma estructura que MetabaseExecuteCard:
- `data.columns`: nombres de columnas
- `data.rows`: filas de datos
- `row_count`: numero de filas
- `running_time`: tiempo en ms
- `status`: "completed" o "failed"
+15
View File
@@ -0,0 +1,15 @@
package infra
import "fmt"
// MetabaseGetCard obtiene una card/pregunta de Metabase por su ID.
func MetabaseGetCard(client MetabaseClient, cardID int) (map[string]any, error) {
path := fmt.Sprintf("/api/card/%d", cardID)
result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase get card %d: %w", cardID, err)
}
return result, nil
}
+52
View File
@@ -0,0 +1,52 @@
---
name: metabase_get_card
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseGetCard(client MetabaseClient, cardID int) (map[string]any, error)"
description: "Obtiene los detalles completos de una card/pregunta de Metabase por su ID. Incluye la query, visualizacion y metadata. Endpoint: GET /api/card/:id."
tags: [metabase, card, question, get, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_get_card.go"
---
## Ejemplo
```go
card, err := MetabaseGetCard(client, 42)
if err != nil {
log.Fatal(err)
}
fmt.Println(card["name"], card["display"])
```
## Notas
Retorna el objeto card completo. Error 404 si no existe.
### Campos principales
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID de la card |
| name | string | Nombre |
| description | string | Descripcion |
| display | string | Tipo visualizacion |
| dataset_query | map | Query (native.query para SQL, query para MBQL) |
| visualization_settings | map | Config de visualizacion |
| collection_id | float64 | Coleccion contenedora |
| database_id | float64 | Database asociada |
| archived | bool | Archivada |
| creator | map | Objeto del usuario creador |
| created_at | string | Fecha creacion |
| updated_at | string | Fecha actualizacion |
+15
View File
@@ -0,0 +1,15 @@
package infra
import "fmt"
// MetabaseGetDashboard obtiene un dashboard completo de Metabase incluyendo sus cards.
func MetabaseGetDashboard(client MetabaseClient, dashboardID int) (map[string]any, error) {
path := fmt.Sprintf("/api/dashboard/%d", dashboardID)
result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase get dashboard %d: %w", dashboardID, err)
}
return result, nil
}
+71
View File
@@ -0,0 +1,71 @@
---
name: metabase_get_dashboard
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseGetDashboard(client MetabaseClient, dashboardID int) (map[string]any, error)"
description: "Obtiene un dashboard completo de Metabase incluyendo todas sus dashcards (cards posicionadas en el dashboard), tabs y parametros. Endpoint: GET /api/dashboard/:id."
tags: [metabase, dashboard, get, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_get_dashboard.go"
---
## Ejemplo
```go
dashboard, err := MetabaseGetDashboard(client, 1)
if err != nil {
log.Fatal(err)
}
fmt.Println(dashboard["name"])
// Acceder a las cards del dashboard
dashcards := dashboard["dashcards"].([]any)
for _, dc := range dashcards {
card := dc.(map[string]any)
fmt.Printf("Card ID: %v, Position: (%v, %v)\n",
card["card_id"], card["col"], card["row"])
}
```
## Notas
### Campos principales
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID del dashboard |
| name | string | Nombre |
| description | string | Descripcion |
| dashcards | []map | Array de dashcards (cards posicionadas) |
| parameters | []map | Filtros del dashboard |
| tabs | []map | Tabs del dashboard |
| collection_id | float64 | Coleccion contenedora |
| archived | bool | Archivado |
### Estructura de cada dashcard
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID del dashcard (positivo) |
| card_id | float64 | ID de la card/pregunta asociada |
| card | map | Objeto card completo |
| size_x | float64 | Ancho en grid (1-18) |
| size_y | float64 | Alto en grid |
| col | float64 | Columna en grid (0-based) |
| row | float64 | Fila en grid (0-based) |
| dashboard_tab_id | float64 | Tab al que pertenece (null = sin tabs) |
| parameter_mappings | []map | Mapeo de filtros a la card |
| visualization_settings | map | Settings de visualizacion |
Usar estos datos para construir el payload de MetabaseUpdateDashboard.
+15
View File
@@ -0,0 +1,15 @@
package infra
import "fmt"
// MetabaseGetUser obtiene un usuario de Metabase por su ID.
func MetabaseGetUser(client MetabaseClient, userID int) (map[string]any, error) {
path := fmt.Sprintf("/api/user/%d", userID)
result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase get user %d: %w", userID, err)
}
return result, nil
}
+51
View File
@@ -0,0 +1,51 @@
---
name: metabase_get_user
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseGetUser(client MetabaseClient, userID int) (map[string]any, error)"
description: "Obtiene los detalles de un usuario de Metabase por su ID numerico. Endpoint: GET /api/user/:id."
tags: [metabase, user, get, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_get_user.go"
---
## Ejemplo
```go
user, err := MetabaseGetUser(client, 1)
if err != nil {
log.Fatal(err)
}
fmt.Println(user["email"], user["first_name"])
```
## Notas
Retorna el objeto usuario completo como map. Error 404 si el ID no existe.
### Campos del usuario retornado
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID numerico |
| email | string | Email |
| first_name | string | Nombre |
| last_name | string | Apellido |
| is_superuser | bool | Es admin |
| is_active | bool | Esta activo |
| common_name | string | Nombre completo |
| date_joined | string | Fecha de creacion |
| last_login | string | Ultimo login |
| group_ids | []float64 | IDs de grupos |
| locale | string | Locale del usuario |
+107
View File
@@ -0,0 +1,107 @@
package infra
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// metabaseRequest ejecuta una peticion HTTP contra la API de Metabase.
// method: GET, POST, PUT, DELETE
// baseURL: URL base sin trailing slash
// token: session token o API key
// path: ruta relativa (ej: "/api/user")
// body: payload JSON (nil para requests sin body)
// Retorna el body deserializado como map o nil si el body esta vacio.
func metabaseRequest(method, baseURL, token, path string, body map[string]any) (map[string]any, error) {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("marshal body: %w", err)
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequest(method, baseURL+path, reqBody)
if err != nil {
return nil, fmt.Errorf("new request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Metabase-Session", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("http %s %s: %w", method, path, err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("metabase %s %s: status %d: %s", method, path, resp.StatusCode, string(respBody))
}
if len(respBody) == 0 {
return nil, nil
}
var result map[string]any
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("unmarshal response: %w", err)
}
return result, nil
}
// metabaseRequestList es como metabaseRequest pero para endpoints que retornan un array JSON.
func metabaseRequestList(method, baseURL, token, path string, body map[string]any) ([]map[string]any, error) {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("marshal body: %w", err)
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequest(method, baseURL+path, reqBody)
if err != nil {
return nil, fmt.Errorf("new request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Metabase-Session", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("http %s %s: %w", method, path, err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("metabase %s %s: status %d: %s", method, path, resp.StatusCode, string(respBody))
}
if len(respBody) == 0 {
return nil, nil
}
var result []map[string]any
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("unmarshal response: %w", err)
}
return result, nil
}
+25
View File
@@ -0,0 +1,25 @@
package infra
import "fmt"
// MetabaseListCards lista preguntas/cards de Metabase.
// filter: "all", "mine", "fav", "archived", "recent", "popular", "database", "table" (vacio = todas).
// modelID: ID de database o tabla cuando filter es "database" o "table" (0 = ignorar).
func MetabaseListCards(client MetabaseClient, filter string, modelID int) ([]map[string]any, error) {
path := "/api/card"
sep := "?"
if filter != "" {
path += sep + "f=" + filter
sep = "&"
}
if modelID > 0 {
path += fmt.Sprintf("%smodel_id=%d", sep, modelID)
}
result, err := metabaseRequestList("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase list cards: %w", err)
}
return result, nil
}
+63
View File
@@ -0,0 +1,63 @@
---
name: metabase_list_cards
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseListCards(client MetabaseClient, filter string, modelID int) ([]map[string]any, error)"
description: "Lista preguntas/cards de Metabase con filtro opcional. Retorna array de cards. Endpoint: GET /api/card."
tags: [metabase, card, question, list, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_list_cards.go"
---
## Ejemplo
```go
// Listar todas las cards
cards, err := MetabaseListCards(client, "all", 0)
// Solo mis preguntas
cards, err := MetabaseListCards(client, "mine", 0)
// Cards de una database especifica
cards, err := MetabaseListCards(client, "database", 1)
// Cards archivadas
cards, err := MetabaseListCards(client, "archived", 0)
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| filter | string | no | "all", "mine", "fav", "archived", "recent", "popular", "database", "table". Vacio = todas |
| modelID | int | no | ID de database/tabla. Solo aplica con filter "database" o "table". 0 = ignorar |
No tiene paginacion con offset/limit. Retorna todas las cards que coinciden.
### Campos principales de cada card
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID numerico de la card |
| name | string | Nombre de la pregunta |
| description | string | Descripcion |
| display | string | Tipo de visualizacion (table, bar, line, pie, etc.) |
| collection_id | float64 | ID de la coleccion/carpeta |
| database_id | float64 | ID de la database |
| creator_id | float64 | ID del creador |
| archived | bool | Esta archivada |
| dataset_query | map | Query de la card (native o structured) |
@@ -0,0 +1,19 @@
package infra
import "fmt"
// MetabaseListDashboards lista dashboards de Metabase.
// filter: "all", "mine" o "archived" (vacio = todas).
func MetabaseListDashboards(client MetabaseClient, filter string) ([]map[string]any, error) {
path := "/api/dashboard"
if filter != "" {
path += "?f=" + filter
}
result, err := metabaseRequestList("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase list dashboards: %w", err)
}
return result, nil
}
@@ -0,0 +1,57 @@
---
name: metabase_list_dashboards
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseListDashboards(client MetabaseClient, filter string) ([]map[string]any, error)"
description: "Lista dashboards de Metabase con filtro opcional. Retorna array de dashboards resumidos (sin dashcards). Endpoint: GET /api/dashboard."
tags: [metabase, dashboard, list, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_list_dashboards.go"
---
## Ejemplo
```go
// Listar todos los dashboards
dashboards, err := MetabaseListDashboards(client, "all")
// Solo mis dashboards
dashboards, err := MetabaseListDashboards(client, "mine")
// Dashboards archivados
dashboards, err := MetabaseListDashboards(client, "archived")
```
## Notas
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| filter | string | no | "all", "mine", "archived". Vacio = todas |
Retorna dashboards resumidos (sin cards). Para ver las cards de un dashboard, usar MetabaseGetDashboard.
### Campos principales de cada dashboard
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID del dashboard |
| name | string | Nombre |
| description | string | Descripcion |
| collection_id | float64 | Coleccion contenedora |
| creator_id | float64 | ID del creador |
| archived | bool | Archivado |
| created_at | string | Fecha creacion |
+30
View File
@@ -0,0 +1,30 @@
package infra
import "fmt"
// MetabaseListUsers lista usuarios de Metabase con filtros opcionales.
// status: "active", "deactivated" o "all" (vacio = "active").
// query: filtro por nombre o email (vacio = sin filtro).
// limit/offset: paginacion (0 = valores por defecto de Metabase).
func MetabaseListUsers(client MetabaseClient, status, query string, limit, offset int) (map[string]any, error) {
path := "/api/user?"
if status != "" {
path += "status=" + status + "&"
}
if query != "" {
path += "query=" + query + "&"
}
if limit > 0 {
path += fmt.Sprintf("limit=%d&", limit)
}
if offset > 0 {
path += fmt.Sprintf("offset=%d&", offset)
}
result, err := metabaseRequest("GET", client.BaseURL, client.Token, path, nil)
if err != nil {
return nil, fmt.Errorf("metabase list users: %w", err)
}
return result, nil
}
+67
View File
@@ -0,0 +1,67 @@
---
name: metabase_list_users
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseListUsers(client MetabaseClient, status, query string, limit, offset int) (map[string]any, error)"
description: "Lista usuarios de una instancia Metabase con filtros opcionales por estado, nombre/email y paginacion. Endpoint: GET /api/user. Requiere permisos de superusuario."
tags: [metabase, user, list, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_list_users.go"
---
## Ejemplo
```go
client, _ := MetabaseAuth("http://localhost:3000", "admin@example.com", "pass")
// Listar todos los usuarios activos
users, err := MetabaseListUsers(client, "active", "", 0, 0)
// Buscar usuario por email
users, err := MetabaseListUsers(client, "", "john@", 10, 0)
// Listar desactivados
users, err := MetabaseListUsers(client, "deactivated", "", 25, 0)
```
## Notas
Retorna un map con la estructura paginada de Metabase:
- `data`: array de objetos usuario (id, email, first_name, last_name, is_superuser, etc.)
- `total`: numero total de usuarios que coinciden
- `limit`: tamanio de pagina usado
- `offset`: offset usado
### Parametros para un LLM
| Parametro | Tipo | Requerido | Descripcion |
|-----------|------|-----------|-------------|
| client | MetabaseClient | si | Cliente autenticado |
| status | string | no | "active" (default), "deactivated", "all" |
| query | string | no | Filtro por nombre o email |
| limit | int | no | Tamanio de pagina (0 = default Metabase) |
| offset | int | no | Offset para paginacion (0 = inicio) |
### Campos del usuario retornado
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| id | float64 | ID numerico del usuario |
| email | string | Email unico |
| first_name | string | Nombre |
| last_name | string | Apellido |
| is_superuser | bool | Es admin |
| is_active | bool | Esta activo |
| common_name | string | Nombre completo |
| last_login | string | Fecha ultimo login |
+18
View File
@@ -0,0 +1,18 @@
package infra
import "fmt"
// MetabaseUpdateCard actualiza campos de una card/pregunta en Metabase.
// fields es un map con los campos a actualizar.
// Campos comunes: name, description, display, dataset_query, visualization_settings,
// collection_id, archived, enable_embedding.
func MetabaseUpdateCard(client MetabaseClient, cardID int, fields map[string]any) (map[string]any, error) {
path := fmt.Sprintf("/api/card/%d", cardID)
result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields)
if err != nil {
return nil, fmt.Errorf("metabase update card %d: %w", cardID, err)
}
return result, nil
}
+69
View File
@@ -0,0 +1,69 @@
---
name: metabase_update_card
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateCard(client MetabaseClient, cardID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza campos de una card/pregunta en Metabase. Solo se modifican los campos incluidos en el map. Endpoint: PUT /api/card/:id."
tags: [metabase, card, question, update, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_update_card.go"
---
## Ejemplo
```go
// Cambiar nombre y descripcion
card, err := MetabaseUpdateCard(client, 42, map[string]any{
"name": "Updated Revenue Chart",
"description": "Now includes refunds",
})
// Archivar una card (soft-delete)
card, err := MetabaseUpdateCard(client, 42, map[string]any{
"archived": true,
})
// Mover a otra coleccion
card, err := MetabaseUpdateCard(client, 42, map[string]any{
"collection_id": 10,
})
// Cambiar la query SQL
card, err := MetabaseUpdateCard(client, 42, map[string]any{
"dataset_query": map[string]any{
"database": 1,
"type": "native",
"native": map[string]any{"query": "SELECT * FROM users LIMIT 100"},
},
})
```
## Notas
### Campos actualizables
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| name | string | Nombre de la pregunta |
| description | string | Descripcion |
| display | string | Tipo de visualizacion |
| dataset_query | map | Query SQL o MBQL |
| visualization_settings | map | Config de visualizacion |
| collection_id | int | Mover a otra coleccion |
| archived | bool | Archivar/desarchivar (soft-delete) |
| enable_embedding | bool | Habilitar embedding publico |
| embedding_params | map | Parametros de embedding |
Solo incluir los campos que se quieren cambiar.
Para eliminar permanentemente usar MetabaseDeleteCard. Para soft-delete usar archived: true.
@@ -0,0 +1,23 @@
package infra
import "fmt"
// MetabaseUpdateDashboard actualiza un dashboard en Metabase.
// fields puede incluir metadata del dashboard Y/O la lista completa de dashcards y tabs.
//
// Para gestionar cards en el dashboard, incluir "dashcards" en fields:
// - Agregar card: incluirla con ID negativo (ej: -1, -2)
// - Actualizar card: incluirla con su ID positivo existente
// - Eliminar card: omitirla del array (el array es el estado deseado completo)
//
// Campos comunes: name, description, archived, parameters, dashcards, tabs, collection_id.
func MetabaseUpdateDashboard(client MetabaseClient, dashboardID int, fields map[string]any) (map[string]any, error) {
path := fmt.Sprintf("/api/dashboard/%d", dashboardID)
result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields)
if err != nil {
return nil, fmt.Errorf("metabase update dashboard %d: %w", dashboardID, err)
}
return result, nil
}
@@ -0,0 +1,110 @@
---
name: metabase_update_dashboard
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateDashboard(client MetabaseClient, dashboardID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza un dashboard en Metabase incluyendo metadata, cards y tabs. El campo dashcards representa el estado completo deseado: cards nuevas con ID negativo, existentes con ID positivo, omitidas se eliminan. Endpoint: PUT /api/dashboard/:id."
tags: [metabase, dashboard, update, cards, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_update_dashboard.go"
---
## Ejemplo
```go
// Cambiar nombre
MetabaseUpdateDashboard(client, 1, map[string]any{
"name": "Updated Dashboard",
})
// Agregar una card al dashboard
// Primero obtener las dashcards existentes
dash, _ := MetabaseGetDashboard(client, 1)
existingCards := dash["dashcards"].([]any)
// Construir nuevo array con las existentes + la nueva
dashcards := make([]map[string]any, 0)
for _, dc := range existingCards {
dashcards = append(dashcards, dc.(map[string]any))
}
// Agregar nueva card (ID negativo = nueva)
dashcards = append(dashcards, map[string]any{
"id": -1, "card_id": 55, "size_x": 6, "size_y": 4, "col": 0, "row": 0,
})
MetabaseUpdateDashboard(client, 1, map[string]any{
"dashcards": dashcards,
})
// Archivar dashboard (soft-delete)
MetabaseUpdateDashboard(client, 1, map[string]any{"archived": true})
```
## Notas
### Gestion de dashcards (IMPORTANTE)
El array `dashcards` representa el **estado completo deseado** del dashboard:
| Accion | Como hacerlo |
|--------|-------------|
| Agregar card | Incluir con **ID negativo** (-1, -2, etc.) |
| Actualizar card | Incluir con su **ID positivo** existente |
| Eliminar card | **Omitir** del array |
| No cambiar cards | No incluir el campo dashcards |
**Flujo tipico para agregar una card:**
1. `MetabaseGetDashboard` para obtener dashcards existentes
2. Copiar las existentes al nuevo array
3. Agregar la nueva con ID negativo
4. Enviar el array completo
### Estructura de una dashcard
```go
map[string]any{
"id": -1, // negativo = nueva, positivo = existente
"card_id": 42, // ID de la card/pregunta
"size_x": 6, // ancho (1-18)
"size_y": 4, // alto
"col": 0, // columna (0-based)
"row": 0, // fila (0-based)
"dashboard_tab_id": nil, // tab (nil = sin tabs)
"parameter_mappings": []map[string]any{}, // mapeo de filtros
"visualization_settings": map[string]any{}, // settings custom
}
```
### Gestion de tabs
```go
map[string]any{
"tabs": []map[string]any{
{"id": 1, "name": "Overview"}, // tab existente
{"id": -1, "name": "Details"}, // tab nuevo (ID negativo)
},
}
```
### Campos actualizables
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| name | string | Nombre del dashboard |
| description | string | Descripcion |
| archived | bool | Archivar/desarchivar |
| dashcards | []map | Estado completo de cards |
| tabs | []map | Tabs del dashboard |
| parameters | []map | Filtros del dashboard |
| collection_id | int | Mover a otra coleccion |
+17
View File
@@ -0,0 +1,17 @@
package infra
import "fmt"
// MetabaseUpdateUser actualiza campos de un usuario en Metabase.
// fields es un map con los campos a actualizar. Campos validos:
// first_name, last_name, email, is_superuser, group_ids, locale, login_attributes.
func MetabaseUpdateUser(client MetabaseClient, userID int, fields map[string]any) (map[string]any, error) {
path := fmt.Sprintf("/api/user/%d", userID)
result, err := metabaseRequest("PUT", client.BaseURL, client.Token, path, fields)
if err != nil {
return nil, fmt.Errorf("metabase update user %d: %w", userID, err)
}
return result, nil
}
+58
View File
@@ -0,0 +1,58 @@
---
name: metabase_update_user
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func MetabaseUpdateUser(client MetabaseClient, userID int, fields map[string]any) (map[string]any, error)"
description: "Actualiza campos de un usuario en Metabase. Solo se modifican los campos incluidos en el map. Requiere permisos de superusuario. Endpoint: PUT /api/user/:id."
tags: [metabase, user, update, api]
uses_functions: []
uses_types: [MetabaseClient_go_infra]
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [fmt]
tested: false
tests: []
test_file_path: ""
file_path: "functions/infra/metabase_update_user.go"
---
## Ejemplo
```go
// Cambiar nombre
user, err := MetabaseUpdateUser(client, 5, map[string]any{
"first_name": "Jane",
"last_name": "Smith",
})
// Promover a admin
user, err := MetabaseUpdateUser(client, 5, map[string]any{
"is_superuser": true,
})
// Cambiar grupos
user, err := MetabaseUpdateUser(client, 5, map[string]any{
"group_ids": []int{1, 3, 5},
})
```
## Notas
### Campos actualizables
| Campo | Tipo | Descripcion |
|-------|------|-------------|
| first_name | string | Nombre |
| last_name | string | Apellido |
| email | string | Email (debe ser unico) |
| is_superuser | bool | Permisos de admin |
| group_ids | []int | IDs de grupos del usuario |
| locale | string | Locale (ej: "es", "en") |
| login_attributes | map | Atributos para sandboxing |
Solo incluir los campos que se quieren cambiar. Los demas se mantienen sin modificar.
Retorna el objeto usuario actualizado.
+6
View File
@@ -20,3 +20,9 @@ type ImageInfo struct {
Size string Size string
Created string Created string
} }
// MetabaseClient holds the connection details for a Metabase instance API.
type MetabaseClient struct {
BaseURL string // e.g. "http://localhost:3000"
Token string // session token or API key
}
+7
View File
@@ -0,0 +1,7 @@
package infra
// MetabaseClient holds the connection details for a Metabase instance API.
type MetabaseClient struct {
BaseURL string // e.g. "http://localhost:3000"
Token string // session token or API key
}
+24
View File
@@ -0,0 +1,24 @@
---
name: MetabaseClient
lang: go
domain: infra
version: "1.0.0"
algebraic: product
definition: |
type MetabaseClient struct {
BaseURL string
Token string
}
description: "Cliente para la API REST de Metabase. Contiene la URL base de la instancia y el token de autenticacion (session token o API key)."
tags: [metabase, api, client, infra]
uses_types: []
file_path: "types/infra/metabase_client.go"
---
## Notas
Tipo producto con dos campos obligatorios:
- `BaseURL`: URL base de la instancia Metabase sin trailing slash (ej: `http://localhost:3000`)
- `Token`: token de sesion obtenido con `MetabaseAuth()` o una API key creada en el admin UI de Metabase
El token se envia como header `X-Metabase-Session` en session tokens o `x-api-key` en API keys. Las funciones del registry usan `X-Metabase-Session` por defecto.