package main import ( "context" "database/sql" "net/http" "os" "path/filepath" "sync" ) var ( registryDBOnce sync.Once registryDBConn *sql.DB registryDBErr error ) func resolveRegistryDBPath() string { if p := os.Getenv("FN_REGISTRY_DB"); p != "" { return p } if root := os.Getenv("FN_REGISTRY_ROOT"); root != "" { return filepath.Join(root, "registry.db") } return "/home/lucas/fn_registry/registry.db" } func openRegistryDB() (*sql.DB, error) { registryDBOnce.Do(func() { path := resolveRegistryDBPath() // Read-only access — registry.db es source of truth gestionada via `fn index`. db, err := sql.Open("sqlite3", "file:"+path+"?mode=ro&_query_only=1") if err != nil { registryDBErr = err return } registryDBConn = db }) return registryDBConn, registryDBErr } func (s *Server) handleFunctionByID(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id == "" { writeError(w, http.StatusBadRequest, "id is required") return } db, err := openRegistryDB() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } ctx, cancel := context.WithTimeout(r.Context(), queryTimeout) defer cancel() // Try functions table first. const fnQuery = `SELECT id, name, kind, lang, domain, version, purity, signature, description, tags, returns, returns_optional, error_type, file_path, code, documentation FROM functions WHERE id = ?` var ( fnID, name, kind, lang, domain, version, purity, signature string description, tags, returns, errorType, filePath string code, documentation string returnsOptional bool ) err = db.QueryRowContext(ctx, fnQuery, id).Scan( &fnID, &name, &kind, &lang, &domain, &version, &purity, &signature, &description, &tags, &returns, &returnsOptional, &errorType, &filePath, &code, &documentation, ) if err == nil { writeJSON(w, http.StatusOK, map[string]any{ "entity": "function", "id": fnID, "name": name, "kind": kind, "lang": lang, "domain": domain, "version": version, "purity": purity, "signature": signature, "description": description, "tags": tags, "returns": returns, "returns_optional": returnsOptional, "error_type": errorType, "file_path": filePath, "code": code, "documentation": documentation, }) return } if err != sql.ErrNoRows { writeError(w, http.StatusInternalServerError, err.Error()) return } // Fallback: types table. const typeQuery = `SELECT id, name, lang, domain, version, description, tags, definition, file_path FROM types WHERE id = ?` var ( tID, tName, tLang, tDomain, tVersion string tDescription, tTags, tDefinition string tFilePath string ) err = db.QueryRowContext(ctx, typeQuery, id).Scan( &tID, &tName, &tLang, &tDomain, &tVersion, &tDescription, &tTags, &tDefinition, &tFilePath, ) if err == nil { writeJSON(w, http.StatusOK, map[string]any{ "entity": "type", "id": tID, "name": tName, "lang": tLang, "domain": tDomain, "version": tVersion, "description": tDescription, "tags": tTags, "definition": tDefinition, "file_path": tFilePath, }) return } if err == sql.ErrNoRows { writeError(w, http.StatusNotFound, "not_found") return } writeError(w, http.StatusInternalServerError, err.Error()) }