Files
base/plugins/org/org_isolation.go
zeekay e4b1f3c75c a Base is addressed at /v1/bases, and Base stops running a second auth
Two things wore the name "platform" here, and neither is the PaaS at
platform.hanzo.ai. That collision is why "where are my Bases?" had no answer a
person could act on: one prefix, two products, and the one you reached was
whichever you happened to mean.

A Base is what an org gets — its own collections, its own files, its own key —
so the resource a client addresses is /v1/bases. Not /v1/orgs: the org is IAM's
noun and Base does not get a second one. Everything scoped to an org now hangs
off the Base it belongs to (/v1/bases/{id}/config, /creds, /customers), and
compliance moves to /v1/compliance, a sibling of /v1/iam rather than a child of
Base's own resource. The package is `org`, which seven of its files already
were. Nothing addressed the old prefix — no client, no UI, no manifest — so this
renames a surface rather than breaking one.

The auth proxy is DELETED, not renamed. Its five routes reshaped bodies and
forwarded to /v1/iam/login, /signup, /userinfo and /send-verification-code —
which the opaque /v1/iam/{path...} passthrough beside it already reaches. So
Base carried a hand-written login, signup and one-time-code flow whose only job
was to be a second way to reach IAM. Auth is IAM's, and one way is the whole
point of that rule.

The prefix test that already banned /api/ bans this too, so the name cannot come
back: it reads route LITERALS through go/ast, so a comment explaining the ban is
prose and a route is a route. Verified to fail on the exact regression.
2026-08-10 19:18:08 -07:00

123 lines
3.0 KiB
Go

package org
import (
"fmt"
"strings"
"github.com/hanzoai/base/core"
)
// CollectionTemplate defines a collection schema that gets cloned per org.
type CollectionTemplate struct {
// Name is the base collection name (without org prefix).
// The actual collection will be created as t_{slug}_{Name}.
Name string
// Type is the collection type: "base", "auth", or "view".
// Defaults to "base" if empty.
Type string
// Fields defines the fields for the collection.
Fields []core.Field
}
// OrgPrefix returns the collection name prefix for an org slug.
// Format: t_{slug}_
func OrgPrefix(slug string) string {
return "t_" + slug + "_"
}
// ScopedQuery returns the prefixed collection name for an org.
// Example: ScopedQuery("acme", "tasks") returns "t_acme_tasks".
func ScopedQuery(orgSlug, collection string) string {
return OrgPrefix(orgSlug) + collection
}
// CreateOrgCollections creates prefixed collections for an org from the
// given templates. Each template's Name is prefixed with t_{slug}_.
//
// Collections that already exist are skipped.
func CreateOrgCollections(app core.App, orgSlug string, templates []CollectionTemplate) error {
prefix := OrgPrefix(orgSlug)
var errs []string
for _, tmpl := range templates {
if tmpl.Name == "" {
continue
}
fullName := prefix + tmpl.Name
// Skip if already exists.
_, err := app.FindCollectionByNameOrId(fullName)
if err == nil {
continue
}
colType := tmpl.Type
if colType == "" {
colType = core.CollectionTypeBase
}
collection := core.NewCollection(colType, fullName)
// Add template fields.
for _, f := range tmpl.Fields {
collection.Fields.Add(f)
}
if err := app.Save(collection); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", fullName, err))
}
}
if len(errs) > 0 {
return fmt.Errorf("failed to create org collections: %s", strings.Join(errs, "; "))
}
return nil
}
// DeleteOrgCollections removes all collections with the org's prefix.
func DeleteOrgCollections(app core.App, orgSlug string) error {
prefix := OrgPrefix(orgSlug)
allCollections, err := app.FindAllCollections()
if err != nil {
return fmt.Errorf("failed to list collections: %w", err)
}
var errs []string
for _, col := range allCollections {
if strings.HasPrefix(col.Name, prefix) {
if err := app.Delete(col); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", col.Name, err))
}
}
}
if len(errs) > 0 {
return fmt.Errorf("failed to delete org collections: %s", strings.Join(errs, "; "))
}
return nil
}
// ListOrgCollections returns all collection names belonging to an org.
func ListOrgCollections(app core.App, orgSlug string) ([]string, error) {
prefix := OrgPrefix(orgSlug)
allCollections, err := app.FindAllCollections()
if err != nil {
return nil, fmt.Errorf("failed to list collections: %w", err)
}
var names []string
for _, col := range allCollections {
if strings.HasPrefix(col.Name, prefix) {
// Return the name without prefix for cleaner display.
names = append(names, strings.TrimPrefix(col.Name, prefix))
}
}
return names, nil
}