From a158961e200335b54019266b72c85045b1eb7d48 Mon Sep 17 00:00:00 2001 From: Kevin Hoffman Date: Wed, 6 Mar 2024 09:58:35 -0500 Subject: [PATCH] now only ever publishes 1 initialized event --- internal/globalservice/event_api.go | 1 + internal/globalservice/proj_catalog.go | 12 +++++------ internal/globalservice/whoami.go | 20 +++++++++++++++++- internal/models/global_api.go | 1 + natster/catalog.go | 5 +++++ natster/context.go | 13 ++++++++++-- natster/init.go | 29 ++++++++++++++++++-------- 7 files changed, 63 insertions(+), 18 deletions(-) diff --git a/internal/globalservice/event_api.go b/internal/globalservice/event_api.go index 952cd3b..e054ea6 100644 --- a/internal/globalservice/event_api.go +++ b/internal/globalservice/event_api.go @@ -221,6 +221,7 @@ func handleEventPut(srv *GlobalService) func(m *nats.Msg) { } } +// NOTE: this is safe to publish now because we're no longer writing multiple initialized events func (srv *GlobalService) publishSynadiaHubAutoShare(targetKey string) error { slog.Info("Detected Natster initialized event, auto-sharing synadia hub.") subject := fmt.Sprintf("natster.events.%s.%s.synadiahub.%s", diff --git a/internal/globalservice/proj_catalog.go b/internal/globalservice/proj_catalog.go index a3df05c..8d9181c 100644 --- a/internal/globalservice/proj_catalog.go +++ b/internal/globalservice/proj_catalog.go @@ -191,6 +191,10 @@ func (srv *GlobalService) AllCatalogs() ([]string, error) { func handleValidateName(srv *GlobalService) func(m *nats.Msg) { return func(m *nats.Msg) { candidateName := string(m.Data) + res := models.CatalogNameValidationResult{ + Valid: true, + Message: "", + } allKeys, err := srv.AllCatalogs() if err != nil { slog.Error("Failed to query list of all catalogs", slog.Any("error", err)) @@ -198,15 +202,11 @@ func handleValidateName(srv *GlobalService) func(m *nats.Msg) { return } inUse := slices.Contains(allKeys, candidateName) - res := models.CatalogNameValidationResult{ - Valid: true, - Message: "", - } + if inUse { res.Valid = false res.Message = "Catalog name has already been shared" - } - if !isAlpha(candidateName) { + } else if !isAlpha(candidateName) { res.Valid = false res.Message = "Catalog name must contain only numbers and letters if it is to be made shareable" } diff --git a/internal/globalservice/whoami.go b/internal/globalservice/whoami.go index b2c085e..a3ec113 100644 --- a/internal/globalservice/whoami.go +++ b/internal/globalservice/whoami.go @@ -3,8 +3,10 @@ package globalservice import ( "context" "log/slog" + "time" "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" "github.com/nats-io/nkeys" "github.com/synadia-io/control-plane-sdk-go/syncp" "github.com/synadia-labs/natster/internal/models" @@ -16,15 +18,31 @@ func handleWhoAmi(srv *GlobalService) func(m *nats.Msg) { oauth, err := srv.GetOAuthIdForAccount(accountKey) if err != nil { slog.Error("Failed to query OAuth ID for account", err) - _ = m.Respond(models.NewApiResultFail("Not Found", 404)) + _ = m.Respond(models.NewApiResultFail("Internal server error", 500)) + return + } + js, _ := jetstream.New(srv.nc) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + kv, err := js.KeyValue(ctx, accountProjectionBucketName) + if err != nil { + slog.Error("Failed to get key value store", slog.Any("error", err)) + _ = m.Respond(models.NewApiResultFail("Internal server error", 500)) return } + initialized := int64(0) + act, err := loadAccount(kv, accountKey) + if err == nil { + initialized = act.InitializedAt + } // Note: a non-error but nil oauth is valid - just means it hasn't been context // bound yet resp := models.WhoamiResponse{ AccountKey: accountKey, OAuthIdentity: oauth, + Initialized: initialized, } _ = m.Respond(models.NewApiResultPass(resp)) } diff --git a/internal/models/global_api.go b/internal/models/global_api.go index 67b5ec2..ed64826 100644 --- a/internal/models/global_api.go +++ b/internal/models/global_api.go @@ -61,6 +61,7 @@ type CatalogShareSummary struct { type WhoamiResponse struct { AccountKey string `json:"account_key"` OAuthIdentity *string `json:"oauth_id,omitempty"` + Initialized int64 `json:"initialized"` } type ContextQueryResponse struct { diff --git a/natster/catalog.go b/natster/catalog.go index c733957..00c8e8a 100644 --- a/natster/catalog.go +++ b/natster/catalog.go @@ -298,6 +298,11 @@ func ShareCatalog(ctx *fisk.ParseContext) error { if err != nil { return err } + if nctx.AccountPublicKey == ShareOpts.AccountKey { + fmt.Println("You cannot share catalogs with yourself.") + return nil + } + client, err := globalservice.NewClientWithCredsPath(nctx.CredsPath) if err != nil { slog.Error( diff --git a/natster/context.go b/natster/context.go index 3dec621..3bbe0c7 100644 --- a/natster/context.go +++ b/natster/context.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "time" "github.com/choria-io/fisk" "github.com/jedib0t/go-pretty/v6/table" @@ -23,15 +24,23 @@ func DisplayContext(ctx *fisk.ParseContext) error { } idString := "(unlinked)" + initializedOn := "(never)" whoami, _ := client.Whoami() - if whoami != nil && whoami.OAuthIdentity != nil { - idString = *whoami.OAuthIdentity + if whoami != nil { + if whoami.OAuthIdentity != nil { + idString = *whoami.OAuthIdentity + } + if whoami.Initialized > 0 { + t := time.Unix(whoami.Initialized, 0) + initializedOn = t.Format("2006-01-02 15:04:05") + } } t := newTableWriter(ctxx.AccountName, "cyan") w := t.writer w.AppendRow(table.Row{"Account", ctxx.AccountPublicKey}) + w.AppendRow(table.Row{"Initialized At", initializedOn}) w.AppendRow(table.Row{"Synadia Cloud Team", ctxx.TeamID}) w.AppendRow(table.Row{"Synadia Cloud System", ctxx.SystemID}) w.AppendRow(table.Row{"Synadia Cloud User", ctxx.UserID}) diff --git a/natster/init.go b/natster/init.go index d15993d..fafe9d6 100644 --- a/natster/init.go +++ b/natster/init.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path" + "time" "github.com/AlecAivazis/survey/v2" "github.com/choria-io/fisk" @@ -185,19 +186,29 @@ func InitNatster(ctx *fisk.ParseContext) error { return err } globalClient := globalservice.NewClient(conn) - data, _ := json.Marshal(models.NatsterInitializedEvent{ - AccountId: newCtx.AccountID, - AccountName: newCtx.AccountName, - AccountKey: newCtx.AccountPublicKey, - }) - err = globalClient.PublishEvent(models.NatsterInitializedEventType, "none", "none", data) + whoami, err := globalClient.Whoami() if err != nil { - fmt.Printf("Failed to contact Natster global service to post initialization event: %s", err) - return err + fmt.Println("There was a problem querying the global service. You may need to re-run `init`") + } else { + if whoami.Initialized == 0 { + data, _ := json.Marshal(models.NatsterInitializedEvent{ + AccountId: newCtx.AccountID, + AccountName: newCtx.AccountName, + AccountKey: newCtx.AccountPublicKey, + }) + err = globalClient.PublishEvent(models.NatsterInitializedEventType, "none", "none", data) + if err != nil { + fmt.Printf("Failed to contact Natster global service to post initialization event: %s", err) + return err + } + } else { + t := time.Unix(whoami.Initialized, 0) + fmt.Printf("Note: this account was previously initialized on %s\n", t.Format("2006-01-02 15:04:05")) + } } fmt.Printf("Congratulations! Your account (%s) is ready to serve Natster catalogs!\n", accountName) - fmt.Println("You can now use `natster catalog serve` to host a media catalog and `natster catalog share` to share with friends.") + fmt.Println("To get started, you'll want to do the following:\n1. `natster catalog new` to create a catalog\n2. `natster catalog serve` to host the media catalog\n3. `natster catalog share` to share with friends.") return nil }