mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
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.
123 lines
3.0 KiB
Go
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
|
|
}
|