Extract ExtendGrantTx, RevokeGrantTx, and ExpireGrantTx into internal/entitlements so the operator handlers, expiry activity, and demo seed all run the same conferral path. The seed plays a five-grant history on Carlos's org; pin-timestamps now uses dense-rank offsets and transition list ordering ties break by transition_id.
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/db"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/demoseed"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/integrations"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/logging"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var seedDemoCmd = &cobra.Command{
|
|
Use: "seed-demo",
|
|
Short: "Seed a fixed reference catalog of demo data for the operator panel",
|
|
Long: `Insert an idempotent reference catalog (products, entitlement sets, plan ladder,
|
|
the personal org-type default, one grant, and a ledger demo on Carlos Member's
|
|
organization) so the operator panel has data to walk
|
|
through. Re-running is a no-op. Person rows for bob/carlos/diana are pre-seeded;
|
|
Alice is not — log in as alice once (before seeding, so her pool gets floored at
|
|
the org-type default) to exercise the OIDC lazy-creation path. See OpenSpec change
|
|
2026-05-10-member-console-demo-seeder for the rationale.`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
ctx := context.Background()
|
|
|
|
env := viper.GetString("env")
|
|
logger := logging.SetupLogger(env)
|
|
ctx = logging.WithContext(ctx, logger)
|
|
|
|
dbDSN := viper.GetString("db-dsn")
|
|
dbConfig := db.DefaultDBConfig(dbDSN)
|
|
database, err := db.ConnectAndMigrate(ctx, logger, dbConfig, migrationSources())
|
|
if err != nil {
|
|
logger.Error("failed to initialize database", slog.Any("error", err))
|
|
return err
|
|
}
|
|
defer database.Close()
|
|
|
|
// A reseeded database must hold what a booted one holds (operator
|
|
// root domains, providers, the system tenant), or the demo state
|
|
// depends on whether the app has restarted since the reset.
|
|
if err := ensureBootDataState(ctx, database, logger, integrations.All()); err != nil {
|
|
logger.Error("seed-demo: boot data state", slog.Any("error", err))
|
|
return err
|
|
}
|
|
|
|
// The operator's own row comes first: the steps that need an author
|
|
// (the baseline grant, the ledger demo) look her up and skip when she
|
|
// is absent, so seeding her afterwards would leave both out of a
|
|
// one-pass snapshot build.
|
|
var summary demoseed.Summary
|
|
if withOperator, _ := cmd.Flags().GetBool("with-operator"); withOperator {
|
|
created, err := demoseed.SeedOperator(ctx, database, logger)
|
|
if err != nil {
|
|
logger.Error("seed-demo: operator person", slog.Any("error", err))
|
|
return err
|
|
}
|
|
if created {
|
|
summary.Created++
|
|
} else {
|
|
summary.Skipped++
|
|
}
|
|
}
|
|
|
|
seeded, err := demoseed.Seed(ctx, database, logger)
|
|
if err != nil {
|
|
logger.Error("seed-demo failed", slog.Any("error", err))
|
|
return err
|
|
}
|
|
summary.Created += seeded.Created
|
|
summary.Skipped += seeded.Skipped
|
|
|
|
logger.Info("seed-demo complete",
|
|
slog.Int("created", summary.Created),
|
|
slog.Int("skipped", summary.Skipped),
|
|
)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
seedDemoCmd.Flags().Bool("with-operator", false, "Also seed the operator's own person and organization (alice), which the default seed leaves to her first sign-in; used by the screen-capture utility's demo snapshot")
|
|
rootCmd.AddCommand(seedDemoCmd)
|
|
}
|