mirror of
https://github.com/hanzoai/base.git
synced 2026-11-02 07:27:53 +00:00
Rename / namespace: - Finish github.com/pocketbase/* → github.com/hanzoai/base (+ /dbx) sweep across all Go sources; restore the renamed root launcher (Base, base.go). - Drop the upstream-only surfaces ours doesn't ship (admin ui/, the password/OTP/oauth2/impersonate auth files, extensions) re-added by the upstream merge. - Decomplect the on-disk layout to one base_/ namespace: data/, migrations/, hooks/, types.d.ts are siblings under base_/ (default data dir base_/data). Drop the hz_ prefix everywhere; collapse migratecmd's identical JS/Go branches to one path. - Remove the accidentally-committed hz_data/ dev dir (db + iam.key) and gitignore the runtime namespace dirs. Deps / build: - kmsclient now imported from the light github.com/hanzoai/kms/sdk/go module (the root kms module zip exceeds Go's download limit); cloud/pkg/base Mount removed (lives in the fused daemon). - replicate v0.4.0 → v0.8.0; metric.Counter.Get() (no prometheus dto); network/metrics.go import fix; auth gitlab/github tidy. Tests / behavior: - Wire registerNotifyWatcherHooks; debounce notify files past the kqueue rescan window so cross-instance settings/collections reload fires. - migratecmd: exclude createdAt/updatedAt (not created/updated); regenerate Go+JS automigration goldens from canonical output. - Convert the two stale archive red-probes to assert the landed defenses (backlog cap bounds memory; forged-segment signature is rejected). IAM-native (Base = pure IAM client): - Rip the ~1k-line home-grown embedded IAM (iam_embedded*.go) and the iam-user CLI. Base no longer reinvents identity as _iam_users/_superusers rows. IAM_ENDPOINT (hanzo.id, or an in-process iam.Embed in the fused daemon) is required; Base validates IAM JWTs via JWKS only — superuser is the IAM admin claim, resolved to an ephemeral unpersisted record. - Extract the IDV reverse-proxy (/v1/idv/*) into idv_proxy.go — it was mis-colocated in the embedded onboarding file and is independent of IAM. - Bind the SQL console (/v1/sql) and test it through real IAM JWTs against a JWKS stub (no hardcoded record tokens, no persisted rows).
121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
package migrations
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/dbx"
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tools/dbutils"
|
|
)
|
|
|
|
// see https://github.com/hanzoai/base/issues/7689
|
|
func init() {
|
|
core.SystemMigrations.Register(func(txApp core.App) error {
|
|
collections, err := txApp.FindAllCollections()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, collection := range collections {
|
|
// existing system collection indexes can't be modified and view don't have indexes
|
|
if collection.System || collection.IsView() {
|
|
continue
|
|
}
|
|
|
|
masterIndexes := []struct {
|
|
Name string `db:"name"`
|
|
SQL string `db:"sql"`
|
|
}{}
|
|
|
|
err := txApp.DB().Select("name", "sql").
|
|
From("sqlite_master").
|
|
AndWhere(dbx.HashExp{
|
|
"type": "index",
|
|
"tbl_name": collection.Name,
|
|
}).
|
|
AndWhere(dbx.NewExp("sql IS NOT NULL AND name NOT LIKE 'sqlite_autoindex_%'")).
|
|
All(&masterIndexes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// no indexes
|
|
if len(masterIndexes) == 0 && len(collection.Indexes) == 0 {
|
|
continue
|
|
}
|
|
|
|
missingParsedIndexes := map[string]dbutils.Index{}
|
|
|
|
// find missing master indexes
|
|
masterLoop:
|
|
for _, masterIndex := range masterIndexes {
|
|
mParsed := dbutils.ParseIndex(masterIndex.SQL)
|
|
mParsed.SchemaName = ""
|
|
mParsed.TableName = collection.Name
|
|
|
|
for _, raw := range collection.Indexes {
|
|
cParsed := dbutils.ParseIndex(raw)
|
|
|
|
// index already exists (if needed it will be normalized on resave)
|
|
if cParsed.IndexName != "" && strings.EqualFold(cParsed.IndexName, mParsed.IndexName) {
|
|
continue masterLoop
|
|
}
|
|
}
|
|
|
|
missingParsedIndexes[masterIndex.Name] = mParsed
|
|
}
|
|
|
|
missingIndexesLoop:
|
|
for _, missing := range missingParsedIndexes {
|
|
missingSQL := missing.Build()
|
|
|
|
// it shouldn't be possible but for just in case if there is an edge case the regex doesn't cover
|
|
if missingSQL == "" {
|
|
return fmt.Errorf("failed to build sqlite_master index: %v", missing)
|
|
}
|
|
|
|
// drop the missing index to recreate later
|
|
_, err := txApp.DB().DropIndex(missing.TableName, missing.IndexName).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to drop index %s: %w", missing.IndexName, err)
|
|
}
|
|
|
|
// no recreate: duplicated single unique tokenKey or email
|
|
// (auth collections are guaranteed to have them)
|
|
if collection.IsAuth() && missing.Unique && len(missing.Columns) == 1 &&
|
|
(strings.EqualFold(missing.Columns[0].Name, core.FieldNameTokenKey) || strings.EqualFold(missing.Columns[0].Name, core.FieldNameEmail)) {
|
|
continue missingIndexesLoop
|
|
}
|
|
|
|
// no recreate: the same index definition alreay exists
|
|
// in the collection but with different name
|
|
for _, raw := range collection.Indexes {
|
|
cParsed := dbutils.ParseIndex(raw)
|
|
cParsed.IndexName = missing.IndexName
|
|
cParsed.SchemaName = missing.SchemaName
|
|
cParsed.TableName = missing.TableName
|
|
cSQL := cParsed.Build()
|
|
|
|
if missingSQL == cSQL {
|
|
continue missingIndexesLoop
|
|
}
|
|
}
|
|
|
|
// recreate: add the missing index to the collection list and
|
|
// leave the user to decide whether they want to keep it or not
|
|
// (the index could have been previously created externally, e.g. via the sqlite3 cli)
|
|
collection.Indexes = append(collection.Indexes, missingSQL)
|
|
}
|
|
|
|
// resave to trigger indexes normalization
|
|
err = txApp.Save(collection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, nil)
|
|
}
|