Files
sqlite_api/handlers_datafactory.go
T
Egutierrez 11c986edc7 chore: auto-commit (5 archivos)
- handlers.go
- handlers_test.go
- main.go
- datafactory_events.go
- handlers_datafactory.go

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:33:25 +02:00

206 lines
6.2 KiB
Go

package main
// REST handlers (read-only) for data_factory.db (issue 0097).
//
// Endpoints:
// GET /api/datafactory/nodes?kind=<kind>
// GET /api/datafactory/runs?node_id=<id>&limit=N
// GET /api/datafactory/databases
//
// All endpoints lazy-open data_factory.db on first request (creating the
// file + applying migrations if missing). If the open fails, returns 503.
// POST trigger is intentionally NOT implemented — sqlite_api is read-only;
// run insertion is done out-of-band by DAG steps / function wrappers.
import (
"context"
"database/sql"
"net/http"
"strconv"
)
// dataFactoryNode is the JSON row for /api/datafactory/nodes.
type dataFactoryNode struct {
ID string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
FunctionID string `json:"function_id"`
Description string `json:"description"`
ConfigJSON string `json:"config_json"`
ScheduleCron string `json:"schedule_cron"`
Enabled bool `json:"enabled"`
TagsCSV string `json:"tags_csv"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// dataFactoryRun is the JSON row for /api/datafactory/runs.
type dataFactoryRun struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
Status string `json:"status"`
RowsIn int64 `json:"rows_in"`
RowsOut int64 `json:"rows_out"`
KbIn int64 `json:"kb_in"`
KbOut int64 `json:"kb_out"`
DurationMS int64 `json:"duration_ms"`
Trigger string `json:"trigger"`
Error string `json:"error"`
Notes string `json:"notes"`
}
// dataFactoryDatabase is the JSON row for /api/datafactory/databases.
type dataFactoryDatabase struct {
ID string `json:"id"`
Kind string `json:"kind"`
Label string `json:"label"`
URI string `json:"uri"`
Description string `json:"description"`
TagsCSV string `json:"tags_csv"`
LastSeenAt string `json:"last_seen_at"`
TableCount int64 `json:"table_count"`
SizeBytes int64 `json:"size_bytes"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func (s *Server) handleDataFactoryNodes(w http.ResponseWriter, r *http.Request) {
db, err := s.dataFactoryDB()
if err != nil {
writeError(w, http.StatusServiceUnavailable, "data_factory.db unavailable: "+err.Error())
return
}
ctx, cancel := context.WithTimeout(r.Context(), queryTimeout)
defer cancel()
kind := r.URL.Query().Get("kind")
var rows *sql.Rows
if kind != "" {
rows, err = db.QueryContext(ctx, `
SELECT id, kind, name, function_id, description, config_json,
schedule_cron, enabled, tags_csv, created_at, updated_at
FROM nodes
WHERE kind = ?
ORDER BY kind, name`, kind)
} else {
rows, err = db.QueryContext(ctx, `
SELECT id, kind, name, function_id, description, config_json,
schedule_cron, enabled, tags_csv, created_at, updated_at
FROM nodes
ORDER BY kind, name`)
}
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
nodes := make([]dataFactoryNode, 0, 16)
for rows.Next() {
var n dataFactoryNode
var enabled int
if err := rows.Scan(&n.ID, &n.Kind, &n.Name, &n.FunctionID, &n.Description,
&n.ConfigJSON, &n.ScheduleCron, &enabled, &n.TagsCSV,
&n.CreatedAt, &n.UpdatedAt); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
n.Enabled = enabled != 0
nodes = append(nodes, n)
}
writeJSON(w, http.StatusOK, map[string]any{"nodes": nodes, "count": len(nodes)})
}
func (s *Server) handleDataFactoryRuns(w http.ResponseWriter, r *http.Request) {
db, err := s.dataFactoryDB()
if err != nil {
writeError(w, http.StatusServiceUnavailable, "data_factory.db unavailable: "+err.Error())
return
}
ctx, cancel := context.WithTimeout(r.Context(), queryTimeout)
defer cancel()
nodeID := r.URL.Query().Get("node_id")
limit := 100
if l := r.URL.Query().Get("limit"); l != "" {
if n, err := strconv.Atoi(l); err == nil && n > 0 && n <= 1000 {
limit = n
}
}
var rows *sql.Rows
if nodeID != "" {
rows, err = db.QueryContext(ctx, `
SELECT id, node_id, started_at, COALESCE(finished_at,''), status,
rows_in, rows_out, kb_in, kb_out, duration_ms, trigger, error, notes
FROM runs
WHERE node_id = ?
ORDER BY started_at DESC
LIMIT ?`, nodeID, limit)
} else {
rows, err = db.QueryContext(ctx, `
SELECT id, node_id, started_at, COALESCE(finished_at,''), status,
rows_in, rows_out, kb_in, kb_out, duration_ms, trigger, error, notes
FROM runs
ORDER BY started_at DESC
LIMIT ?`, limit)
}
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
runs := make([]dataFactoryRun, 0, 16)
for rows.Next() {
var rr dataFactoryRun
if err := rows.Scan(&rr.ID, &rr.NodeID, &rr.StartedAt, &rr.FinishedAt,
&rr.Status, &rr.RowsIn, &rr.RowsOut, &rr.KbIn, &rr.KbOut,
&rr.DurationMS, &rr.Trigger, &rr.Error, &rr.Notes); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
runs = append(runs, rr)
}
writeJSON(w, http.StatusOK, map[string]any{"runs": runs, "count": len(runs)})
}
func (s *Server) handleDataFactoryDatabases(w http.ResponseWriter, r *http.Request) {
db, err := s.dataFactoryDB()
if err != nil {
writeError(w, http.StatusServiceUnavailable, "data_factory.db unavailable: "+err.Error())
return
}
ctx, cancel := context.WithTimeout(r.Context(), queryTimeout)
defer cancel()
rows, err := db.QueryContext(ctx, `
SELECT id, kind, label, uri, description, tags_csv, last_seen_at,
table_count, size_bytes, created_at, updated_at
FROM databases
ORDER BY kind, label`)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
out := make([]dataFactoryDatabase, 0, 16)
for rows.Next() {
var d dataFactoryDatabase
if err := rows.Scan(&d.ID, &d.Kind, &d.Label, &d.URI, &d.Description,
&d.TagsCSV, &d.LastSeenAt, &d.TableCount, &d.SizeBytes,
&d.CreatedAt, &d.UpdatedAt); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
out = append(out, d)
}
writeJSON(w, http.StatusOK, map[string]any{"databases": out, "count": len(out)})
}