package infra import ( "database/sql" "fmt" "net/http" "strings" "time" "github.com/google/uuid" ) // CRUDCreateHandler retorna un http.HandlerFunc que parsea un body JSON, // valida los campos contra la definicion del recurso, genera id UUID y timestamps, // inserta en la tabla y responde 201 con el registro creado. func CRUDCreateHandler(res CRUDResource, db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { body := map[string]any{} if err := HTTPParseBody(r, &body, 1<<20); err != nil { HTTPErrorResponse(w, HTTPError{Status: http.StatusBadRequest, Code: "invalid_body", Message: err.Error()}) return } // Validar campos required y validaciones for _, f := range res.Fields { val, present := body[f.Name] if !present { if f.Required && f.Default == "" { HTTPErrorResponse(w, HTTPError{Status: http.StatusBadRequest, Code: "validation_error", Message: fmt.Sprintf("field %q is required", f.Name)}) return } continue } if err := crudValidateField(f, val); err != nil { HTTPErrorResponse(w, HTTPError{Status: http.StatusBadRequest, Code: "validation_error", Message: err.Error()}) return } } id := uuid.NewString() now := time.Now().UTC().Format(time.RFC3339Nano) // Construir INSERT solo con los campos presentes cols := []string{"id"} placeholders := []string{"?"} args := []any{id} for _, f := range res.Fields { if val, present := body[f.Name]; present { cols = append(cols, f.Name) placeholders = append(placeholders, "?") args = append(args, val) } } cols = append(cols, "created_at", "updated_at") placeholders = append(placeholders, "?", "?") args = append(args, now, now) insertSQL := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", res.Table, strings.Join(cols, ", "), strings.Join(placeholders, ", ")) if _, err := db.Exec(insertSQL, args...); err != nil { // UNIQUE violations → 409 if strings.Contains(strings.ToLower(err.Error()), "unique") { HTTPErrorResponse(w, HTTPError{Status: http.StatusConflict, Code: "unique_violation", Message: err.Error()}) return } HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()}) return } // Leer de vuelta para devolver todas las columnas (incluido defaults) rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s WHERE id = ?", res.Table), id) if err != nil { HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()}) return } defer rows.Close() colsOut, err := rows.Columns() if err != nil { HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()}) return } if !rows.Next() { HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: "inserted row not found"}) return } row, err := crudScanRow(rows, colsOut) if err != nil { HTTPErrorResponse(w, HTTPError{Status: http.StatusInternalServerError, Code: "db_error", Message: err.Error()}) return } HTTPJSONResponse(w, http.StatusCreated, row) } }