Files
base/plugins/functions/functions.go
zeekay 70a06029a4 base: route convention /api/* -> /v1/*
One path: /v1/*. We do not use /api/ — that's Casdoor's path. The
BASE_API_PREFIX env knob is gone; the prefix is hard-coded in
apis/base.go and apis/serve.go banner.

Server-side mounts:
- apis: bindHealthApi, bindCollectionApi, bindRecordCrudApi, etc. all
  attach under r.Group("/v1") (was r.Group(BASE_API_PREFIX, default /api))
- platform plugin: /api/platform/* -> /v1/platform/* (including auth and
  org sub-groups)
- kms plugin: /api/kms/* -> /v1/kms/*
- functions plugin: /api/functions/* -> /v1/functions/*
- cloudsql plugin: /api/cloud-sql/*, /api/meta/* -> /v1/*
- superuser OIDC: /_/api/oidc/config -> /_/v1/oidc/config
- core settings: default RateLimitRule labels /api/batch, /api/ ->
  /v1/batch, /v1/
- apis/batch.go: ValidBatchActions regexes match /v1/collections/*;
  internal rewrite targets /v1/collections/* (was /v1/base/collections/*,
  a stale artifact)

Clients:
- sdk/base-js (BaseClient.collections, files, realtime, CRDT WS,
  signIn/refresh/health) targets /v1/*
- sdk/base-react (use-crdt WS path) targets /v1/crdt
- ui-react admin (lib/api.ts, settings.application copy) targets /v1/*
- cmd/cli (NewClient.APIPrefix) defaults to /v1; client_test.go and
  cmd/cli_test.go stubServer use /v1/*
- cmd/typegen calls /v1/collections

Upstream paths untouched: Casdoor IAM (/api/login, /api/userinfo,
/api/signup, /api/send-verification-code, /api/get-user), KMS Infisical
(/api/v1/auth/universal-auth/login, /api/v2/workspace/environments),
Kubernetes tasks (/api/v1/namespaces/...), IPFS (/api/v0/...), Gitea/
GitLab/Discord/Strava/Patreon/Box/Wakatime upstream APIs. Those are
their contracts, not ours.

Tests: all 437 prior /api/ test URLs across apis/*_test.go and
plugins/platform/auth_proxy_test.go updated to /v1/. Build + go vet
clean. ./apis ./core ./plugins/platform ./plugins/jsvm ./forms all
green. Pre-existing red probes (network/TestRedProbe_*,
migratecmd/TestAutomigrateCollection*) unchanged.
2026-05-15 14:41:26 -07:00

98 lines
2.4 KiB
Go

// Package functions implements serverless function management for Hanzo Base
// via OpenFaaS. It provides per-tenant function deployment, invocation, and
// lifecycle management through the Base API surface.
//
// Example:
//
// functions.MustRegister(app, functions.Config{
// GatewayURL: "http://openfaas-gateway.hanzo.svc:8080",
// FunctionNamespace: "openfaas-fn",
// RegistryURL: "registry.hanzo.svc:5000",
// })
package functions
import (
"net/http"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/tools/router"
)
// Config holds the Functions plugin configuration.
type Config struct {
// GatewayURL is the OpenFaaS gateway URL.
GatewayURL string
// FunctionNamespace is the K8s namespace for function deployments.
FunctionNamespace string
// RegistryURL is the container registry URL for function images.
RegistryURL string
}
// MustRegister registers the Functions plugin and panics on failure.
func MustRegister(app core.App, config Config) {
if err := Register(app, config); err != nil {
panic(err)
}
}
// Register registers the Functions plugin to the provided app instance.
func Register(app core.App, config Config) error {
if config.GatewayURL == "" {
config.GatewayURL = "http://openfaas-gateway.hanzo.svc:8080"
}
if config.FunctionNamespace == "" {
config.FunctionNamespace = "openfaas-fn"
}
if config.RegistryURL == "" {
config.RegistryURL = "registry.hanzo.svc:5000"
}
p := &plugin{
app: app,
config: config,
client: &http.Client{},
}
app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error {
if err := e.Next(); err != nil {
return err
}
return p.ensureCollections()
})
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
p.registerRoutes(e.Router)
return e.Next()
})
return nil
}
type plugin struct {
app core.App
config Config
client *http.Client
}
// --------------------------------------------------------------------------
// Routes
// --------------------------------------------------------------------------
func (p *plugin) registerRoutes(r *router.Router[*core.RequestEvent]) {
api := r.Group("/v1/functions")
// Function CRUD
api.POST("", p.handleDeployFunction)
api.GET("", p.handleListFunctions)
api.GET("/{name}", p.handleGetFunction)
api.DELETE("/{name}", p.handleDeleteFunction)
// Invocation
api.POST("/{name}/invoke", p.handleInvokeFunction)
// Logs
api.GET("/{name}/logs", p.handleGetLogs)
}