diff --git a/.changeset/green-deers-pretend.md b/.changeset/green-deers-pretend.md new file mode 100644 index 00000000000..1bb18a1d3cc --- /dev/null +++ b/.changeset/green-deers-pretend.md @@ -0,0 +1,5 @@ +--- +'@audius/harmony': patch +--- + +Fix active and hover styling for FilterButton diff --git a/core/.dockerignore b/core/.dockerignore new file mode 100644 index 00000000000..e3bcc0d2e95 --- /dev/null +++ b/core/.dockerignore @@ -0,0 +1,3 @@ +**/*.env +*.env +/tmp diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000000..adb24d5cea2 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +**/tmp diff --git a/core/Makefile b/core/Makefile new file mode 100644 index 00000000000..27a9dadb4a9 --- /dev/null +++ b/core/Makefile @@ -0,0 +1,34 @@ +dev-discovery-1:: + @go run main.go -env-file=./infra/dev_config/discovery-one.env + +dev-content-1:: + @go run main.go -env-file=./infra/dev_config/content-one.env + +dev-content-2:: + @go run main.go -env-file=./infra/dev_config/content-two.env + +dev-content-3:: + @go run main.go -env-file=./infra/dev_config/content-three.env + +up: + make down + make gen + cd infra && docker compose up --build -d + +down: + cd infra && docker compose down + +deps:: + @go install github.com/onsi/ginkgo/v2/ginkgo@v2.19.0 + @brew install protobuf + @go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest + @go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + @go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + +gen:: + cd db && sqlc generate + go mod tidy + +test:: + @go test -v ./... + @cd test/integration && ginkgo -r diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000000..1bd9da5b608 --- /dev/null +++ b/core/README.md @@ -0,0 +1,54 @@ +# audius core + +The distributed event log that binds discovery, content, and identity together. The audius L1. + +## architecture + +Built with cometbft, audius core is the L1 blockchain that stores all the canon events in the audius protocol. The core is the library that allows you to build node types around this source, publish events, index data, and materialize views that then power audius applications. + +```mermaid +graph TD + L1["Audius L1"] + L2a["Discovery Provider"] + L2b["Content Node"] + L2c["Identity Service"] + + L1 --> |"indexes discovery\n related data"| L2a + L1 --> |"indexes content\n related data"| L2b + L1 --> |"indexes identity\n related data"| L2c + +``` + +## configuration + +Whether running as an independent image or embedded, core requires some environment variables to run. + +### local cluster + +### running in production + + +## testing + +To run tests simply run the make command. This will run both unit and integration tests. + +```bash +make test +``` + +### unit tests + +Place unit tests next to appropriate files in the standard go fashion + +```bash +core/signature.go +core/signature_test.go +``` + +### integration tests + +Integration tests are written with [ginkgo](https://github.com/onsi/ginkgo) and [gomega](https://github.com/onsi/gomega) to be more readable, the way to generate one is to use the ginkgo cli installed in the `make deps` command. + +```bash +ginkgo generate NewTestName +``` diff --git a/core/accounts/signature.go b/core/accounts/signature.go new file mode 100644 index 00000000000..ec6c5a12f9c --- /dev/null +++ b/core/accounts/signature.go @@ -0,0 +1 @@ +package accounts diff --git a/core/accounts/wallet.go b/core/accounts/wallet.go new file mode 100644 index 00000000000..13fc970eb5a --- /dev/null +++ b/core/accounts/wallet.go @@ -0,0 +1,24 @@ +package accounts + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + + "github.com/cometbft/cometbft/crypto/ed25519" +) + +func EthToCometKey(hexPkey string) (ed25519.PrivKey, error) { + ethPkeyBytes, err := hex.DecodeString(hexPkey) + if err != nil { + return nil, err + } + + if len(ethPkeyBytes) != 32 { + return nil, errors.New("private key length not 32") + } + + hash := sha256.Sum256(ethPkeyBytes) + eckey := ed25519.GenPrivKeyFromSecret(hash[:]) + return eckey, nil +} diff --git a/core/accounts/wallet_test.go b/core/accounts/wallet_test.go new file mode 100644 index 00000000000..ec6c5a12f9c --- /dev/null +++ b/core/accounts/wallet_test.go @@ -0,0 +1 @@ +package accounts diff --git a/core/chain/abci.go b/core/chain/abci.go new file mode 100644 index 00000000000..763e82ef9fe --- /dev/null +++ b/core/chain/abci.go @@ -0,0 +1,170 @@ +package chain + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + + "github.com/AudiusProject/audius-protocol/core/common" + "github.com/AudiusProject/audius-protocol/core/db" + abcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type KVStoreApplication struct { + logger *common.Logger + queries *db.Queries + pool *pgxpool.Pool + onGoingBlock pgx.Tx +} + +var _ abcitypes.Application = (*KVStoreApplication)(nil) + +func NewKVStoreApplication(logger *common.Logger, pool *pgxpool.Pool) *KVStoreApplication { + return &KVStoreApplication{ + logger: logger, + queries: db.New(pool), + pool: pool, + onGoingBlock: nil, + } +} + +func (app *KVStoreApplication) Info(_ context.Context, info *abcitypes.InfoRequest) (*abcitypes.InfoResponse, error) { + return &abcitypes.InfoResponse{}, nil +} + +func (app *KVStoreApplication) Query(ctx context.Context, req *abcitypes.QueryRequest) (*abcitypes.QueryResponse, error) { + resp := abcitypes.QueryResponse{Key: req.Data} + + kv, err := app.queries.GetKey(ctx, string(req.Data)) + if err != nil { + resp.Log = err.Error() + return &resp, err + } + + value := []byte(kv.Value) + resp.Log = "exists" + resp.Value = value + + return &resp, nil +} + +func (app *KVStoreApplication) CheckTx(_ context.Context, check *abcitypes.CheckTxRequest) (*abcitypes.CheckTxResponse, error) { + code := app.isValid(check.Tx) + return &abcitypes.CheckTxResponse{Code: code}, nil +} + +func (app *KVStoreApplication) InitChain(_ context.Context, chain *abcitypes.InitChainRequest) (*abcitypes.InitChainResponse, error) { + return &abcitypes.InitChainResponse{}, nil +} + +func (app *KVStoreApplication) PrepareProposal(_ context.Context, proposal *abcitypes.PrepareProposalRequest) (*abcitypes.PrepareProposalResponse, error) { + return &abcitypes.PrepareProposalResponse{Txs: proposal.Txs}, nil +} + +func (app *KVStoreApplication) ProcessProposal(_ context.Context, proposal *abcitypes.ProcessProposalRequest) (*abcitypes.ProcessProposalResponse, error) { + return &abcitypes.ProcessProposalResponse{Status: abcitypes.PROCESS_PROPOSAL_STATUS_ACCEPT}, nil +} + +func (app *KVStoreApplication) FinalizeBlock(ctx context.Context, req *abcitypes.FinalizeBlockRequest) (*abcitypes.FinalizeBlockResponse, error) { + logger := app.logger + var txs = make([]*abcitypes.ExecTxResult, len(req.Txs)) + + // early out if empty block + if len(txs) == 0 { + return &abcitypes.FinalizeBlockResponse{ + TxResults: txs, + }, nil + } + + // open in progres pg transaction + app.startInProgressTx(ctx) + for i, tx := range req.Txs { + if code := app.isValid(tx); code != 0 { + logger.Errorf("Error: invalid transaction index %v", i) + txs[i] = &abcitypes.ExecTxResult{Code: code} + } else { + parts := bytes.SplitN(tx, []byte("="), 2) + key, value := parts[0], parts[1] + logger.Infof("Adding key %s with value %s", key, value) + + qtx := app.getDb() + + hash := sha256.Sum256(tx) + txHash := hex.EncodeToString(hash[:]) + + params := db.InsertKVStoreParams{ + Key: string(key), + Value: string(value), + TxHash: txHash, + } + + record, err := qtx.InsertKVStore(ctx, params) + if err != nil { + logger.Errorf("failed to persisted kv entry %v", err) + } + + txs[i] = &abcitypes.ExecTxResult{ + Code: 0, + Events: []abcitypes.Event{ + { + Type: "app", + Attributes: []abcitypes.EventAttribute{ + {Key: "key", Value: record.Key, Index: true}, + {Key: "value", Value: record.Value, Index: true}, + }, + }, + }, + } + } + } + + return &abcitypes.FinalizeBlockResponse{ + TxResults: txs, + }, nil +} + +func (app KVStoreApplication) Commit(ctx context.Context, commit *abcitypes.CommitRequest) (*abcitypes.CommitResponse, error) { + app.logger.Info("in commit phase", "onGoingBlock", app.onGoingBlock) + if err := app.commitInProgressTx(ctx); err != nil { + app.logger.Error("failure to commit tx", "error", err) + return &abcitypes.CommitResponse{}, err + } + return &abcitypes.CommitResponse{}, nil +} + +func (app *KVStoreApplication) ListSnapshots(_ context.Context, snapshots *abcitypes.ListSnapshotsRequest) (*abcitypes.ListSnapshotsResponse, error) { + return &abcitypes.ListSnapshotsResponse{}, nil +} + +func (app *KVStoreApplication) OfferSnapshot(_ context.Context, snapshot *abcitypes.OfferSnapshotRequest) (*abcitypes.OfferSnapshotResponse, error) { + return &abcitypes.OfferSnapshotResponse{}, nil +} + +func (app *KVStoreApplication) LoadSnapshotChunk(_ context.Context, chunk *abcitypes.LoadSnapshotChunkRequest) (*abcitypes.LoadSnapshotChunkResponse, error) { + return &abcitypes.LoadSnapshotChunkResponse{}, nil +} + +func (app *KVStoreApplication) ApplySnapshotChunk(_ context.Context, chunk *abcitypes.ApplySnapshotChunkRequest) (*abcitypes.ApplySnapshotChunkResponse, error) { + return &abcitypes.ApplySnapshotChunkResponse{Result: abcitypes.APPLY_SNAPSHOT_CHUNK_RESULT_ACCEPT}, nil +} + +func (app KVStoreApplication) ExtendVote(_ context.Context, extend *abcitypes.ExtendVoteRequest) (*abcitypes.ExtendVoteResponse, error) { + return &abcitypes.ExtendVoteResponse{}, nil +} + +func (app *KVStoreApplication) VerifyVoteExtension(_ context.Context, verify *abcitypes.VerifyVoteExtensionRequest) (*abcitypes.VerifyVoteExtensionResponse, error) { + return &abcitypes.VerifyVoteExtensionResponse{}, nil +} + +func (app *KVStoreApplication) isValid(tx []byte) uint32 { + // check format + parts := bytes.Split(tx, []byte("=")) + if len(parts) != 2 { + return 1 + } + + return 0 +} diff --git a/core/chain/db.go b/core/chain/db.go new file mode 100644 index 00000000000..471bedc7a56 --- /dev/null +++ b/core/chain/db.go @@ -0,0 +1,40 @@ +package chain + +import ( + "context" + "errors" + + "github.com/AudiusProject/audius-protocol/core/db" + "github.com/jackc/pgx/v5" +) + +// returns in current postgres tx for this block +func (c *KVStoreApplication) getDb() *db.Queries { + return c.queries.WithTx(c.onGoingBlock) +} + +func (c *KVStoreApplication) startInProgressTx(ctx context.Context) error { + dbTx, err := c.pool.Begin(ctx) + if err != nil { + return err + } + + c.onGoingBlock = dbTx + return nil +} + +// commits the current tx that's finished indexing +func (c *KVStoreApplication) commitInProgressTx(ctx context.Context) error { + if c.onGoingBlock != nil { + err := c.onGoingBlock.Commit(ctx) + if err != nil { + if errors.Is(err, pgx.ErrTxClosed) { + c.onGoingBlock = nil + return nil + } + return err + } + c.onGoingBlock = nil + } + return nil +} diff --git a/core/chain/node.go b/core/chain/node.go new file mode 100644 index 00000000000..6540680c31a --- /dev/null +++ b/core/chain/node.go @@ -0,0 +1,73 @@ +package chain + +import ( + "context" + "fmt" + + "github.com/AudiusProject/audius-protocol/core/common" + "github.com/AudiusProject/audius-protocol/core/config" + cfg "github.com/cometbft/cometbft/config" + nm "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + "github.com/jackc/pgx/v5/pgxpool" +) + +func NewNode(logger *common.Logger, c *config.Config, pool *pgxpool.Pool) (*nm.Node, error) { + homeDir := c.HomeDir + + config := cfg.DefaultConfig() + config.SetRoot(homeDir) + + // postgres indexer config + config.TxIndex.Indexer = "psql" + config.TxIndex.PsqlConn = c.PSQLConn + config.TxIndex.TableBlocks = "core_blocks" + config.TxIndex.TableTxResults = "core_tx_results" + config.TxIndex.TableEvents = "core_events" + config.TxIndex.TableAttributes = "core_attributes" + + config.P2P.PexReactor = true + + if c.PersistentPeers != "" { + config.P2P.PersistentPeers = c.PersistentPeers + } + + if c.RPCladdr != "" { + config.RPC.ListenAddress = c.RPCladdr + } + if c.P2PLaddr != "" { + config.P2P.ListenAddress = c.P2PLaddr + } + + app := NewKVStoreApplication(logger, pool) + + pv := privval.LoadFilePV( + config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile(), + ) + + nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) + if err != nil { + return nil, fmt.Errorf("failed to load node's key: %v", err) + } + + node, err := nm.NewNode( + context.Background(), + config, + pv, + nodeKey, + proxy.NewLocalClientCreator(app), + nm.DefaultGenesisDocProviderFunc(config), + cfg.DefaultDBProvider, + nm.DefaultMetricsProvider(config.Instrumentation), + logger, + ) + + if err != nil { + return nil, fmt.Errorf("creating node: %v", err) + } + + return node, nil +} diff --git a/core/common/fs.go b/core/common/fs.go new file mode 100644 index 00000000000..46b52874689 --- /dev/null +++ b/core/common/fs.go @@ -0,0 +1,19 @@ +package common + +import ( + "fmt" + "os" +) + +func FileExists(filePath string) bool { + _, err := os.Stat(filePath) + return !os.IsNotExist(err) +} + +func CreateDirIfNotExist(dir string) error { + err := os.MkdirAll(dir, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + return nil +} diff --git a/core/common/logger.go b/core/common/logger.go new file mode 100644 index 00000000000..5d0d35299fa --- /dev/null +++ b/core/common/logger.go @@ -0,0 +1,69 @@ +package common + +import ( + "fmt" + "log/slog" + "os" + + "github.com/cometbft/cometbft/libs/log" +) + +type Logger struct { + log slog.Logger +} + +func NewLogger(opts *slog.HandlerOptions) *Logger { + logger := *slog.New(slog.NewJSONHandler(os.Stdout, opts)) + return &Logger{ + logger, + } +} + +func (l *Logger) Debug(msg string, keyvals ...interface{}) { + l.log.Debug(msg, keyvals...) +} + +func (l *Logger) Info(msg string, keyvals ...interface{}) { + l.log.Info(msg, keyvals...) +} + +func (l *Logger) Error(msg string, keyvals ...interface{}) { + l.log.Error(msg, keyvals...) +} + +func (l *Logger) With(keyvals ...interface{}) log.Logger { + newLogger := l.log.With(keyvals...) + return &Logger{log: *newLogger} +} + +func (l *Logger) Debugf(msg string, keyvals ...interface{}) { + message := fmt.Sprintf(msg, keyvals...) + l.log.Debug(message) +} + +func (l *Logger) Errorf(msg string, keyvals ...interface{}) { + message := fmt.Sprintf(msg, keyvals...) + l.log.Error(message) +} + +func (l *Logger) Infof(msg string, keyvals ...interface{}) { + message := fmt.Sprintf(msg, keyvals...) + l.log.Info(message) +} + +func (l *Logger) Warningf(msg string, keyvals ...interface{}) { + message := fmt.Sprintf(msg, keyvals...) + l.log.Warn(message) +} + +func (l *Logger) Fatalf(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + l.log.Error(message) +} + +func (l *Logger) Printf(format string, v ...interface{}) { + message := fmt.Sprintf(format, v...) + l.log.Info(message) +} + +var _ log.Logger = (*Logger)(nil) diff --git a/core/config/comet.go b/core/config/comet.go new file mode 100644 index 00000000000..90ec3890702 --- /dev/null +++ b/core/config/comet.go @@ -0,0 +1,82 @@ +package config + +import ( + "fmt" + + "github.com/AudiusProject/audius-protocol/core/accounts" + "github.com/AudiusProject/audius-protocol/core/common" + "github.com/AudiusProject/audius-protocol/core/config/genesis" + "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/p2p" + "github.com/cometbft/cometbft/privval" +) + +// reads in or creates cometbft keys from the delegate private key +// also creates validator keys and genesis file +func InitComet(logger *common.Logger, environment, delegatePrivateKey, homeDir string) error { + logger.Info("initializing comet config") + + key, err := accounts.EthToCometKey(delegatePrivateKey) + if err != nil { + return fmt.Errorf("creating key %v", err) + } + + if err := common.CreateDirIfNotExist(homeDir); err != nil { + logger.Error("error creating homeDir", "error", err) + } + + if err := common.CreateDirIfNotExist(fmt.Sprintf("%s/config", homeDir)); err != nil { + logger.Error("error creating config dir", "error", err) + } + + if err := common.CreateDirIfNotExist(fmt.Sprintf("%s/data", homeDir)); err != nil { + logger.Error("error creating data dir", "error", err) + } + + config := config.DefaultConfig() + config.SetRoot(homeDir) + + privValKeyFile := config.PrivValidatorKeyFile() + privValStateFile := config.PrivValidatorStateFile() + + var pv *privval.FilePV + if common.FileExists(privValKeyFile) { + logger.Info("Found private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } else { + pv = privval.NewFilePV(key, privValKeyFile, privValStateFile) + pv.Save() + logger.Info("Generated private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } + + nodeKeyFile := config.NodeKeyFile() + if common.FileExists(nodeKeyFile) { + logger.Info("Found node key", "path", nodeKeyFile) + } else { + p2pKey := p2p.NodeKey{ + PrivKey: key, + } + if err := p2pKey.SaveAs(nodeKeyFile); err != nil { + return fmt.Errorf("creating node key %v", err) + } + logger.Info("Generated node key", "path", nodeKeyFile) + } + + genFile := config.GenesisFile() + if common.FileExists(genFile) { + logger.Info("Found genesis file", "path", genFile) + } else { + + genDoc, err := genesis.Read(environment) + if err != nil { + return fmt.Errorf("error reading genesis: %v", err) + } + + if err := genDoc.SaveAs(genFile); err != nil { + return fmt.Errorf("saving gen file %v", err) + } + logger.Info("Generated genesis file", "path", genFile) + } + return nil +} diff --git a/core/config/config.go b/core/config/config.go new file mode 100644 index 00000000000..5918fb0dd78 --- /dev/null +++ b/core/config/config.go @@ -0,0 +1,79 @@ +package config + +import ( + "flag" + "fmt" + "os" + + "github.com/AudiusProject/audius-protocol/core/common" + "github.com/joho/godotenv" +) + +type NodeType = int + +const ( + Discovery NodeType = iota + Content + Identity +) + +type Config struct { + /* Comet Config */ + HomeDir string + RPCladdr string + P2PLaddr string + PSQLConn string + RetainBlocks uint + PersistentPeers string + + /* Audius Config */ + Environment string + DelegatePrivateKey string + + /* System Config */ + RunDownMigration bool +} + +func ReadConfig(logger *common.Logger) (*Config, error) { + // read in dotenv if passed in via flag + envFile := flag.String("env-file", "", ".env file to read for config") + + flag.Parse() + + // load dotenv file if passed in + if *envFile != "" { + logger.Infof("reading env from file %s", *envFile) + if err := godotenv.Load(*envFile); err != nil { + return nil, fmt.Errorf("dot env provided but couldn't load %v", err) + } + } + + var cfg Config + // comet config + cfg.HomeDir = os.Getenv("homeDir") + cfg.RPCladdr = os.Getenv("rpcLaddr") + cfg.P2PLaddr = os.Getenv("p2pLaddr") + cfg.PersistentPeers = os.Getenv("persistentPeers") + + // check if discovery specific key is set + isDiscovery := os.Getenv("audius_delegate_private_key") != "" + if isDiscovery { + cfg.Environment = os.Getenv("audius_discprov_env") + cfg.DelegatePrivateKey = os.Getenv("audius_delegate_private_key") + cfg.PSQLConn = os.Getenv("audius_db_url") + } else { + // isContent + cfg.Environment = os.Getenv("MEDIORUM_ENV") + cfg.DelegatePrivateKey = os.Getenv("delegatePrivateKey") + cfg.PSQLConn = os.Getenv("dbUrl") + } + + // only allow down migration in dev env + cfg.RunDownMigration = os.Getenv("runDownMigration") == "true" && cfg.Environment == "dev" + + if err := InitComet(logger, cfg.Environment, cfg.DelegatePrivateKey, cfg.HomeDir); err != nil { + return nil, fmt.Errorf("initializing comet %v", err) + } + + return &cfg, nil +} diff --git a/core/config/genesis/dev.json b/core/config/genesis/dev.json new file mode 100644 index 00000000000..3d4ea065cd9 --- /dev/null +++ b/core/config/genesis/dev.json @@ -0,0 +1,71 @@ +{ + "genesis_time": "2024-07-25T00:50:59.2083Z", + "chain_id": "audius-devnet", + "initial_height": "0", + "consensus_params": { + "block": { + "max_bytes": "104857600", + "max_gas": "10000000" + }, + "evidence": { + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000", + "max_bytes": "104857600" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + }, + "version": { + "app": "0" + }, + "synchrony": { + "precision": "500000000", + "message_delay": "2000000000" + }, + "feature": { + "vote_extensions_enable_height": "0", + "pbts_enable_height": "0" + } + }, + "validators": [ + { + "address": "FFAD25668E060A357BBE534C8B7E5B4E1274368B", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "6wdF9AgylXFnTfTdlEp98dzlgPNIooHOmfUT4qaWpmM=" + }, + "power": "25", + "name": "discovery-1" + }, + { + "address": "807D94EC3E918C7766B92125FCED72DF8FCB5902", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "YiY0RL2PAfpJu6QqKzLrvlgZzFDimSSuprUPOJ8XjvE=" + }, + "power": "25", + "name": "content-1" + }, + { + "address": "6CF596D1EEA8B960683978FDC73CC076B8A266EF", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "d+5yfKP0GO9dwgZ2owEb9my8Gs2vJbhA315J6o6nRhg=" + }, + "power": "25", + "name": "content-2" + }, + { + "address": "00222113DF360274CF28F7B165A7284C5FB32CDE", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "CYn9pT7PfZ1RPnLSQTaufmSqNNbvKFfwXXHbtY4zLeg=" + }, + "power": "25", + "name": "content-3" + } + ], + "app_hash": "" +} diff --git a/core/config/genesis/genesis.go b/core/config/genesis/genesis.go new file mode 100644 index 00000000000..83b5001d6e9 --- /dev/null +++ b/core/config/genesis/genesis.go @@ -0,0 +1,52 @@ +package genesis + +import ( + "embed" + "fmt" + + "github.com/cometbft/cometbft/types" +) + +//go:embed dev.json +var devGenesis embed.FS + +//go:embed stage.json +var stageGenesis embed.FS + +//go:embed prod.json +var prodGenesis embed.FS + +func Read(environment string) (*types.GenesisDoc, error) { + switch environment { + case "prod", "production", "mainnet": + data, err := prodGenesis.ReadFile("prod.json") + if err != nil { + return nil, fmt.Errorf("failed to read embedded file: %v", err) + } + genDoc, err := types.GenesisDocFromJSON(data) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal prod.json into genesis: %v", err) + } + return genDoc, nil + case "stage", "staging", "testnet": + data, err := stageGenesis.ReadFile("stage.json") + if err != nil { + return nil, fmt.Errorf("failed to read embedded file: %v", err) + } + genDoc, err := types.GenesisDocFromJSON(data) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal stage.json into genesis: %v", err) + } + return genDoc, nil + default: + data, err := devGenesis.ReadFile("dev.json") + if err != nil { + return nil, fmt.Errorf("failed to read embedded file: %v", err) + } + genDoc, err := types.GenesisDocFromJSON(data) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal dev.json into genesis: %v", err) + } + return genDoc, nil + } +} diff --git a/core/config/genesis/prod.json b/core/config/genesis/prod.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/core/config/genesis/prod.json @@ -0,0 +1 @@ +{} diff --git a/core/config/genesis/stage.json b/core/config/genesis/stage.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/core/config/genesis/stage.json @@ -0,0 +1 @@ +{} diff --git a/core/db/db.go b/core/db/db.go new file mode 100644 index 00000000000..5b8c8f5307a --- /dev/null +++ b/core/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/core/db/migrate.go b/core/db/migrate.go new file mode 100644 index 00000000000..65db7c67291 --- /dev/null +++ b/core/db/migrate.go @@ -0,0 +1,68 @@ +package db + +import ( + "database/sql" + "errors" + "fmt" + "time" + + "embed" + + "github.com/AudiusProject/audius-protocol/core/common" + migrate "github.com/rubenv/sql-migrate" +) + +//go:embed sql/migrations/* +var migrationsFS embed.FS + +func RunMigrations(logger *common.Logger, pgConnectionString string, downFirst bool) error { + tries := 10 + db, err := sql.Open("postgres", pgConnectionString) + if err != nil { + return fmt.Errorf("error opening sql db %v", err) + } + defer db.Close() + for { + if tries < 0 { + return errors.New("ran out of retries for migrations") + } + err = db.Ping() + if err != nil { + logger.Errorf("could not ping postgres %v", err) + tries = tries - 1 + time.Sleep(2 * time.Second) + continue + } + err := runMigrations(logger, db, downFirst) + if err != nil { + logger.Error("issue running migrations", "error", err, "tries_left", tries) + return fmt.Errorf("can't run migrations %v", err) + } + return nil + } +} + +func runMigrations(logger *common.Logger, db *sql.DB, downFirst bool) error { + migrations := migrate.EmbedFileSystemMigrationSource{ + FileSystem: migrationsFS, + Root: "sql/migrations", + } + + migrate.SetTable("core_db_migrations") + + if downFirst { + _, err := migrate.Exec(db, "postgres", migrations, migrate.Down) + if err != nil { + return fmt.Errorf("error running down migrations %v", err) + } + } + + n, err := migrate.Exec(db, "postgres", migrations, migrate.Up) + if err != nil { + return fmt.Errorf("error running migrations %v", err) + } + + logger.Infof("Applied %d successful migrations!", n) + + return nil +} diff --git a/core/db/models.go b/core/db/models.go new file mode 100644 index 00000000000..7fc17a9fc61 --- /dev/null +++ b/core/db/models.go @@ -0,0 +1,78 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package db + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type BlockEvent struct { + BlockID int64 + Height int64 + ChainID string + Type string + Key pgtype.Text + CompositeKey pgtype.Text + Value pgtype.Text +} + +type CoreAttribute struct { + EventID int64 + Key string + CompositeKey string + Value pgtype.Text +} + +type CoreBlock struct { + Rowid int64 + Height int64 + ChainID string + CreatedAt pgtype.Timestamptz +} + +type CoreEvent struct { + Rowid int64 + BlockID int64 + TxID pgtype.Int8 + Type string +} + +type CoreKvstore struct { + ID int32 + Key string + Value string + TxHash string + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} + +type CoreTxResult struct { + Rowid int64 + BlockID int64 + Index int32 + CreatedAt pgtype.Timestamptz + TxHash string + TxResult []byte +} + +type EventAttribute struct { + BlockID int64 + TxID pgtype.Int8 + Type string + Key pgtype.Text + CompositeKey pgtype.Text + Value pgtype.Text +} + +type TxEvent struct { + Height int64 + Index int32 + ChainID string + Type string + Key pgtype.Text + CompositeKey pgtype.Text + Value pgtype.Text + CreatedAt pgtype.Timestamptz +} diff --git a/core/db/reads.sql.go b/core/db/reads.sql.go new file mode 100644 index 00000000000..30f779cf705 --- /dev/null +++ b/core/db/reads.sql.go @@ -0,0 +1,28 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: reads.sql + +package db + +import ( + "context" +) + +const getKey = `-- name: GetKey :one +select id, key, value, tx_hash, created_at, updated_at from core_kvstore where key = $1 +` + +func (q *Queries) GetKey(ctx context.Context, key string) (CoreKvstore, error) { + row := q.db.QueryRow(ctx, getKey, key) + var i CoreKvstore + err := row.Scan( + &i.ID, + &i.Key, + &i.Value, + &i.TxHash, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/core/db/sql/migrations/00001_comet_indexing.sql b/core/db/sql/migrations/00001_comet_indexing.sql new file mode 100644 index 00000000000..c37eb07684f --- /dev/null +++ b/core/db/sql/migrations/00001_comet_indexing.sql @@ -0,0 +1,99 @@ +/* + This file defines the database schema for the PostgresQL ("psql") event sink + implementation in CometBFT. The operator must create a database and install + this schema before using the database to index events. + + https://github.com/cometbft/cometbft/blob/main/state/indexer/sink/psql/schema.sql + */ + +-- +migrate Up + +-- The core_blocks table records metadata about each block. +-- The block record does not include its events or transactions (see core_tx_results). +CREATE TABLE core_blocks ( + rowid BIGSERIAL PRIMARY KEY, + + height BIGINT NOT NULL, + chain_id VARCHAR NOT NULL, + + -- When this block header was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + + UNIQUE (height, chain_id) +); + +-- Index core_blocks by height and chain, since we need to resolve block IDs when +-- indexing transaction records and transaction events. +CREATE INDEX idx_core_blocks_height_chain ON core_blocks(height, chain_id); + +-- The core_tx_results table records metadata about transaction results. Note that +-- the events from a transaction are stored separately. +CREATE TABLE core_tx_results ( + rowid BIGSERIAL PRIMARY KEY, + + -- The block to which this transaction belongs. + block_id BIGINT NOT NULL REFERENCES core_blocks(rowid), + -- The sequential index of the transaction within the block. + index INTEGER NOT NULL, + -- When this result record was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + -- The hex-encoded hash of the transaction. + tx_hash VARCHAR NOT NULL, + -- The protobuf wire encoding of the TxResult message. + tx_result BYTEA NOT NULL, + + UNIQUE (block_id, index) +); + +-- The core_events table records events. All events (both block and transaction) are +-- associated with a block ID; transaction events also have a transaction ID. +CREATE TABLE core_events ( + rowid BIGSERIAL PRIMARY KEY, + + -- The block and transaction this event belongs to. + -- If tx_id is NULL, this is a block event. + block_id BIGINT NOT NULL REFERENCES core_blocks(rowid), + tx_id BIGINT NULL REFERENCES core_tx_results(rowid), + + -- The application-defined type label for the event. + type VARCHAR NOT NULL +); + +-- The core_attributes table records event attributes. +CREATE TABLE core_attributes ( + event_id BIGINT NOT NULL REFERENCES core_events(rowid), + key VARCHAR NOT NULL, -- bare key + composite_key VARCHAR NOT NULL, -- composed type.key + value VARCHAR NULL, + + UNIQUE (event_id, key) +); + +-- A joined view of core_events and their core_attributes. Events that do not have any +-- core_attributes are represented as a single row with empty key and value fields. +CREATE VIEW event_attributes AS + SELECT block_id, tx_id, type, key, composite_key, value + FROM core_events LEFT JOIN core_attributes ON (core_events.rowid = core_attributes.event_id); + +-- A joined view of all block events (those having tx_id NULL). +CREATE VIEW block_events AS + SELECT core_blocks.rowid as block_id, height, chain_id, type, key, composite_key, value + FROM core_blocks JOIN event_attributes ON (core_blocks.rowid = event_attributes.block_id) + WHERE event_attributes.tx_id IS NULL; + +-- A joined view of all transaction events. +CREATE VIEW tx_events AS + SELECT height, index, chain_id, type, key, composite_key, value, core_tx_results.created_at + FROM core_blocks JOIN core_tx_results ON (core_blocks.rowid = core_tx_results.block_id) + JOIN event_attributes ON (core_tx_results.rowid = event_attributes.tx_id) + WHERE event_attributes.tx_id IS NOT NULL; + +-- +migrate Down +drop index if exists idx_core_blocks_height_chain; +drop view if exists block_events; +drop view if exists tx_events; +drop view if exists event_attributes; +drop table if exists core_attributes; +drop table if exists core_events; +drop table if exists core_tx_results; +drop table if exists core_blocks; diff --git a/core/db/sql/migrations/00002_comet_kvstore.sql b/core/db/sql/migrations/00002_comet_kvstore.sql new file mode 100644 index 00000000000..8557461f8f7 --- /dev/null +++ b/core/db/sql/migrations/00002_comet_kvstore.sql @@ -0,0 +1,17 @@ +-- +migrate Up +create table core_kvstore( + id serial primary key, + key varchar(255) unique not null, + value text not null, + tx_hash text not null, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +create index idx_core_kvstore_key on core_kvstore(key); +create index idx_core_kvstore_tx_hash on core_kvstore(tx_hash); + +-- +migrate Down +drop index if exists idx_core_kvstore_key; +drop index if exists idx_core_kvstore_tx_hash; +drop table if exists core_kvstore; diff --git a/core/db/sql/reads.sql b/core/db/sql/reads.sql new file mode 100644 index 00000000000..0c65d2fb2c9 --- /dev/null +++ b/core/db/sql/reads.sql @@ -0,0 +1,2 @@ +-- name: GetKey :one +select * from core_kvstore where key = $1; diff --git a/core/db/sql/writes.sql b/core/db/sql/writes.sql new file mode 100644 index 00000000000..82f085ff5f6 --- /dev/null +++ b/core/db/sql/writes.sql @@ -0,0 +1,9 @@ +-- name: InsertKVStore :one +insert into core_kvstore (key, value, tx_hash) +values ($1, $2, $3) +on conflict (key) +do update set + value = excluded.value, + tx_hash = excluded.tx_hash, + updated_at = now() +returning id, key, value, tx_hash, created_at, updated_at; diff --git a/core/db/sqlc.yaml b/core/db/sqlc.yaml new file mode 100644 index 00000000000..ca1f5ac4d47 --- /dev/null +++ b/core/db/sqlc.yaml @@ -0,0 +1,19 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "sql/reads.sql" + schema: "sql/migrations" + gen: + go: + package: "db" + out: "./" + sql_package: "pgx/v5" + + - engine: "postgresql" + queries: "sql/writes.sql" + schema: "sql/migrations" + gen: + go: + package: "db" + out: "./" + sql_package: "pgx/v5" diff --git a/core/db/writes.sql.go b/core/db/writes.sql.go new file mode 100644 index 00000000000..f1eb4f42355 --- /dev/null +++ b/core/db/writes.sql.go @@ -0,0 +1,41 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: writes.sql + +package db + +import ( + "context" +) + +const insertKVStore = `-- name: InsertKVStore :one +insert into core_kvstore (key, value, tx_hash) +values ($1, $2, $3) +on conflict (key) +do update set + value = excluded.value, + tx_hash = excluded.tx_hash, + updated_at = now() +returning id, key, value, tx_hash, created_at, updated_at +` + +type InsertKVStoreParams struct { + Key string + Value string + TxHash string +} + +func (q *Queries) InsertKVStore(ctx context.Context, arg InsertKVStoreParams) (CoreKvstore, error) { + row := q.db.QueryRow(ctx, insertKVStore, arg.Key, arg.Value, arg.TxHash) + var i CoreKvstore + err := row.Scan( + &i.ID, + &i.Key, + &i.Value, + &i.TxHash, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/core/gen/proto/protocol.pb.go b/core/gen/proto/protocol.pb.go new file mode 100644 index 00000000000..ad32cd77916 --- /dev/null +++ b/core/gen/proto/protocol.pb.go @@ -0,0 +1,711 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: protocol.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// generic signature structure +// TODO: add protection for replay attacks +type Signature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // sha256 of signature data + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + // ecdsa signature of related data + SignedData string `protobuf:"bytes,2,opt,name=signed_data,json=signedData,proto3" json:"signed_data,omitempty"` +} + +func (x *Signature) Reset() { + *x = Signature{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Signature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Signature) ProtoMessage() {} + +func (x *Signature) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Signature.ProtoReflect.Descriptor instead. +func (*Signature) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{0} +} + +func (x *Signature) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Signature) GetSignedData() string { + if x != nil { + return x.SignedData + } + return "" +} + +// what gets persisted to chain, canon and indexable +type SignedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // sent in initial request + SenderSignature *Signature `protobuf:"bytes,1,opt,name=sender_signature,json=senderSignature,proto3" json:"sender_signature,omitempty"` + // created before checktx step by node that responded to request + ResponderSignature *Signature `protobuf:"bytes,2,opt,name=responder_signature,json=responderSignature,proto3" json:"responder_signature,omitempty"` + // created at proposal step by node responsible for the new block + ProposerSignature *Signature `protobuf:"bytes,3,opt,name=proposer_signature,json=proposerSignature,proto3" json:"proposer_signature,omitempty"` + Event *Event `protobuf:"bytes,4,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *SignedEvent) Reset() { + *x = SignedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SignedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedEvent) ProtoMessage() {} + +func (x *SignedEvent) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedEvent.ProtoReflect.Descriptor instead. +func (*SignedEvent) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{1} +} + +func (x *SignedEvent) GetSenderSignature() *Signature { + if x != nil { + return x.SenderSignature + } + return nil +} + +func (x *SignedEvent) GetResponderSignature() *Signature { + if x != nil { + return x.ResponderSignature + } + return nil +} + +func (x *SignedEvent) GetProposerSignature() *Signature { + if x != nil { + return x.ProposerSignature + } + return nil +} + +func (x *SignedEvent) GetEvent() *Event { + if x != nil { + return x.Event + } + return nil +} + +// what the user sends, outside world +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // eth address + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + // prod, stage, dev, custom + Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` + Timestamp uint32 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // for replay attacks, random client generated + EventId string `protobuf:"bytes,4,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + // for knowing if the protocol can process tx + ProtocolVersion string `protobuf:"bytes,5,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + // jump up body slot in case more event metadata is added + // + // Types that are assignable to Body: + // + // *Event_Plays + Body isEvent_Body `protobuf_oneof:"body"` +} + +func (x *Event) Reset() { + *x = Event{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{2} +} + +func (x *Event) GetSender() string { + if x != nil { + return x.Sender + } + return "" +} + +func (x *Event) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Event) GetTimestamp() uint32 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Event) GetEventId() string { + if x != nil { + return x.EventId + } + return "" +} + +func (x *Event) GetProtocolVersion() string { + if x != nil { + return x.ProtocolVersion + } + return "" +} + +func (m *Event) GetBody() isEvent_Body { + if m != nil { + return m.Body + } + return nil +} + +func (x *Event) GetPlays() *PlaysEvent { + if x, ok := x.GetBody().(*Event_Plays); ok { + return x.Plays + } + return nil +} + +type isEvent_Body interface { + isEvent_Body() +} + +type Event_Plays struct { + Plays *PlaysEvent `protobuf:"bytes,1000,opt,name=plays,proto3,oneof"` +} + +func (*Event_Plays) isEvent_Body() {} + +type Play struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ListenerAddress string `protobuf:"bytes,1,opt,name=listenerAddress,proto3" json:"listenerAddress,omitempty"` + Count int32 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *Play) Reset() { + *x = Play{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Play) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Play) ProtoMessage() {} + +func (x *Play) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Play.ProtoReflect.Descriptor instead. +func (*Play) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{3} +} + +func (x *Play) GetListenerAddress() string { + if x != nil { + return x.ListenerAddress + } + return "" +} + +func (x *Play) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +type PlaysEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plays map[string]*Play `protobuf:"bytes,1,rep,name=plays,proto3" json:"plays,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *PlaysEvent) Reset() { + *x = PlaysEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PlaysEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlaysEvent) ProtoMessage() {} + +func (x *PlaysEvent) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlaysEvent.ProtoReflect.Descriptor instead. +func (*PlaysEvent) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{4} +} + +func (x *PlaysEvent) GetPlays() map[string]*Play { + if x != nil { + return x.Plays + } + return nil +} + +type EventRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Signature *Signature `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Event *Event `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *EventRequest) Reset() { + *x = EventRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventRequest) ProtoMessage() {} + +func (x *EventRequest) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventRequest.ProtoReflect.Descriptor instead. +func (*EventRequest) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{5} +} + +func (x *EventRequest) GetSignature() *Signature { + if x != nil { + return x.Signature + } + return nil +} + +func (x *EventRequest) GetEvent() *Event { + if x != nil { + return x.Event + } + return nil +} + +type EventResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Txhash string `protobuf:"bytes,1,opt,name=txhash,proto3" json:"txhash,omitempty"` + Event *SignedEvent `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *EventResponse) Reset() { + *x = EventResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protocol_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventResponse) ProtoMessage() {} + +func (x *EventResponse) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventResponse.ProtoReflect.Descriptor instead. +func (*EventResponse) Descriptor() ([]byte, []int) { + return file_protocol_proto_rawDescGZIP(), []int{6} +} + +func (x *EventResponse) GetTxhash() string { + if x != nil { + return x.Txhash + } + return "" +} + +func (x *EventResponse) GetEvent() *SignedEvent { + if x != nil { + return x.Event + } + return nil +} + +var File_protocol_proto protoreflect.FileDescriptor + +var file_protocol_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x40, 0x0a, 0x09, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0xfe, 0x01, 0x0a, + 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x10, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0f, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x44, 0x0a, 0x13, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x12, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x42, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x52, 0x11, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xd4, 0x01, + 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, + 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, + 0x05, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x79, 0x73, 0x42, 0x06, 0x0a, 0x04, + 0x62, 0x6f, 0x64, 0x79, 0x22, 0x46, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x79, 0x12, 0x28, 0x0a, 0x0f, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8d, 0x01, 0x0a, + 0x0a, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x05, 0x70, + 0x6c, 0x61, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x70, 0x6c, 0x61, + 0x79, 0x73, 0x1a, 0x48, 0x0a, 0x0a, 0x50, 0x6c, 0x61, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x50, 0x6c, 0x61, + 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x0c, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x25, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x54, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x68, 0x61, 0x73, 0x68, 0x12, + 0x2b, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x32, 0x4c, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x40, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, + 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_protocol_proto_rawDescOnce sync.Once + file_protocol_proto_rawDescData = file_protocol_proto_rawDesc +) + +func file_protocol_proto_rawDescGZIP() []byte { + file_protocol_proto_rawDescOnce.Do(func() { + file_protocol_proto_rawDescData = protoimpl.X.CompressGZIP(file_protocol_proto_rawDescData) + }) + return file_protocol_proto_rawDescData +} + +var file_protocol_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_protocol_proto_goTypes = []any{ + (*Signature)(nil), // 0: protocol.Signature + (*SignedEvent)(nil), // 1: protocol.SignedEvent + (*Event)(nil), // 2: protocol.Event + (*Play)(nil), // 3: protocol.Play + (*PlaysEvent)(nil), // 4: protocol.PlaysEvent + (*EventRequest)(nil), // 5: protocol.EventRequest + (*EventResponse)(nil), // 6: protocol.EventResponse + nil, // 7: protocol.PlaysEvent.PlaysEntry +} +var file_protocol_proto_depIdxs = []int32{ + 0, // 0: protocol.SignedEvent.sender_signature:type_name -> protocol.Signature + 0, // 1: protocol.SignedEvent.responder_signature:type_name -> protocol.Signature + 0, // 2: protocol.SignedEvent.proposer_signature:type_name -> protocol.Signature + 2, // 3: protocol.SignedEvent.event:type_name -> protocol.Event + 4, // 4: protocol.Event.plays:type_name -> protocol.PlaysEvent + 7, // 5: protocol.PlaysEvent.plays:type_name -> protocol.PlaysEvent.PlaysEntry + 0, // 6: protocol.EventRequest.signature:type_name -> protocol.Signature + 2, // 7: protocol.EventRequest.event:type_name -> protocol.Event + 1, // 8: protocol.EventResponse.event:type_name -> protocol.SignedEvent + 3, // 9: protocol.PlaysEvent.PlaysEntry.value:type_name -> protocol.Play + 5, // 10: protocol.Protocol.SubmitEvent:input_type -> protocol.EventRequest + 6, // 11: protocol.Protocol.SubmitEvent:output_type -> protocol.EventResponse + 11, // [11:12] is the sub-list for method output_type + 10, // [10:11] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_protocol_proto_init() } +func file_protocol_proto_init() { + if File_protocol_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protocol_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Signature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*SignedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*Event); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*Play); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*PlaysEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*EventRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protocol_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*EventResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_protocol_proto_msgTypes[2].OneofWrappers = []any{ + (*Event_Plays)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protocol_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_protocol_proto_goTypes, + DependencyIndexes: file_protocol_proto_depIdxs, + MessageInfos: file_protocol_proto_msgTypes, + }.Build() + File_protocol_proto = out.File + file_protocol_proto_rawDesc = nil + file_protocol_proto_goTypes = nil + file_protocol_proto_depIdxs = nil +} diff --git a/core/gen/proto/protocol_grpc.pb.go b/core/gen/proto/protocol_grpc.pb.go new file mode 100644 index 00000000000..c3b7622cba4 --- /dev/null +++ b/core/gen/proto/protocol_grpc.pb.go @@ -0,0 +1,110 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.27.1 +// source: protocol.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + Protocol_SubmitEvent_FullMethodName = "/protocol.Protocol/SubmitEvent" +) + +// ProtocolClient is the client API for Protocol service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProtocolClient interface { + SubmitEvent(ctx context.Context, in *EventRequest, opts ...grpc.CallOption) (*EventResponse, error) +} + +type protocolClient struct { + cc grpc.ClientConnInterface +} + +func NewProtocolClient(cc grpc.ClientConnInterface) ProtocolClient { + return &protocolClient{cc} +} + +func (c *protocolClient) SubmitEvent(ctx context.Context, in *EventRequest, opts ...grpc.CallOption) (*EventResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EventResponse) + err := c.cc.Invoke(ctx, Protocol_SubmitEvent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProtocolServer is the server API for Protocol service. +// All implementations must embed UnimplementedProtocolServer +// for forward compatibility +type ProtocolServer interface { + SubmitEvent(context.Context, *EventRequest) (*EventResponse, error) + mustEmbedUnimplementedProtocolServer() +} + +// UnimplementedProtocolServer must be embedded to have forward compatible implementations. +type UnimplementedProtocolServer struct { +} + +func (UnimplementedProtocolServer) SubmitEvent(context.Context, *EventRequest) (*EventResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitEvent not implemented") +} +func (UnimplementedProtocolServer) mustEmbedUnimplementedProtocolServer() {} + +// UnsafeProtocolServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProtocolServer will +// result in compilation errors. +type UnsafeProtocolServer interface { + mustEmbedUnimplementedProtocolServer() +} + +func RegisterProtocolServer(s grpc.ServiceRegistrar, srv ProtocolServer) { + s.RegisterService(&Protocol_ServiceDesc, srv) +} + +func _Protocol_SubmitEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProtocolServer).SubmitEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Protocol_SubmitEvent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProtocolServer).SubmitEvent(ctx, req.(*EventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Protocol_ServiceDesc is the grpc.ServiceDesc for Protocol service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Protocol_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "protocol.Protocol", + HandlerType: (*ProtocolServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SubmitEvent", + Handler: _Protocol_SubmitEvent_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protocol.proto", +} diff --git a/core/go.mod b/core/go.mod new file mode 100644 index 00000000000..c837d0af069 --- /dev/null +++ b/core/go.mod @@ -0,0 +1,106 @@ +module github.com/AudiusProject/audius-protocol/core + +go 1.22.5 + +replace github.com/cometbft/cometbft => github.com/alecsavvy/cometbft v1.0.2 + +require ( + github.com/jackc/pgx/v5 v5.6.0 + github.com/onsi/ginkgo/v2 v2.19.0 + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 +) + +require ( + github.com/DataDog/zstd v1.4.5 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.1 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.12.0 // indirect + github.com/cometbft/cometbft/api v1.0.0-rc.1 // indirect + github.com/cosmos/crypto v0.1.2 // indirect + github.com/cosmos/gogoproto v1.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/flatbuffers v2.0.8+incompatible // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/linxGnu/grocksdb v1.8.14 // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae // indirect + github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/cors v1.11.0 // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/supranational/blst v0.3.12 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/cometbft/cometbft v1.0.0-rc1 + github.com/cometbft/cometbft-load-test v0.1.0 + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/joho/godotenv v1.5.1 + github.com/onsi/gomega v1.33.1 + github.com/rubenv/sql-migrate v1.7.0 + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect +) diff --git a/core/go.sum b/core/go.sum new file mode 100644 index 00000000000..7211e09b097 --- /dev/null +++ b/core/go.sum @@ -0,0 +1,387 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= +github.com/alecsavvy/cometbft v1.0.2 h1:8yQzVEzba66NG3STormRLjW9vIkHVUC7EugRKq/ttwg= +github.com/alecsavvy/cometbft v1.0.2/go.mod h1:64cB2wvltmK5plHlJFLYOZYGsaTKNW2EZgcHBisHP7o= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= +github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= +github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/cometbft/cometbft-db v0.12.0 h1:v77/z0VyfSU7k682IzZeZPFZrQAKiQwkqGN0QzAjMi0= +github.com/cometbft/cometbft-db v0.12.0/go.mod h1:aX2NbCrjNVd2ZajYxt1BsiFf/Z+TQ2MN0VxdicheYuw= +github.com/cometbft/cometbft-load-test v0.1.0 h1:Axw5oVXlQqZLVUHVxmULSfEtpEuaUMmoeM560hvBAmw= +github.com/cometbft/cometbft-load-test v0.1.0/go.mod h1:QEXKZ2L5SH1gEw6DRSKYpd3nDCWrzIejaHxwYdgKtkc= +github.com/cometbft/cometbft/api v1.0.0-rc.1 h1:GtdXwDGlqwHYs16A4egjwylfYOMYyEacLBrs3Zvpt7g= +github.com/cometbft/cometbft/api v1.0.0-rc.1/go.mod h1:NDFKiBBD8HJC6QQLAoUI99YhsiRZtg2+FJWfk6A6m6o= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/cosmos/crypto v0.1.2 h1:Yn500sPY+9sKVdhiPUSDtt8JOpBGMB515dOmla4zfls= +github.com/cosmos/crypto v0.1.2/go.mod h1:b6VWz3HczIpBaQPvI7KrbQeF3pXHh0al3T5e0uwMBQw= +github.com/cosmos/gogoproto v1.5.0 h1:SDVwzEqZDDBoslaeZg+dGE55hdzHfgUA40pEanMh52o= +github.com/cosmos/gogoproto v1.5.0/go.mod h1:iUM31aofn3ymidYG6bUR5ZFrk+Om8p5s754eMUcyp8I= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= +github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae h1:FatpGJD2jmJfhZiFDElaC0QhZUDQnxUeAwTGkfAHN3I= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= +github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.12 h1:Vfas2U2CFHhniv2QkUm2OVa1+pGTdqtpqm9NnhUUbZ8= +github.com/supranational/blst v0.3.12/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo= +go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/core/infra/Dockerfile b/core/infra/Dockerfile new file mode 100644 index 00000000000..2d9bfb6d652 --- /dev/null +++ b/core/infra/Dockerfile @@ -0,0 +1,25 @@ +ARG GO_VERSION=1.22.5 +FROM golang:${GO_VERSION} AS build +WORKDIR /src + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,target=. \ + CGO_ENABLED=0 go build -o /bin/server . + +FROM alpine:latest AS final + +RUN --mount=type=cache,target=/var/cache/apk \ + apk --update add \ + ca-certificates \ + tzdata \ + && \ + update-ca-certificates + +COPY --from=build /bin/server /bin/ + +ENTRYPOINT [ "/bin/server" ] diff --git a/core/infra/dev_config/content-one.docker.env b/core/infra/dev_config/content-one.docker.env new file mode 100644 index 00000000000..9ae4b26bb7f --- /dev/null +++ b/core/infra/dev_config/content-one.docker.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content1Home +delegatePrivateKey=21118f9a6de181061a2abd549511105adb4877cf9026f271092e6813b7cf58ab +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@audius-protocol-db-1:5432/creator_node_1?sslmode=disable +runDownMigration="true" +persistentPeers="ffad25668e060a357bbe534c8b7e5b4e1274368b@core.discoveryprovider1.docker.co:26656" +rpcLaddr="tcp://0.0.0.0:26657" diff --git a/core/infra/dev_config/content-one.env b/core/infra/dev_config/content-one.env new file mode 100644 index 00000000000..897aa20c9d7 --- /dev/null +++ b/core/infra/dev_config/content-one.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content1Home +delegatePrivateKey=21118f9a6de181061a2abd549511105adb4877cf9026f271092e6813b7cf58ab +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@0.0.0.0:5432/creator_node_1?sslmode=disable +runDownMigration="true" +rpcLaddr=tcp://0.0.0.0:6621 +p2pLaddr=tcp://0.0.0.0:6622 diff --git a/core/infra/dev_config/content-three.docker.env b/core/infra/dev_config/content-three.docker.env new file mode 100644 index 00000000000..5c610726927 --- /dev/null +++ b/core/infra/dev_config/content-three.docker.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content3Home +delegatePrivateKey=1aa14c63d481dcc1185a654eb52c9c0749d07ac8f30ef17d45c3c391d9bf68eb +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@audius-protocol-db-1:5432/creator_node_3?sslmode=disable +runDownMigration="true" +persistentPeers="ffad25668e060a357bbe534c8b7e5b4e1274368b@core.discoveryprovider1.docker.co:26656" +rpcLaddr="tcp://0.0.0.0:26657" diff --git a/core/infra/dev_config/content-three.env b/core/infra/dev_config/content-three.env new file mode 100644 index 00000000000..e9e925679b4 --- /dev/null +++ b/core/infra/dev_config/content-three.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content3Home +delegatePrivateKey=1aa14c63d481dcc1185a654eb52c9c0749d07ac8f30ef17d45c3c391d9bf68eb +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@0.0.0.0:5432/creator_node_3?sslmode=disable +runDownMigration="true" +rpcLaddr=tcp://0.0.0.0:6641 +p2pLaddr=tcp://0.0.0.0:6642 diff --git a/core/infra/dev_config/content-two.docker.env b/core/infra/dev_config/content-two.docker.env new file mode 100644 index 00000000000..12726935a9d --- /dev/null +++ b/core/infra/dev_config/content-two.docker.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content2Home +delegatePrivateKey=1166189cdf129cdcb011f2ad0e5be24f967f7b7026d162d7c36073b12020b61c +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@audius-protocol-db-1:5432/creator_node_2?sslmode=disable +runDownMigration="true" +persistentPeers="ffad25668e060a357bbe534c8b7e5b4e1274368b@core.discoveryprovider1.docker.co:26656" +rpcLaddr="tcp://0.0.0.0:26657" diff --git a/core/infra/dev_config/content-two.env b/core/infra/dev_config/content-two.env new file mode 100644 index 00000000000..a70a9a13141 --- /dev/null +++ b/core/infra/dev_config/content-two.env @@ -0,0 +1,7 @@ +homeDir=./tmp/content2Home +delegatePrivateKey=1166189cdf129cdcb011f2ad0e5be24f967f7b7026d162d7c36073b12020b61c +MEDIORUM_ENV=dev +dbUrl=postgres://postgres:postgres@0.0.0.0:5432/creator_node_2?sslmode=disable +runDownMigration="true" +rpcLaddr=tcp://0.0.0.0:6631 +p2pLaddr=tcp://0.0.0.0:6632 diff --git a/core/infra/dev_config/discovery-one.docker.env b/core/infra/dev_config/discovery-one.docker.env new file mode 100644 index 00000000000..b7f5de98d72 --- /dev/null +++ b/core/infra/dev_config/discovery-one.docker.env @@ -0,0 +1,6 @@ +homeDir=./tmp/discoveryOneHome +audius_delegate_private_key=d09ba371c359f10f22ccda12fd26c598c7921bda3220c9942174562bc6a36fe8 +audius_discprov_env=dev +audius_db_url=postgres://postgres:postgres@audius-protocol-db-1:5432/discovery_provider_1?sslmode=disable +runDownMigration="true" +rpcLaddr="tcp://0.0.0.0:26657" diff --git a/core/infra/dev_config/discovery-one.env b/core/infra/dev_config/discovery-one.env new file mode 100644 index 00000000000..f67413606b2 --- /dev/null +++ b/core/infra/dev_config/discovery-one.env @@ -0,0 +1,7 @@ +homeDir=./tmp/discoveryOneHome +audius_delegate_private_key=d09ba371c359f10f22ccda12fd26c598c7921bda3220c9942174562bc6a36fe8 +audius_discprov_env=dev +audius_db_url=postgres://postgres:postgres@0.0.0.0:5432/discovery_provider_1?sslmode=disable +runDownMigration="true" +rpcLaddr="tcp://0.0.0.0:6611" +p2pLaddr="tcp://0.0.0.0:6612" diff --git a/core/infra/docker-compose.yml b/core/infra/docker-compose.yml new file mode 100644 index 00000000000..86a5e7effaa --- /dev/null +++ b/core/infra/docker-compose.yml @@ -0,0 +1,64 @@ +services: + discovery-1: + container_name: core.discoveryprovider1.docker.co + build: + context: ../ + dockerfile: ./infra/Dockerfile + target: final + env_file: + - ./dev_config/discovery-one.docker.env + restart: unless-stopped + ports: + - "6610:26656" # CometBFT P2P Server + - "6611:26657" # CometBFT RPC Server + networks: + - audius-protocol_default + + content-1: + container_name: core.contentnode1.docker.co + build: + context: ../ + dockerfile: ./infra/Dockerfile + target: final + env_file: + - ./dev_config/content-one.docker.env + restart: unless-stopped + ports: + - "6710:26656" # CometBFT P2P Server + - "6711:26657" # CometBFT RPC Server + networks: + - audius-protocol_default + + content-2: + container_name: core.contentnode2.docker.co + build: + context: ../ + dockerfile: ./infra/Dockerfile + target: final + env_file: + - ./dev_config/content-two.docker.env + restart: unless-stopped + ports: + - "6720:26656" # CometBFT P2P Server + - "6721:26657" # CometBFT RPC Server + networks: + - audius-protocol_default + + content-3: + container_name: core.contentnode3.docker.co + build: + context: ../ + dockerfile: ./infra/Dockerfile + target: final + env_file: + - ./dev_config/content-three.docker.env + restart: unless-stopped + ports: + - "6730:26656" # CometBFT P2P Server + - "6731:26657" # CometBFT RPC Server + networks: + - audius-protocol_default + +networks: + audius-protocol_default: + external: true diff --git a/core/main.go b/core/main.go new file mode 100644 index 00000000000..03ee0afc3d4 --- /dev/null +++ b/core/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/AudiusProject/audius-protocol/core/chain" + "github.com/AudiusProject/audius-protocol/core/common" + "github.com/AudiusProject/audius-protocol/core/config" + "github.com/AudiusProject/audius-protocol/core/db" + "github.com/jackc/pgx/v5/pgxpool" +) + +func main() { + logger := common.NewLogger(nil) + + // core config + config, err := config.ReadConfig(logger) + if err != nil { + logger.Errorf("reading in config: %v", err) + return + } + + // db migrations + if err := db.RunMigrations(logger, config.PSQLConn, config.RunDownMigration); err != nil { + logger.Errorf("running migrations: %v", err) + return + } + + pool, err := pgxpool.New(context.Background(), config.PSQLConn) + if err != nil { + logger.Errorf("couldn't create pgx pool: %v", err) + return + } + defer pool.Close() + + node, err := chain.NewNode(logger, config, pool) + if err != nil { + logger.Errorf("node init error: %v", err) + return + } + + node.Start() + defer func() { + node.Stop() + node.Wait() + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c + +} diff --git a/core/test/integration/integration_suite_test.go b/core/test/integration/integration_suite_test.go new file mode 100644 index 00000000000..66dd35e60ff --- /dev/null +++ b/core/test/integration/integration_suite_test.go @@ -0,0 +1,13 @@ +package integration_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Integration Suite") +} diff --git a/core/test/integration/kv_store_test.go b/core/test/integration/kv_store_test.go new file mode 100644 index 00000000000..8fa25e593f0 --- /dev/null +++ b/core/test/integration/kv_store_test.go @@ -0,0 +1,59 @@ +package integration_test + +import ( + "context" + + "github.com/cometbft/cometbft/rpc/client/http" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("KvStore", func() { + It("should be on the same network and peer", func() { + ctx := context.Background() + + discovery1rpc, err := http.New("http://localhost:6611") + Expect(err).To(BeNil()) + + res, err := discovery1rpc.Status(ctx) + Expect(err).To(BeNil()) + Expect(res.NodeInfo.Network).To(Equal("audius-devnet")) + + netRes, err := discovery1rpc.NetInfo(ctx) + Expect(err).To(BeNil()) + Expect(netRes.NPeers).To(Equal(3)) + + content1rpc, err := http.New("http://localhost:6711") + Expect(err).To(BeNil()) + + res, err = content1rpc.Status(ctx) + Expect(err).To(BeNil()) + Expect(res.NodeInfo.Network).To(Equal("audius-devnet")) + + netRes, err = content1rpc.NetInfo(ctx) + Expect(err).To(BeNil()) + Expect(netRes.NPeers).To(Equal(3)) + + content2rpc, err := http.New("http://localhost:6721") + Expect(err).To(BeNil()) + + res, err = content2rpc.Status(ctx) + Expect(err).To(BeNil()) + Expect(res.NodeInfo.Network).To(Equal("audius-devnet")) + + netRes, err = content2rpc.NetInfo(ctx) + Expect(err).To(BeNil()) + Expect(netRes.NPeers).To(Equal(3)) + + content3rpc, err := http.New("http://localhost:6731") + Expect(err).To(BeNil()) + + res, err = content3rpc.Status(ctx) + Expect(err).To(BeNil()) + Expect(res.NodeInfo.Network).To(Equal("audius-devnet")) + + netRes, err = content3rpc.NetInfo(ctx) + Expect(err).To(BeNil()) + Expect(netRes.NPeers).To(Equal(3)) + }) +}) diff --git a/core/test/load/main.go b/core/test/load/main.go new file mode 100644 index 00000000000..03bc8db721c --- /dev/null +++ b/core/test/load/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/cometbft/cometbft-load-test/pkg/loadtest" +) + +// stub, fill out when abci is done +func main() { + if err := loadtest.RegisterClientFactory("audius-loadtest", nil); err != nil { + panic(err) + } + + loadtest.Run(&loadtest.CLIConfig{}) +} diff --git a/mediorum/.version.json b/mediorum/.version.json index 7a7d84042e8..b57d1b38476 100644 --- a/mediorum/.version.json +++ b/mediorum/.version.json @@ -1,4 +1,4 @@ { - "version": "0.6.153", + "version": "0.6.158", "service": "content-node" } diff --git a/mediorum/ddl/ddl.go b/mediorum/ddl/ddl.go index 017e5212d0a..d59f94ab526 100644 --- a/mediorum/ddl/ddl.go +++ b/mediorum/ddl/ddl.go @@ -22,6 +22,9 @@ var dropBlobs string //go:embed drop_audio_analysis_ops.sql var dropAnalysisOpsDDL string +//go:embed drop_uploads_audio_analysis_ops.sql +var dropUploadsAnalysisOpsDDL string + var mediorumMigrationTable = ` create table if not exists mediorum_migrations ( "hash" text primary key, @@ -39,9 +42,15 @@ var deleteQmAudioAnalysisWithBadKey = ` delete from qm_audio_analyses where length(results::json->>'key') > 12; ` +var advanceStuckCrudrCursors = ` +update cursors set last_ulid = '01J3C13JH1X7SHJYZ7PV250WPK' where last_ulid < '01J3C13JH1X7SHJYZ7PV250WPK'; +` + func Migrate(db *sql.DB, myHost string) { mustExec(db, mediorumMigrationTable) + runMigration(db, advanceStuckCrudrCursors) + runMigration(db, delistStatusesDDL) runMigration(db, addDelistReasonsDDL) @@ -56,6 +65,8 @@ func Migrate(db *sql.DB, myHost string) { runMigration(db, qmSyncTable) runMigration(db, deleteQmAudioAnalysisWithBadKey) + + runMigration(db, dropUploadsAnalysisOpsDDL) } func runMigration(db *sql.DB, ddl string) { diff --git a/mediorum/ddl/drop_uploads_audio_analysis_ops.sql b/mediorum/ddl/drop_uploads_audio_analysis_ops.sql new file mode 100644 index 00000000000..a8070ece955 --- /dev/null +++ b/mediorum/ddl/drop_uploads_audio_analysis_ops.sql @@ -0,0 +1,8 @@ +begin; + delete from ops where "table" = 'uploads' and action = 'update' and exists ( + select 1 + from jsonb_array_elements(data) as elem + where elem->>'audio_analysis_status' = 'timeout' + and elem->'selected_preview' = '{"Valid": false, "String": ""}' + ); +commit; diff --git a/mediorum/go.mod b/mediorum/go.mod index 5d03bc17cc3..dec9fcbe86c 100644 --- a/mediorum/go.mod +++ b/mediorum/go.mod @@ -18,7 +18,6 @@ require ( github.com/spf13/cast v1.5.0 github.com/storyicon/sigverify v1.1.0 github.com/stretchr/testify v1.8.4 - github.com/tysonmote/rendezvous v0.0.0-20220626212128-be0258dbbd3d gocloud.dev v0.34.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sync v0.3.0 diff --git a/mediorum/go.sum b/mediorum/go.sum index 080a8318126..1e1950f9810 100644 --- a/mediorum/go.sum +++ b/mediorum/go.sum @@ -387,8 +387,6 @@ github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefld github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/tysonmote/rendezvous v0.0.0-20220626212128-be0258dbbd3d h1:yjJ6daNdq0f0FgxgriM8copaRDtC1hE7XUx5xDZKxUM= -github.com/tysonmote/rendezvous v0.0.0-20220626212128-be0258dbbd3d/go.mod h1:aaQPZ8JRQJ8uu7P5QK46yb5h+4bgN2Xt5qmhxlvprw8= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/mediorum/server/audio_analysis.go b/mediorum/server/audio_analysis.go index e1f99d3cc70..fd96080ca94 100644 --- a/mediorum/server/audio_analysis.go +++ b/mediorum/server/audio_analysis.go @@ -20,6 +20,15 @@ func (ss *MediorumServer) startAudioAnalyzer() { work := make(chan *Upload) numWorkers := 4 + numWorkersOverride := os.Getenv("AUDIO_ANALYSIS_WORKERS") + if numWorkersOverride != "" { + num, err := strconv.ParseInt(numWorkersOverride, 10, 64) + if err != nil { + ss.logger.Warn("failed to parse AUDIO_ANALYSIS_WORKERS", "err", err, "AUDIO_ANALYSIS_WORKERS", numWorkersOverride) + } else { + numWorkers = int(num) + } + } // start workers for i := 0; i < numWorkers; i++ { @@ -29,8 +38,28 @@ func (ss *MediorumServer) startAudioAnalyzer() { time.Sleep(time.Minute) // find old work from backlog + ticker := time.NewTicker(15 * time.Minute) + defer ticker.Stop() + + for { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) + go func() { + defer cancel() + ss.findMissedAudioAnalysisJobs(ctx, work) + }() + + select { + case <-ticker.C: + cancel() + } + + time.Sleep(time.Minute) + } +} + +func (ss *MediorumServer) findMissedAudioAnalysisJobs(ctx context.Context, work chan<- *Upload) { uploads := []*Upload{} - err := ss.crud.DB.Where("template = ? and audio_analysis_status != ?", JobTemplateAudio, JobStatusDone). + err := ss.crud.DB.Where("template = ? and (audio_analysis_status is null or audio_analysis_status != ?)", JobTemplateAudio, JobStatusDone). Order("random()"). Find(&uploads). Error @@ -40,13 +69,27 @@ func (ss *MediorumServer) startAudioAnalyzer() { } for _, upload := range uploads { + select { + case <-ctx.Done(): + // if the context is done, stop processing + return + default: + } + cid, ok := upload.TranscodeResults["320"] if !ok { continue } _, isMine := ss.rendezvousAllHosts(cid) + isMine = isMine || (ss.Config.Env == "prod" && ss.Config.Self.Host == "https://creatornode2.audius.co") if isMine && upload.AudioAnalysisErrorCount < MAX_TRIES { - work <- upload + select { + case work <- upload: + // successfully sent the job to the channel + case <-ctx.Done(): + // if the context is done, stop processing + return + } } } } @@ -241,7 +284,7 @@ func (ss *MediorumServer) analyzeBPM(filename string) (float64, error) { // converts an MP3 file to WAV format using ffmpeg func convertToWav(inputFile, outputFile string) error { - cmd := exec.Command("ffmpeg", "-i", inputFile, "-f", "wav", "-t", "300", outputFile) + cmd := exec.Command("ffmpeg", "-i", inputFile, "-f", "wav", "-t", "30", outputFile) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to convert to WAV: %v, output: %s", err, string(output)) } diff --git a/mediorum/server/legacy_audio_analysis.go b/mediorum/server/legacy_audio_analysis.go index ef94fb40078..a8af4b27234 100644 --- a/mediorum/server/legacy_audio_analysis.go +++ b/mediorum/server/legacy_audio_analysis.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" "time" @@ -13,16 +14,22 @@ import ( "gocloud.dev/gcerrors" ) -const MAX_TRIES = 5 +const MAX_TRIES = 3 // scroll qm cids func (ss *MediorumServer) startLegacyAudioAnalyzer() { - ctx := context.Background() - logger := ss.logger - work := make(chan *QmAudioAnalysis) - numWorkers := 4 + numWorkers := 5 + numWorkersOverride := os.Getenv("LEGACY_AUDIO_ANALYSIS_WORKERS") + if numWorkersOverride != "" { + num, err := strconv.ParseInt(numWorkersOverride, 10, 64) + if err != nil { + ss.logger.Warn("failed to parse LEGACY_AUDIO_ANALYSIS_WORKERS", "err", err, "LEGACY_AUDIO_ANALYSIS_WORKERS", numWorkersOverride) + } else { + numWorkers = int(num) + } + } // start workers for i := 0; i < numWorkers; i++ { @@ -31,16 +38,43 @@ func (ss *MediorumServer) startLegacyAudioAnalyzer() { time.Sleep(time.Minute) - // find work + // find work, requerying every 15 minutes + ticker := time.NewTicker(15 * time.Minute) + defer ticker.Stop() + + for { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) + go func() { + defer cancel() + ss.findLegacyAudioAnalysisJobs(ctx, work) + }() + + select { + case <-ticker.C: + cancel() + } + + time.Sleep(time.Minute) + } +} + +func (ss *MediorumServer) findLegacyAudioAnalysisJobs(ctx context.Context, work chan<- *QmAudioAnalysis) { var cid string rows, _ := ss.pgPool.Query(ctx, "select key from qm_cids where key not like '%.jpg' order by random()") _, err := pgx.ForEachRow(rows, []any{&cid}, func() error { + select { + case <-ctx.Done(): + // if the context is done, stop processing + return ctx.Err() + default: + } if strings.HasSuffix(cid, ".jpg") { return nil } preferredHosts, isMine := ss.rendezvousAllHosts(cid) + isMine = isMine || (ss.Config.Env == "prod" && ss.Config.Self.Host == "https://creatornode2.audius.co") if !isMine { return nil } @@ -57,15 +91,25 @@ func (ss *MediorumServer) startLegacyAudioAnalyzer() { } if analysis.Status != JobStatusDone && analysis.ErrorCount < MAX_TRIES { - work <- analysis + select { + case work <- analysis: + // successfully sent the job to the channel + case <-ctx.Done(): + // if the context is done, stop processing + return ctx.Err() + } } return nil }) + if err != nil { - logger.Error("failed to find qm rows", "err", err) + if err == context.Canceled { + ss.logger.Info("findLegacyAudioAnalysisJobs terminated") + } else { + ss.logger.Error("failed to find qm rows", "err", err) + } } - } func (ss *MediorumServer) startLegacyAudioAnalysisWorker(n int, work chan *QmAudioAnalysis) { diff --git a/mediorum/server/placement.go b/mediorum/server/placement.go index edfdb84e056..c08612e3408 100644 --- a/mediorum/server/placement.go +++ b/mediorum/server/placement.go @@ -7,9 +7,7 @@ import ( "net/url" "sort" "strings" - "sync" - "github.com/tysonmote/rendezvous" "golang.org/x/exp/slices" ) @@ -25,8 +23,6 @@ func (ss *MediorumServer) rendezvousAllHosts(key string) ([]string, bool) { return orderedHosts, isMine } -// ~~~~~~~~~~~~~~~~~~~~~ new type of hash ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - type HostTuple struct { host string score []byte @@ -66,37 +62,15 @@ func NewRendezvousHasher(hosts []string) *RendezvousHasher { liveHosts = append(liveHosts, h) } return &RendezvousHasher{ - hosts: liveHosts, - legacyHasher: rendezvous.New(liveHosts...), + hosts: liveHosts, } } type RendezvousHasher struct { - mu sync.Mutex - hosts []string - legacyHasher *rendezvous.Hash + hosts []string } func (rh *RendezvousHasher) Rank(key string) []string { - // HashMigration: use R=2 (crc32) + R=2 (sha256) - // take first 2 legacy crc32 nodes first - - // after migration complete, just call `rank` - - rh.mu.Lock() - legacy := rh.legacyHasher.GetN(2, key) - rh.mu.Unlock() - result := make([]string, 0, len(rh.hosts)) - result = append(result, legacy...) - for _, h := range rh.rank(key) { - if !slices.Contains(legacy, h) { - result = append(result, h) - } - } - return result -} - -func (rh *RendezvousHasher) rank(key string) []string { tuples := make(HostTuples, len(rh.hosts)) keyBytes := []byte(key) hasher := sha256.New() diff --git a/mediorum/server/placement_test.go b/mediorum/server/placement_test.go index 9fe144a8d63..9a198c19bf1 100644 --- a/mediorum/server/placement_test.go +++ b/mediorum/server/placement_test.go @@ -18,7 +18,7 @@ func TestRendezvous256(t *testing.T) { // sha256 ordering { - ordered := rh.rank(testCid) + ordered := rh.Rank(testCid) expected := []string{ "https://blockdaemon-audius-content-09.bdnodes.net", "https://cn4.mainnet.audiusindex.org", @@ -31,21 +31,4 @@ func TestRendezvous256(t *testing.T) { assert.Equal(t, exp, ordered[idx], fmt.Sprintf("expected rank %d to be %s got %s", idx, exp, ordered[idx])) } } - - // HashMigration ordering - { - ordered := rh.Rank(testCid) - expected := []string{ - "https://audius-content-15.cultur3stake.com", - "https://audius-content-4.figment.io", - - "https://blockdaemon-audius-content-09.bdnodes.net", - "https://cn4.mainnet.audiusindex.org", - "https://cn0.mainnet.audiusindex.org", - "https://creatornode.audius3.prod-eks-ap-northeast-1.staked.cloud", - } - for idx, exp := range expected { - assert.Equal(t, exp, ordered[idx], fmt.Sprintf("expected rank %d to be %s got %s", idx, exp, ordered[idx])) - } - } } diff --git a/mediorum/server/repair.go b/mediorum/server/repair.go index 408f112ebe4..44e9bf090c2 100644 --- a/mediorum/server/repair.go +++ b/mediorum/server/repair.go @@ -54,7 +54,7 @@ func (ss *MediorumServer) startRepairer() { // run the next job tracker.CursorI = lastRun.CursorI + 1 - // 50% percent of time... clean up over-replicated and pull under-replicated + // 50% of time run cleanup mode if tracker.CursorI > 2 { tracker.CursorI = 1 } diff --git a/mediorum/server/replicate.go b/mediorum/server/replicate.go index ff80cf91259..fad05e6396e 100644 --- a/mediorum/server/replicate.go +++ b/mediorum/server/replicate.go @@ -207,7 +207,7 @@ func (ss *MediorumServer) pullFileFromHost(host, cid string) error { return errors.New("should not pull blob from self") } client := http.Client{ - Timeout: time.Minute, + Timeout: time.Minute * 3, } u := apiPath(host, "internal/blobs", url.PathEscape(cid)) diff --git a/mediorum/server/serve_blob.go b/mediorum/server/serve_blob.go index cef5664ee92..daac9c15533 100644 --- a/mediorum/server/serve_blob.go +++ b/mediorum/server/serve_blob.go @@ -79,13 +79,17 @@ func (ss *MediorumServer) serveBlobLocation(c echo.Context) error { return 1 }) - if fix, _ := strconv.ParseBool(c.QueryParam("fix")); fix { - if len(attrs) > 0 { - best := attrs[0] - if err := ss.pullFileFromHost(best.Host, cid); err != nil { - return err + if fix, _ := strconv.ParseBool(c.QueryParam("fix")); fix && len(attrs) > 0 { + best := attrs[0] + for _, a := range attrs { + if a.Attr.Size < best.Attr.Size { + break + } + if err := ss.pullFileFromHost(a.Host, cid); err == nil { + break } } + } } diff --git a/mediorum/server/transcode.go b/mediorum/server/transcode.go index 1f9bd2145b2..669c3d2eedc 100644 --- a/mediorum/server/transcode.go +++ b/mediorum/server/transcode.go @@ -15,6 +15,7 @@ import ( "os" "os/exec" "runtime" + "strconv" "strings" "sync" "time" @@ -41,6 +42,15 @@ func (ss *MediorumServer) startTranscoder() { if numWorkers < 2 { numWorkers = 2 } + numWorkersOverride := os.Getenv("TRANSCODE_WORKERS") + if numWorkersOverride != "" { + num, err := strconv.ParseInt(numWorkersOverride, 10, 64) + if err != nil { + ss.logger.Warn("failed to parse TRANSCODE_WORKERS", "err", err, "TRANSCODE_WORKERS", numWorkersOverride) + } else { + numWorkers = int(num) + } + } // on boot... reset any of my wip jobs for _, statuses := range [][]string{{JobStatusBusy, JobStatusNew}, {JobStatusBusyRetranscode, JobStatusRetranscode}} { diff --git a/monitoring/vector/Dockerfile b/monitoring/vector/Dockerfile index 8f7eb4bc468..f7571422985 100644 --- a/monitoring/vector/Dockerfile +++ b/monitoring/vector/Dockerfile @@ -1,4 +1,4 @@ -FROM timberio/vector:0.38.0-alpine +FROM timberio/vector:0.39.0-alpine ARG audius_axiom_token ENV audius_axiom_token ${audius_axiom_token} diff --git a/monitoring/vector/vector.toml b/monitoring/vector/vector.toml index 426200269c7..291f500ed95 100644 --- a/monitoring/vector/vector.toml +++ b/monitoring/vector/vector.toml @@ -77,7 +77,7 @@ type = "filter" inputs = ["parse"] condition = ''' - contains(["INFO", "WARN", "ERROR"], .level) + match_any(string!(.level), [r'INFO', r'WARN', r'ERROR']) ''' [transforms.throttle] diff --git a/package-lock.json b/package-lock.json index 357d2dce91d..a7f8fc49d44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "root", - "version": "1.5.91", + "version": "1.5.92", "lockfileVersion": 3, "requires": true, "cacheBust": 2, "packages": { "": { "name": "root", - "version": "1.5.91", + "version": "1.5.92", "hasInstallScript": true, "workspaces": [ "packages/*", @@ -140903,7 +140903,7 @@ }, "packages/mobile": { "name": "@audius/mobile", - "version": "1.5.91", + "version": "1.5.92", "dependencies": { "@amplitude/react-native": "2.17.2", "@audius/common": "*", @@ -143992,7 +143992,7 @@ }, "packages/web": { "name": "@audius/web", - "version": "1.5.91", + "version": "1.5.92", "dependencies": { "@audius/common": "*", "@audius/fetch-nft": "0.2.6", diff --git a/package.json b/package.json index b43e11e0d4d..514d2cbbcb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.5.91", + "version": "1.5.92", "workspaces": [ "packages/*", "packages/discovery-provider/plugins/pedalboard/apps/*", diff --git a/packages/common/src/services/audius-api-client/ResponseAdapter.ts b/packages/common/src/services/audius-api-client/ResponseAdapter.ts index d9d1d867006..0a652a5980a 100644 --- a/packages/common/src/services/audius-api-client/ResponseAdapter.ts +++ b/packages/common/src/services/audius-api-client/ResponseAdapter.ts @@ -264,6 +264,7 @@ export const makeTrack = ( : null, stem_of: track.stem_of.parent_track_id === null ? null : track.stem_of, + // TODO: Remove this when api is fixed to return UTC dates release_date: dayjs .utc(track.release_date) .local() @@ -376,6 +377,10 @@ export const makePlaylist = ( is_stream_gated, stream_conditions, access, + // TODO: Remove this when api is fixed to return UTC dates + release_date: playlist.release_date + ? dayjs.utc(playlist.release_date).local() + : undefined, // utc -> local // Fields to prune id: undefined, diff --git a/packages/common/src/services/audius-api-client/types.ts b/packages/common/src/services/audius-api-client/types.ts index 51ed338a9bb..97901bbc175 100644 --- a/packages/common/src/services/audius-api-client/types.ts +++ b/packages/common/src/services/audius-api-client/types.ts @@ -216,6 +216,7 @@ export type APIPlaylist = { cover_art_cids: Nullable is_stream_gated: boolean stream_conditions: Nullable + release_date?: string } export type APISearchPlaylist = Omit< diff --git a/packages/common/src/store/cache/collections/actions.ts b/packages/common/src/store/cache/collections/actions.ts index ae1f40c7939..1eda616197f 100644 --- a/packages/common/src/store/cache/collections/actions.ts +++ b/packages/common/src/store/cache/collections/actions.ts @@ -153,8 +153,17 @@ export function orderPlaylistFailed( return { type: ORDER_PLAYLIST_FAILED, error, params, metadata } } -export function publishPlaylist(playlistId: ID, dismissToastKey?: string) { - return { type: PUBLISH_PLAYLIST, playlistId, dismissToastKey } +export function publishPlaylist( + playlistId: ID, + dismissToastKey?: string, + isAlbum?: boolean +) { + return { + type: PUBLISH_PLAYLIST, + playlistId, + dismissToastKey, + isAlbum + } } export function publishPlaylistFailed( diff --git a/packages/common/src/store/purchase-content/sagas.ts b/packages/common/src/store/purchase-content/sagas.ts index 6d9a473e990..fdd2eba500a 100644 --- a/packages/common/src/store/purchase-content/sagas.ts +++ b/packages/common/src/store/purchase-content/sagas.ts @@ -652,19 +652,20 @@ function* doStartPurchaseContentFlow({ // Poll to confirm purchase (waiting for a signature) yield* pollForPurchaseConfirmation({ contentId, contentType }) + const { metadata } = yield* call(getContentInfo, { + contentId, + contentType + }) // Auto-favorite the purchased item if (contentType === PurchaseableContentType.TRACK) { yield* put(saveTrack(contentId, FavoriteSource.IMPLICIT)) } if (contentType === PurchaseableContentType.ALBUM) { - const { metadata } = yield* call(getContentInfo, { - contentId, - contentType - }) yield* put(saveCollection(contentId, FavoriteSource.IMPLICIT)) if ( 'playlist_contents' in metadata && - metadata.playlist_contents.track_ids + metadata.playlist_contents.track_ids && + !metadata.is_private ) { for (const track of metadata.playlist_contents.track_ids) { yield* put(saveTrack(track.track, FavoriteSource.IMPLICIT)) @@ -675,11 +676,17 @@ function* doStartPurchaseContentFlow({ // Check if playing the purchased track's preview and if so, stop it const isPreviewing = yield* select(getPreviewing) const nowPlayingTrackId = yield* select(getTrackId) - if ( + const isPlayingTrackInAlbum = + contentType === PurchaseableContentType.ALBUM && + 'playlist_contents' in metadata && + metadata.playlist_contents.track_ids.some( + ({ track: trackId }) => trackId === nowPlayingTrackId + ) + const isPlayingPurchasedTrack = contentType === PurchaseableContentType.TRACK && - contentId === nowPlayingTrackId && - isPreviewing - ) { + contentId === nowPlayingTrackId + + if (isPreviewing && (isPlayingTrackInAlbum || isPlayingPurchasedTrack)) { yield* put(stop({})) } diff --git a/packages/common/src/utils/bpm.ts b/packages/common/src/utils/bpm.ts new file mode 100644 index 00000000000..30992b09430 --- /dev/null +++ b/packages/common/src/utils/bpm.ts @@ -0,0 +1,9 @@ +/** + * Check if a bpm is valid + * It should be a string containing number with up to 3 digits before the decimal and + * up to 1 digit after the decimal + */ +export const isBpmValid = (bpm: string): boolean => { + const regex = /^\d{0,3}(\.\d{0,1})?$/ + return regex.test(bpm) +} diff --git a/packages/common/src/utils/formatUtil.ts b/packages/common/src/utils/formatUtil.ts index a457c6da3bc..0072abfdcf6 100644 --- a/packages/common/src/utils/formatUtil.ts +++ b/packages/common/src/utils/formatUtil.ts @@ -281,11 +281,13 @@ export const formatReleaseDate = ({ const releaseDate = dayjs(date) const now = dayjs() + // Can't use daysDifference === 0 because anything under 24 hours counts + const isToday = releaseDate.isSame(now, 'day') const daysDifference = releaseDate.diff(now, 'days') if (daysDifference >= 0 && daysDifference < 7) { return ( - `${releaseDate.format('dddd')}` + + `${isToday ? 'Today' : releaseDate.format('dddd')}` + (withHour ? ` @ ${releaseDate.format('h A')}` : '') ) } else { diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index d0f81ee5f26..9a530f28238 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -43,3 +43,4 @@ export * from './contentTypeUtils' export * from './sdkMigrationUtils' export * from './musicalKeys' export * from './editableAccess' +export * from './bpm' diff --git a/packages/common/src/utils/linking.ts b/packages/common/src/utils/linking.ts index 868b9955559..c2b124b5dee 100644 --- a/packages/common/src/utils/linking.ts +++ b/packages/common/src/utils/linking.ts @@ -5,6 +5,7 @@ export const externalLinkAllowList = new Set([ 'twitter.com', 'x.com', 'blog.audius.co', + 'link.audius.co', 'audius.co', 'discord.gg', 'solscan.io' diff --git a/packages/discovery-provider/.version.json b/packages/discovery-provider/.version.json index 7242d4cd2f6..4f06aaaeac9 100644 --- a/packages/discovery-provider/.version.json +++ b/packages/discovery-provider/.version.json @@ -1,4 +1,4 @@ { - "version": "0.6.153", + "version": "0.6.158", "service": "discovery-node" } diff --git a/packages/discovery-provider/Dockerfile.dev b/packages/discovery-provider/Dockerfile.dev index 7d3cf87cbf7..ff81e6f6b09 100644 --- a/packages/discovery-provider/Dockerfile.dev +++ b/packages/discovery-provider/Dockerfile.dev @@ -37,6 +37,7 @@ RUN apk update && \ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +RUN rustup default 1.79.0 RUN curl -O 'http://openresty.org/package/admin@openresty.com-5ea678a6.rsa.pub' && \ mv 'admin@openresty.com-5ea678a6.rsa.pub' /etc/apk/keys/ && \ diff --git a/packages/discovery-provider/ddl/migrations/0084_add_release_date_to_trending_params.sql b/packages/discovery-provider/ddl/migrations/0084_add_release_date_to_trending_params.sql new file mode 100644 index 00000000000..affca01b88c --- /dev/null +++ b/packages/discovery-provider/ddl/migrations/0084_add_release_date_to_trending_params.sql @@ -0,0 +1,241 @@ +-- Add release_date to trending params +begin; + +CREATE OR REPLACE FUNCTION recreate_trending_params() +RETURNS void AS +$$ +BEGIN + create materialized view public.trending_params as + select + t.track_id, + t.release_date, + t.genre, + t.owner_id, + ap.play_count, + au.follower_count as owner_follower_count, + coalesce(aggregate_track.repost_count, 0) as repost_count, + coalesce(aggregate_track.save_count, 0) as save_count, + coalesce(repost_week.repost_count, (0) :: bigint) as repost_week_count, + coalesce(repost_month.repost_count, (0) :: bigint) as repost_month_count, + coalesce(repost_year.repost_count, (0) :: bigint) as repost_year_count, + coalesce(save_week.repost_count, (0) :: bigint) as save_week_count, + coalesce(save_month.repost_count, (0) :: bigint) as save_month_count, + coalesce(save_year.repost_count, (0) :: bigint) as save_year_count, + coalesce(karma.karma, (0) :: numeric) as karma + from + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + public.tracks t + left join ( + select + ap_1.count as play_count, + ap_1.play_item_id + from + public.aggregate_plays ap_1 + ) ap on ((ap.play_item_id = t.track_id)) + ) + left join ( + select + au_1.user_id, + au_1.follower_count + from + public.aggregate_user au_1 + ) au on ((au.user_id = t.owner_id)) + ) + left join ( + select + aggregate_track_1.track_id, + aggregate_track_1.repost_count, + aggregate_track_1.save_count + from + public.aggregate_track aggregate_track_1 + ) aggregate_track on ((aggregate_track.track_id = t.track_id)) + ) + left join ( + select + r.repost_item_id as track_id, + count(r.repost_item_id) as repost_count + from + public.reposts r + where + ( + (r.is_current is true) + and (r.repost_type = 'track' :: public.reposttype) + and (r.is_delete is false) + and (r.created_at > (now() - '1 year' :: interval)) + ) + group by + r.repost_item_id + ) repost_year on ((repost_year.track_id = t.track_id)) + ) + left join ( + select + r.repost_item_id as track_id, + count(r.repost_item_id) as repost_count + from + public.reposts r + where + ( + (r.is_current is true) + and (r.repost_type = 'track' :: public.reposttype) + and (r.is_delete is false) + and (r.created_at > (now() - '1 mon' :: interval)) + ) + group by + r.repost_item_id + ) repost_month on ((repost_month.track_id = t.track_id)) + ) + left join ( + select + r.repost_item_id as track_id, + count(r.repost_item_id) as repost_count + from + public.reposts r + where + ( + (r.is_current is true) + and (r.repost_type = 'track' :: public.reposttype) + and (r.is_delete is false) + and (r.created_at > (now() - '7 days' :: interval)) + ) + group by + r.repost_item_id + ) repost_week on ((repost_week.track_id = t.track_id)) + ) + left join ( + select + r.save_item_id as track_id, + count(r.save_item_id) as repost_count + from + public.saves r + where + ( + (r.is_current is true) + and (r.save_type = 'track' :: public.savetype) + and (r.is_delete is false) + and (r.created_at > (now() - '1 year' :: interval)) + ) + group by + r.save_item_id + ) save_year on ((save_year.track_id = t.track_id)) + ) + left join ( + select + r.save_item_id as track_id, + count(r.save_item_id) as repost_count + from + public.saves r + where + ( + (r.is_current is true) + and (r.save_type = 'track' :: public.savetype) + and (r.is_delete is false) + and (r.created_at > (now() - '1 mon' :: interval)) + ) + group by + r.save_item_id + ) save_month on ((save_month.track_id = t.track_id)) + ) + left join ( + select + r.save_item_id as track_id, + count(r.save_item_id) as repost_count + from + public.saves r + where + ( + (r.is_current is true) + and (r.save_type = 'track' :: public.savetype) + and (r.is_delete is false) + and (r.created_at > (now() - '7 days' :: interval)) + ) + group by + r.save_item_id + ) save_week on ((save_week.track_id = t.track_id)) + ) + left join ( + select + save_and_reposts.item_id as track_id, + sum(au_1.follower_count) as karma + from + ( + ( + select + r_and_s.user_id, + r_and_s.item_id + from + ( + ( + select + reposts.user_id, + reposts.repost_item_id as item_id + from + public.reposts + where + ( + (reposts.is_delete is false) + and (reposts.is_current is true) + and ( + reposts.repost_type = 'track' :: public.reposttype + ) + ) + union + all + select + saves.user_id, + saves.save_item_id as item_id + from + public.saves + where + ( + (saves.is_delete is false) + and (saves.is_current is true) + and (saves.save_type = 'track' :: public.savetype) + ) + ) r_and_s + join public.users on ((r_and_s.user_id = users.user_id)) + ) + where + ( + ( + (users.cover_photo is not null) + or (users.cover_photo_sizes is not null) + ) + and ( + (users.profile_picture is not null) + or (users.profile_picture_sizes is not null) + ) + and (users.bio is not null) + ) + ) save_and_reposts + join public.aggregate_user au_1 on ((save_and_reposts.user_id = au_1.user_id)) + ) + group by + save_and_reposts.item_id + ) karma on ((karma.track_id = t.track_id)) + ) + where + ( + (t.is_current is true) + and (t.is_delete is false) + and (t.is_unlisted is false) + and (t.stem_of is null) + ) with no data; + + create index trending_params_track_id_idx on public.trending_params using btree (track_id); +END; +$$ +LANGUAGE plpgsql; + +drop materialized view if exists trending_params; +select recreate_trending_params(); + +commit; \ No newline at end of file diff --git a/packages/discovery-provider/ddl/migrations/0085_playlists_release_date.sql b/packages/discovery-provider/ddl/migrations/0085_playlists_release_date.sql new file mode 100644 index 00000000000..253f226c1d3 --- /dev/null +++ b/packages/discovery-provider/ddl/migrations/0085_playlists_release_date.sql @@ -0,0 +1,8 @@ +-- Add release_date to playlists / albums without it set +begin; + +update playlists +set release_date = created_at +where release_date is null; + +commit; \ No newline at end of file diff --git a/packages/discovery-provider/integration_tests/challenges/test_trending_challenge.py b/packages/discovery-provider/integration_tests/challenges/test_trending_challenge.py index f07428bdfc7..190350d1c65 100644 --- a/packages/discovery-provider/integration_tests/challenges/test_trending_challenge.py +++ b/packages/discovery-provider/integration_tests/challenges/test_trending_challenge.py @@ -73,7 +73,7 @@ def test_trending_challenge_should_update(app): rank=1, id="1", type="tracks", - version="EJ57D", + version="pnagD", week="2021-08-20", ) ) @@ -291,8 +291,7 @@ def test_trending_challenge_job(app): strategy = trending_strategy_factory.get_strategy( TrendingType.TRACKS, version ) - if strategy.use_mat_view: - strategy.update_track_score_query(session) + strategy.update_track_score_query(session) session.commit() web3 = MockWeb3() diff --git a/packages/discovery-provider/integration_tests/tasks/test_update_trending.py b/packages/discovery-provider/integration_tests/tasks/test_update_trending.py index f0b5696058b..4ff33ee9fdb 100644 --- a/packages/discovery-provider/integration_tests/tasks/test_update_trending.py +++ b/packages/discovery-provider/integration_tests/tasks/test_update_trending.py @@ -5,8 +5,8 @@ from src.models.social.aggregate_interval_plays import t_aggregate_interval_plays from src.models.tracks.track_trending_score import TrackTrendingScore from src.models.tracks.trending_param import t_trending_params -from src.trending_strategies.EJ57D_trending_tracks_strategy import ( - TrendingTracksStrategyEJ57D, +from src.trending_strategies.pnagD_trending_tracks_strategy import ( + TrendingTracksStrategypnagD, ) from src.utils.db_session import get_db @@ -341,12 +341,12 @@ def test_update_track_score_query(app): # setup setup_trending(db) - udpated_strategy = TrendingTracksStrategyEJ57D() + updated_strategy = TrendingTracksStrategypnagD() with db.scoped_session() as session: session.execute("REFRESH MATERIALIZED VIEW aggregate_interval_plays") session.execute("REFRESH MATERIALIZED VIEW trending_params") - udpated_strategy.update_track_score_query(session) + updated_strategy.update_track_score_query(session) scores = session.query(TrackTrendingScore).all() # Test that scores are not generated for hidden/deleted tracks # There should be 7 valid tracks * 3 valid time ranges (week/month/year) @@ -369,10 +369,10 @@ def get_time_sorted(time_range): # Check that the type and version fields are correct for score in scores: - assert score.type == udpated_strategy.trending_type.name - assert score.version == udpated_strategy.version.name + assert score.type == updated_strategy.trending_type.name + assert score.version == updated_strategy.version.name # Check that the type and version fields are correct for score in scores: - assert score.type == udpated_strategy.trending_type.name - assert score.version == udpated_strategy.version.name + assert score.type == updated_strategy.trending_type.name + assert score.version == updated_strategy.version.name diff --git a/packages/discovery-provider/integration_tests/utils.py b/packages/discovery-provider/integration_tests/utils.py index a90c3aa7630..5044845121b 100644 --- a/packages/discovery-provider/integration_tests/utils.py +++ b/packages/discovery-provider/integration_tests/utils.py @@ -230,7 +230,7 @@ def populate_mock_db(db, entities, block_offset=None): release_date=( str(track_meta.get("release_date")) if track_meta.get("release_date") - else None + else track_meta.get("created_at", track_created_at) ), is_unlisted=track_meta.get("is_unlisted", False), is_scheduled_release=track_meta.get("is_scheduled_release", False), @@ -306,7 +306,9 @@ def populate_mock_db(db, entities, block_offset=None): is_stream_gated=playlist_meta.get("is_stream_gated", False), stream_conditions=playlist_meta.get("stream_conditions", None), is_scheduled_release=playlist_meta.get("is_scheduled_release", False), - release_date=playlist_meta.get("release_date", None), + release_date=playlist_meta.get( + "release_date", playlist_meta.get("created_at", playlist_created_at) + ), ) session.add(playlist) for i, playlist_track_meta in enumerate(playlist_tracks): diff --git a/packages/discovery-provider/plugins/notifications/src/main.ts b/packages/discovery-provider/plugins/notifications/src/main.ts index 6f00e30a4a6..1afe921adb8 100644 --- a/packages/discovery-provider/plugins/notifications/src/main.ts +++ b/packages/discovery-provider/plugins/notifications/src/main.ts @@ -115,12 +115,12 @@ export class Processor { logger.info('processing events') this.isRunning = true while (this.isRunning) { - logger.info('Processing app notifications (new)') + logger.debug('Processing app notifications (new)') await sendAppNotifications(this.listener, this.appNotificationsProcessor) - logger.info('Processing app notifications (needs reprocessing)') + logger.debug('Processing app notifications (needs reprocessing)') await this.appNotificationsProcessor.reprocess() - logger.info('Processing DM notifications') + logger.debug('Processing DM notifications') await sendDMNotifications( this.discoveryDB, this.identityDB, diff --git a/packages/discovery-provider/plugins/pedalboard/apps/backfill-audio-analyses/src/backfill_discovery.ts b/packages/discovery-provider/plugins/pedalboard/apps/backfill-audio-analyses/src/backfill_discovery.ts index 71cf9bd1170..375b5e07147 100644 --- a/packages/discovery-provider/plugins/pedalboard/apps/backfill-audio-analyses/src/backfill_discovery.ts +++ b/packages/discovery-provider/plugins/pedalboard/apps/backfill-audio-analyses/src/backfill_discovery.ts @@ -16,7 +16,7 @@ const semaphore = new Semaphore(MAX_CONCURRENT_REQUESTS) const REQUEST_TIMEOUT = 5000 // 5s -const DB_OFFSET_KEY = 'discovery:backfill_audio_analyses:offset' +const DB_OFFSET_KEY = 'discovery:backfill_audio_analyses:offset3' interface Track { track_id: number @@ -46,17 +46,12 @@ function formatErrorLog( } async function getAudioAnalysis(contentNodes: string[], track: Track) { - let result = null + let formattedResult = null const trackCid = track.track_cid + if (!trackCid) return formattedResult // skip tracks that already have their audio analyses - if ( - !trackCid || - track.musical_key || - track.bpm || - track.audio_analysis_error_count! > 0 - ) - return result + if (track.musical_key && track.bpm) return formattedResult const audioUploadId = track.audio_upload_id || '' const isLegacyTrack = !audioUploadId @@ -65,9 +60,20 @@ async function getAudioAnalysis(contentNodes: string[], track: Track) { // allow up to 5 attempts to get audio analysis for this track for (let i = 0; i < 5; i++) { - // choose a random content node - const contentNode = - contentNodes[Math.floor(Math.random() * contentNodes.length)] + // last 2 attempts will always be to the storeall node + let contentNode = "https://creatornode2.audius.co" + let checkStoreAllNodeNext = i == 3 + // except for tracks with >= 3 errors, which attempt to check the storall node first + if (i < 3 || track.audio_analysis_error_count! >= 3) { + // choose a random content node + contentNode = + contentNodes[Math.floor(Math.random() * contentNodes.length)] + } + // check storeall node first for any retried errors. allow 3 attempts + if (track.audio_analysis_error_count! >= 3 && i < 3) { + contentNode = "https://creatornode2.audius.co" + checkStoreAllNodeNext = i < 2 + } try { let analysisUrl = `${contentNode}/uploads/${audioUploadId}` if (isLegacyTrack) { @@ -78,28 +84,41 @@ async function getAudioAnalysis(contentNodes: string[], track: Track) { timeout: REQUEST_TIMEOUT }) if (response.status == 200) { - console.log( - `Successfully retrieved audio analysis for track ID ${ - track.track_id - }, track CID ${trackCid}${ - audioUploadId ? `, upload ID: ${audioUploadId}` : '' - } via ${contentNode}` - ) const resultsKey = isLegacyTrack ? 'results' : 'audio_analysis_results' const errorCountKey = isLegacyTrack ? 'error_count' : 'audio_analysis_error_count' + const statusKey = isLegacyTrack + ? 'status' + : 'audio_analysis_status' const results = response.data[resultsKey] const errorCount = response.data[errorCountKey] - if (!results) { - break + const analysisStatus = response.data[statusKey] + if (!results && analysisStatus != 'error') { + continue } + console.log( + `Successfully retrieved audio analysis results for track ID ${ + track.track_id + }, track CID ${trackCid}${ + audioUploadId ? `, upload ID: ${audioUploadId}` : '' + } via ${contentNode}` + ) + let musicalKey = null let bpm = null if (results?.key) { + if (results?.key.length > 12) { + console.log(`Skipping bad musical key from ${analysisUrl}`) + continue + } musicalKey = results?.key } else if (results?.Key) { + if (results?.Key.length > 12) { + console.log(`Skipping bad musical key from ${analysisUrl}`) + continue + } musicalKey = results?.Key } if (results?.bpm) { @@ -117,7 +136,7 @@ async function getAudioAnalysis(contentNodes: string[], track: Track) { break } - result = { + formattedResult = { track_id: track.track_id, musical_key: musicalKey, bpm, @@ -133,16 +152,24 @@ async function getAudioAnalysis(contentNodes: string[], track: Track) { i + 1 ) ) + if (response.status != 404 && checkStoreAllNodeNext) { + console.log('Sleeping before retrying prod cn2') + await new Promise((resolve) => setTimeout(resolve, 10000)) + } continue } } catch (error: any) { console.log(formatErrorLog(error.message, track, contentNode, i + 1)) + if (checkStoreAllNodeNext) { + console.log('Sleeping before retrying prod cn2') + await new Promise((resolve) => setTimeout(resolve, 10000)) + } continue } } release() // release the semaphore permit - return result + return formattedResult } async function fetchTracks( @@ -160,6 +187,9 @@ async function fetchTracks( 'audio_analysis_error_count' ) .andWhere('track_cid', 'is not', null) + .andWhere('genre', '!=', 'Podcasts') + .andWhere('genre', '!=', 'Podcast') + .andWhere('genre', '!=', 'Audiobooks') .orderBy('track_id', 'asc') .offset(offset) .limit(limit) @@ -234,6 +264,10 @@ export const backfillDiscovery = async (app: App) => { console.error('Missing required delegate private key. Terminating...') return } + if (config.environment != 'prod') { + console.log('Discovery audio analysis backfill is only meant to run on prod. Terminating...') + return + } const db = app.getDnDb() const BACKFILL_BATCH_SIZE = config.testRun ? 100 : 3000 await processBatches(db, BACKFILL_BATCH_SIZE) diff --git a/packages/discovery-provider/src/api/v1/helpers.py b/packages/discovery-provider/src/api/v1/helpers.py index cee2b2a12e2..d85d10f89b4 100644 --- a/packages/discovery-provider/src/api/v1/helpers.py +++ b/packages/discovery-provider/src/api/v1/helpers.py @@ -8,7 +8,6 @@ from flask_restx import reqparse from src import api_helpers -from src.api.v1.models.activities import CollectionActivity, TrackActivity from src.api.v1.models.common import full_response from src.models.rewards.challenge import ChallengeType from src.queries.get_challenges import ChallengeResponse @@ -492,16 +491,21 @@ def filter_hidden_tracks(playlist, current_user_id): def extend_activity(item): if item.get("track_id"): - return TrackActivity( - timestamp=item["activity_timestamp"], item=extend_track(item) - ) + return { + "item_type": "track", + "timestamp": item["activity_timestamp"], + "item": extend_track(item), + } if item.get("playlist_id"): + extended_playlist = extend_playlist(item) # Wee hack to make sure this marshals correctly. The marshaller for # playlist_model expects these two values to be the same type. - extended_playlist = extend_playlist(item) - return CollectionActivity( - timestamp=item["activity_timestamp"], item=extended_playlist - ) + extended_playlist["playlist_contents"] = extended_playlist["added_timestamps"] + return { + "item_type": "playlist", + "timestamp": item["activity_timestamp"], + "item": extended_playlist, + } return None diff --git a/packages/discovery-provider/src/api/v1/models/activities.py b/packages/discovery-provider/src/api/v1/models/activities.py index 5554e220ba3..2edb67f9f19 100644 --- a/packages/discovery-provider/src/api/v1/models/activities.py +++ b/packages/discovery-provider/src/api/v1/models/activities.py @@ -1,4 +1,5 @@ from flask_restx import fields +from flask_restx.fields import MarshallingError, marshal from .common import ns from .playlists import ( @@ -8,80 +9,104 @@ ) from .tracks import track, track_full -base_activity_model = ns.model( + +class ActivityItem(fields.Raw): + def format(self, value): + try: + if value.get("track_id"): + return marshal(value, track) + if value.get("playlist_id"): + return marshal(value, playlist_model) + except Exception as e: + raise MarshallingError("Unable to marshal as activity item") from e + + +class ActivityItemFull(fields.Raw): + def format(self, value): + try: + if value.get("track_id"): + return marshal(value, track_full) + if value.get("playlist_id"): + return marshal(value, full_playlist_model) + except Exception as e: + raise MarshallingError("Unable to marshal as activity item") from e + + +activity_model = ns.model( "activity", { "timestamp": fields.String(required=True), "item_type": fields.String(enum=["track", "playlist"], required=True), - "item": fields.Raw(required=True), + "item": ActivityItem(required=True), "class": fields.String(required=True, discriminator=True), }, ) track_activity_model = ns.inherit( "track_activity", - base_activity_model, + activity_model, { + "item_type": fields.String(enum=["track"], required=True), "item": fields.Nested(track, required=True), }, ) collection_activity_model = ns.inherit( "collection_activity", - base_activity_model, + activity_model, { + "timestamp": fields.String(allow_null=True), + "item_type": fields.String(enum=["playlist"], required=True), "item": fields.Nested(playlist_model, required=True), }, ) -base_activity_full_model = ns.model( +activity_full_model = ns.model( "activity_full", { "timestamp": fields.String(required=True), "item_type": fields.String(enum=["track", "playlist"], required=True), - "item": fields.Raw(required=True), + "item": ActivityItemFull(required=True), "class": fields.String(required=True, discriminator=True), }, ) track_activity_full_model = ns.inherit( "track_activity_full", - base_activity_full_model, + activity_full_model, { + "item_type": fields.String(enum=["track"], required=True), "item": fields.Nested(track_full, required=True), }, ) collection_activity_full_model = ns.inherit( "collection_activity_full", - base_activity_full_model, + activity_full_model, { + "item_type": fields.String(enum=["playlist"], required=True), "item": fields.Nested(full_playlist_model, required=True), }, ) -collection_activity_full_without_tracks_model = ns.model( +collection_activity_full_without_tracks_model = ns.inherit( "collection_activity_full_without_tracks", + activity_full_model, { - "timestamp": fields.String(required=True), "item_type": fields.String(enum=["playlist"], required=True), "item": fields.Nested(full_playlist_without_tracks_model, required=True), }, ) -class TrackActivity(object): - def __init__(self, timestamp, item): - self.timestamp = timestamp - self.item = item - self.item_type = "track" +class TrackActivity(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) -class CollectionActivity(object): - def __init__(self, timestamp, item): - self.timestamp = timestamp - self.item = item - self.item_type = "collection" +class CollectionActivity(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) activity_model = fields.Polymorph( @@ -97,3 +122,12 @@ def __init__(self, timestamp, item): CollectionActivity: collection_activity_full_model, }, ) + + +# Only to be used with `Polymorph` to map the returned classes to marshalling models +def make_polymorph_activity(activity: dict): + if activity.get("item_type") == "track": + return TrackActivity(activity) + if activity.get("item_type") == "playlist": + return CollectionActivity(activity) + return None diff --git a/packages/discovery-provider/src/api/v1/users.py b/packages/discovery-provider/src/api/v1/users.py index 9f5bccf2c4a..67dcb70c0bf 100644 --- a/packages/discovery-provider/src/api/v1/users.py +++ b/packages/discovery-provider/src/api/v1/users.py @@ -53,6 +53,7 @@ activity_full_model, activity_model, collection_activity_full_without_tracks_model, + make_polymorph_activity, track_activity_full_model, track_activity_model, ) @@ -734,7 +735,7 @@ def get(self, id): ) activities = list(map(extend_activity, reposts)) - return success_response(activities) + return success_response(list(map(make_polymorph_activity, activities))) REPOST_LIST_ROUTE = "/handle//reposts" @@ -767,7 +768,7 @@ def _get(self, handle): ) activities = list(map(extend_activity, reposts)) - return success_response(activities) + return success_response(list(map(make_polymorph_activity, activities))) @full_ns.doc( id="""Get Reposts by Handle""", @@ -892,7 +893,7 @@ def get(self, id: str, authed_user_id: int): filter_type = format_library_filter(args) get_tracks_args = GetTrackLibraryArgs( - filter_deleted=False, + filter_deleted=True, user_id=decoded_id, current_user_id=decoded_id, limit=limit, diff --git a/packages/discovery-provider/src/queries/generate_unpopulated_trending_tracks.py b/packages/discovery-provider/src/queries/generate_unpopulated_trending_tracks.py index 86289954881..4e58ab3e424 100644 --- a/packages/discovery-provider/src/queries/generate_unpopulated_trending_tracks.py +++ b/packages/discovery-provider/src/queries/generate_unpopulated_trending_tracks.py @@ -12,13 +12,9 @@ from src.models.tracks.track import Track from src.models.tracks.track_trending_score import TrackTrendingScore from src.queries.get_unpopulated_tracks import get_unpopulated_tracks -from src.tasks.generate_trending import generate_trending from src.trending_strategies.base_trending_strategy import BaseTrendingStrategy from src.trending_strategies.trending_strategy_factory import DEFAULT_TRENDING_VERSIONS -from src.trending_strategies.trending_type_and_version import ( - TrendingType, - TrendingVersion, -) +from src.trending_strategies.trending_type_and_version import TrendingType logger = logging.getLogger(__name__) @@ -38,111 +34,6 @@ def make_trending_tracks_cache_key( return f"generated-trending{version_name}:{time_range}:{(genre.lower() if genre else '')}" -def generate_unpopulated_trending( - session, - genre, - time_range, - strategy, - exclude_gated=SHOULD_TRENDING_EXCLUDE_GATED_TRACKS, - exclude_collectible_gated=SHOULD_TRENDING_EXCLUDE_COLLECTIBLE_GATED_TRACKS, - usdc_purchase_only=False, - limit=TRENDING_TRACKS_LIMIT, -): - # We use limit * 2 here to apply a soft limit so that - # when we later filter out gated tracks, - # we will probabilistically satisfy the given limit. - trending_tracks = generate_trending( - session, time_range, genre, limit * 2, 0, strategy.version - ) - - track_scores = [ - strategy.get_track_score(time_range, track) - for track in trending_tracks["listen_counts"] - ] - - # If usdc_purchase_only is true, then filter out track ids belonging to - # non-USDC purchase tracks before applying the limit. - if usdc_purchase_only: - ids = [track["track_id"] for track in track_scores] - usdc_purchase_track_ids = ( - session.query(Track.track_id) - .filter( - Track.track_id.in_(ids), - Track.is_current == True, - Track.is_delete == False, - Track.stem_of == None, - Track.is_stream_gated == True, - text("CAST(stream_conditions AS TEXT) LIKE '%usdc_purchase%'"), - ) - .all() - ) - usdc_purchase_track_id_set = set(map(lambda t: t[0], usdc_purchase_track_ids)) - track_scores = list( - filter(lambda t: t["track_id"] in usdc_purchase_track_id_set, track_scores) - ) - # If exclude_gated is true, then filter out track ids - # belonging to gated tracks before applying the limit. - elif exclude_gated: - ids = [track["track_id"] for track in track_scores] - non_stream_gated_track_ids = ( - session.query(Track.track_id) - .filter( - Track.track_id.in_(ids), - Track.is_current == True, - Track.is_delete == False, - Track.stem_of == None, - Track.is_stream_gated == False, - ) - .all() - ) - non_stream_gated_track_id_set = set( - map(lambda t: t[0], non_stream_gated_track_ids) - ) - track_scores = list( - filter( - lambda t: t["track_id"] in non_stream_gated_track_id_set, track_scores - ) - ) - elif exclude_collectible_gated: - ids = [track["track_id"] for track in track_scores] - non_collectible_gated_track_ids = ( - session.query(Track.track_id) - .filter( - Track.track_id.in_(ids), - Track.is_current == True, - Track.is_delete == False, - Track.stem_of == None, - or_( - Track.is_stream_gated == False, - not_( - text("CAST(stream_conditions AS TEXT) LIKE '%nft_collection%'") - ), - ), - ) - .all() - ) - non_collectible_gated_track_id_set = set( - map(lambda t: t[0], non_collectible_gated_track_ids) - ) - track_scores = list( - filter( - lambda t: t["track_id"] in non_collectible_gated_track_id_set, - track_scores, - ) - ) - - sorted_track_scores = sorted( - track_scores, key=lambda k: (k["score"], k["track_id"]), reverse=True - ) - sorted_track_scores = sorted_track_scores[:limit] - - # Get unpopulated metadata - track_ids = [track["track_id"] for track in sorted_track_scores] - tracks = get_unpopulated_tracks(session, track_ids, exclude_gated=exclude_gated) - - return (tracks, track_ids) - - def generate_unpopulated_trending_from_mat_views( session, genre, @@ -153,11 +44,9 @@ def generate_unpopulated_trending_from_mat_views( usdc_purchase_only=False, limit=TRENDING_TRACKS_LIMIT, ): - # use all time instead of year for version EJ57D - if strategy.version == TrendingVersion.EJ57D and time_range == "year": + # year time_range equates to allTime for current implementations + if time_range == "year": time_range = "allTime" - elif strategy.version != TrendingVersion.EJ57D and time_range == "allTime": - time_range = "year" trending_scores_query = session.query( TrackTrendingScore.track_id, TrackTrendingScore.score @@ -240,16 +129,7 @@ def make_generate_unpopulated_trending( expects to be passed a function with no arguments.""" def wrapped(): - if strategy.use_mat_view: - return generate_unpopulated_trending_from_mat_views( - session=session, - genre=genre, - time_range=time_range, - strategy=strategy, - exclude_gated=exclude_gated, - usdc_purchase_only=usdc_purchase_only, - ) - return generate_unpopulated_trending( + return generate_unpopulated_trending_from_mat_views( session=session, genre=genre, time_range=time_range, diff --git a/packages/discovery-provider/src/queries/get_trending_playlists.py b/packages/discovery-provider/src/queries/get_trending_playlists.py index 92c8abd1b3c..ef4bb8b1895 100644 --- a/packages/discovery-provider/src/queries/get_trending_playlists.py +++ b/packages/discovery-provider/src/queries/get_trending_playlists.py @@ -68,6 +68,7 @@ def get_scorable_playlist_data(session, time_range, strategy): Array<{ "playlist_id": number "created_at": string + "release_date": string "owner_id": string "windowed_save_count": number "save_count": number @@ -92,6 +93,7 @@ def get_scorable_playlist_data(session, time_range, strategy): session.query( Save.save_item_id, Playlist.created_at, + Playlist.release_date, Playlist.playlist_owner_id, func.count(Save.save_item_id), ) @@ -109,7 +111,12 @@ def get_scorable_playlist_data(session, time_range, strategy): jsonb_array_length(Playlist.playlist_contents["track_ids"]) >= mt, AggregateUser.following_count < zq, ) - .group_by(Save.save_item_id, Playlist.created_at, Playlist.playlist_owner_id) + .group_by( + Save.save_item_id, + Playlist.created_at, + Playlist.release_date, + Playlist.playlist_owner_id, + ) .order_by(desc(func.count(Save.save_item_id))) .limit(TRENDING_LIMIT) ).all() @@ -121,8 +128,9 @@ def get_scorable_playlist_data(session, time_range, strategy): record[0]: { response_name_constants.playlist_id: record[0], response_name_constants.created_at: record[1].isoformat(timespec="seconds"), - response_name_constants.owner_id: record[2], - response_name_constants.windowed_save_count: record[3], + "release_date": record[2].isoformat(timespec="seconds"), + response_name_constants.owner_id: record[3], + response_name_constants.windowed_save_count: record[4], response_name_constants.save_count: 0, response_name_constants.repost_count: 0, response_name_constants.windowed_repost_count: 0, @@ -137,7 +145,7 @@ def get_scorable_playlist_data(session, time_range, strategy): # map owner_id -> [playlist_id], accounting for multiple playlists with the same ID # used in follows playlist_owner_id_map = {} - for playlist_id, _, owner_id, _ in playlists: + for playlist_id, _, _, owner_id, _ in playlists: if owner_id not in playlist_owner_id_map: playlist_owner_id_map[owner_id] = [playlist_id] else: diff --git a/packages/discovery-provider/src/queries/get_underground_trending.py b/packages/discovery-provider/src/queries/get_underground_trending.py index 4771e593275..fa740521bac 100644 --- a/packages/discovery-provider/src/queries/get_underground_trending.py +++ b/packages/discovery-provider/src/queries/get_underground_trending.py @@ -54,6 +54,7 @@ def get_scorable_track_data(session, redis_instance, strategy): Returns a map: { "track_id": string "created_at": string + "release_date": string "owner_id": number "windowed_save_count": number "save_count": number @@ -89,6 +90,7 @@ def get_scorable_track_data(session, redis_instance, strategy): AggregateUser.follower_count, AggregatePlay.count, Track.created_at, + Track.release_date, User.is_verified, Track.is_stream_gated, Track.stream_conditions, @@ -114,6 +116,7 @@ def get_scorable_track_data(session, redis_instance, strategy): record[0]: { "track_id": record[0], "created_at": record[4].isoformat(timespec="seconds"), + "release_date": record[5].isoformat(timespec="seconds"), "owner_id": record[1], "windowed_save_count": 0, "save_count": 0, @@ -122,9 +125,9 @@ def get_scorable_track_data(session, redis_instance, strategy): "owner_follower_count": record[2], "karma": 1, "listens": record[3], - "owner_verified": record[5], - "is_stream_gated": record[6], - "stream_conditions": record[7], + "owner_verified": record[6], + "is_stream_gated": record[7], + "stream_conditions": record[8], } for record in base_query } diff --git a/packages/discovery-provider/src/queries/response_name_constants.py b/packages/discovery-provider/src/queries/response_name_constants.py index 84e438ffc5c..8aec36e9453 100644 --- a/packages/discovery-provider/src/queries/response_name_constants.py +++ b/packages/discovery-provider/src/queries/response_name_constants.py @@ -33,6 +33,7 @@ # integer - total count of tracks saves created by given user track_save_count = "track_save_count" created_at = "created_at" # datetime - time track was created + repost_count = "repost_count" # integer - total count of reposts by given user # integer - blocknumber of latest track for user track_blocknumber = "track_blocknumber" diff --git a/packages/discovery-provider/src/queries/search_es.py b/packages/discovery-provider/src/queries/search_es.py index 736deafd3d0..c2bcdb426ba 100644 --- a/packages/discovery-provider/src/queries/search_es.py +++ b/packages/discovery-provider/src/queries/search_es.py @@ -679,14 +679,40 @@ def track_dsl( } ) - # Only include the track if it is purchaseable or has purchaseable stems + # Only include the track if it is purchaseable or has purchaseable stems/download if only_purchaseable: dsl["must"].append( { "bool": { "should": [ {"term": {"purchaseable": {"value": True}}}, - {"term": {"purchaseable_download": {"value": True}}}, + { + "bool": { + "must": [ + { + "term": { + "purchaseable_download": {"value": True} + } + }, + { + "bool": { + "should": [ + { + "term": { + "has_stems": {"value": True} + } + }, + { + "term": { + "downloadable": {"value": True} + } + }, + ] + } + }, + ] + } + }, ] } } @@ -716,7 +742,7 @@ def track_dsl( double socialScore = followerCount; // Get the current time and updated_at time in milliseconds long currentTime = new Date().getTime(); - long updatedAt = doc['updated_at'].value.toInstant().toEpochMilli(); + long createdAt = doc['created_at'].value.toInstant().toEpochMilli(); // Define time thresholds in milliseconds long oneWeek = 7L * 24L * 60L * 60L * 1000L; @@ -724,7 +750,7 @@ def track_dsl( // Calculate recency factor based on time thresholds double recencyFactor; - long timeDiff = currentTime - updatedAt; + long timeDiff = currentTime - createdAt; if (timeDiff <= oneWeek) { recencyFactor = 3.0; // Most recent (week) @@ -939,6 +965,26 @@ def user_dsl( if sort_method == "recent": query["sort"] = [{"created_at": {"order": "desc"}}] + elif sort_method == "popular": + query["sort"] = [ + { + "_script": { + "type": "number", + "script": { + "lang": "painless", + "source": """ + boolean isVerified = doc['is_verified'].value; + double followerCount = doc['follower_count'].value; + double verifiedMultiplier = isVerified ? 2 : 1; + + // Calculate the popularity score + return verifiedMultiplier * (followerCount * 0.1); + """, + }, + "order": "desc", + } + } + ] return query @@ -1138,6 +1184,46 @@ def base_playlist_dsl( if sort_method == "recent": query["sort"] = [{"updated_at": {"order": "desc"}}] + elif sort_method == "popular": + query["sort"] = [ + { + "_script": { + "type": "number", + "script": { + "lang": "painless", + "source": """ + double repostCount = doc['repost_count'].value; + double saveCount = doc['save_count'].value; + double followerCount = doc['user.follower_count'].value; + double socialScore = followerCount; + // Get the current time and updated_at time in milliseconds + long currentTime = new Date().getTime(); + long updatedAt = doc['updated_at'].value.toInstant().toEpochMilli(); + + // Define time thresholds in milliseconds + long oneWeek = 7L * 24L * 60L * 60L * 1000L; + long oneMonth = 30L * 24L * 60L * 60L * 1000L; + + // Calculate recency factor based on time thresholds + double recencyFactor; + long timeDiff = currentTime - updatedAt; + + if (timeDiff <= oneWeek) { + recencyFactor = 3.0; // Most recent (week) + } else if (timeDiff <= oneMonth) { + recencyFactor = 2.0; // Recent (month) + } else { + recencyFactor = 1.0; // Older + } + + // Calculate the trending score + return ((repostCount * 0.3) + (saveCount * 0.2) + (socialScore * 0.1)) * recencyFactor; + """, + }, + "order": "desc", + } + } + ] return query diff --git a/packages/discovery-provider/src/solana/solana_client_manager.py b/packages/discovery-provider/src/solana/solana_client_manager.py index 8debbf1e691..fe5908bc53e 100644 --- a/packages/discovery-provider/src/solana/solana_client_manager.py +++ b/packages/discovery-provider/src/solana/solana_client_manager.py @@ -61,7 +61,7 @@ def handle_get_sol_tx_info(client: Client, index: int) -> GetTransactionResp: except SolanaTransactionFetchError as e: raise e except Exception as e: - logger.error( + logger.debug( f"solana_client_manager.py | get_sol_tx_info | \ Error fetching tx {tx_sig} from endpoint {endpoint}, {e}", exc_info=True, diff --git a/packages/discovery-provider/src/tasks/entity_manager/entities/track.py b/packages/discovery-provider/src/tasks/entity_manager/entities/track.py index b8a1a90193e..0dbb9ceb89a 100644 --- a/packages/discovery-provider/src/tasks/entity_manager/entities/track.py +++ b/packages/discovery-provider/src/tasks/entity_manager/entities/track.py @@ -32,7 +32,7 @@ parse_release_date, validate_signer, ) -from src.tasks.metadata import immutable_track_fields +from src.tasks.metadata import immutable_track_fields, is_valid_musical_key from src.tasks.task_helpers import generate_slug_and_collision_id from src.utils import helpers from src.utils.hardcoded_data import genre_allowlist @@ -419,6 +419,13 @@ def populate_track_record_metadata(track_record: Track, track_metadata, handle, except ValueError: continue + elif key == "musical_key": + if "musical_key" in track_metadata and track_metadata["musical_key"]: + if isinstance( + track_metadata["musical_key"], str + ) and is_valid_musical_key(track_metadata["musical_key"]): + track_record.musical_key = track_metadata["musical_key"] + else: # For most fields, update the track_record when the corresponding field exists # in track_metadata diff --git a/packages/discovery-provider/src/tasks/index_solana_plays.py b/packages/discovery-provider/src/tasks/index_solana_plays.py index 7c3acc51d96..69d51c12a9e 100644 --- a/packages/discovery-provider/src/tasks/index_solana_plays.py +++ b/packages/discovery-provider/src/tasks/index_solana_plays.py @@ -88,7 +88,7 @@ def parse_instruction_data( decoded[user_id_start:user_id_end] ), ) - logger.warning( + logger.debug( log, exc_info=False, ) diff --git a/packages/discovery-provider/src/tasks/index_trending.py b/packages/discovery-provider/src/tasks/index_trending.py index e28b2ddb101..af360475077 100644 --- a/packages/discovery-provider/src/tasks/index_trending.py +++ b/packages/discovery-provider/src/tasks/index_trending.py @@ -12,7 +12,6 @@ from src.models.notifications.notification import Notification from src.models.tracks.track import Track from src.queries.generate_unpopulated_trending_tracks import ( - generate_unpopulated_trending, generate_unpopulated_trending_from_mat_views, make_trending_tracks_cache_key, ) @@ -101,8 +100,7 @@ def index_trending(self, db: SessionManager, redis: Redis, timestamp): strategy = trending_strategy_factory.get_strategy( TrendingType.TRACKS, version ) - if strategy.use_mat_view: - strategy.update_track_score_query(session) + strategy.update_track_score_query(session) for version in trending_track_versions: strategy = trending_strategy_factory.get_strategy( @@ -111,20 +109,12 @@ def index_trending(self, db: SessionManager, redis: Redis, timestamp): for genre in genres: for time_range in time_ranges: cache_start_time = time.time() - if strategy.use_mat_view: - res = generate_unpopulated_trending_from_mat_views( - session=session, - genre=genre, - time_range=time_range, - strategy=strategy, - ) - else: - res = generate_unpopulated_trending( - session=session, - genre=genre, - time_range=time_range, - strategy=strategy, - ) + res = generate_unpopulated_trending_from_mat_views( + session=session, + genre=genre, + time_range=time_range, + strategy=strategy, + ) key = make_trending_tracks_cache_key(time_range, genre, version) set_json_cached_key(redis, key, res) cache_end_time = time.time() diff --git a/packages/discovery-provider/src/tasks/metadata.py b/packages/discovery-provider/src/tasks/metadata.py index d3c989b5953..69d47f6b4e9 100644 --- a/packages/discovery-provider/src/tasks/metadata.py +++ b/packages/discovery-provider/src/tasks/metadata.py @@ -1,8 +1,44 @@ +from enum import Enum from typing import Any, List, Optional, TypedDict # Required format for track metadata retrieved from the content system +class MusicalKey(str, Enum): + A_MAJOR = "A major" + A_MINOR = "A minor" + B_FLAT_MAJOR = "B flat major" + B_FLAT_MINOR = "B flat minor" + B_MAJOR = "B major" + B_MINOR = "B minor" + C_MAJOR = "C major" + C_MINOR = "C minor" + D_FLAT_MAJOR = "D flat major" + D_FLAT_MINOR = "D flat minor" + D_MAJOR = "D major" + D_MINOR = "D minor" + E_FLAT_MAJOR = "E flat major" + E_FLAT_MINOR = "E flat minor" + E_MAJOR = "E major" + E_MINOR = "E minor" + F_MAJOR = "F major" + F_MINOR = "F minor" + G_FLAT_MAJOR = "G flat major" + G_FLAT_MINOR = "G flat minor" + G_MAJOR = "G major" + G_MINOR = "G minor" + A_FLAT_MAJOR = "A flat major" + A_FLAT_MINOR = "A flat minor" + SILENCE = "Silence" + + def __str__(self) -> str: + return str.__str__(self) + + +def is_valid_musical_key(musical_key: str) -> bool: + return musical_key in MusicalKey.__members__.values() + + class TrackParent(TypedDict): parent_track_id: int diff --git a/packages/discovery-provider/src/tasks/repair_audio_analyses.py b/packages/discovery-provider/src/tasks/repair_audio_analyses.py index 233658e0bc2..21876fdf1ea 100644 --- a/packages/discovery-provider/src/tasks/repair_audio_analyses.py +++ b/packages/discovery-provider/src/tasks/repair_audio_analyses.py @@ -9,6 +9,7 @@ from src.models.tracks.track import Track from src.tasks.celery_app import celery +from src.tasks.metadata import is_valid_musical_key from src.utils import get_all_nodes from src.utils.prometheus_metric import save_duration_metric from src.utils.structured_logger import StructuredLogger, log_duration @@ -21,7 +22,7 @@ def valid_musical_key(musical_key): - return isinstance(musical_key, str) and len(musical_key) <= 12 + return isinstance(musical_key, str) and is_valid_musical_key(musical_key) def valid_bpm(bpm): @@ -40,7 +41,7 @@ def query_tracks(session: Session) -> List[Track]: Track.genre != "Podcast", Track.genre != "Audiobooks", ) - .order_by(Track.track_id.asc()) + .order_by(Track.track_id.desc()) .limit(BATCH_SIZE) .all() ) diff --git a/packages/discovery-provider/src/trending_strategies/EJ57D_trending_tracks_strategy.py b/packages/discovery-provider/src/trending_strategies/EJ57D_trending_tracks_strategy.py index c9b7799d48e..33b425beb75 100644 --- a/packages/discovery-provider/src/trending_strategies/EJ57D_trending_tracks_strategy.py +++ b/packages/discovery-provider/src/trending_strategies/EJ57D_trending_tracks_strategy.py @@ -52,7 +52,7 @@ def z(time, track): class TrendingTracksStrategyEJ57D(BaseTrendingStrategy): def __init__(self): - super().__init__(TrendingType.TRACKS, TrendingVersion.EJ57D, True) + super().__init__(TrendingType.TRACKS, TrendingVersion.EJ57D) def get_track_score(self, time_range, track): logger.error( diff --git a/packages/discovery-provider/src/trending_strategies/base_trending_strategy.py b/packages/discovery-provider/src/trending_strategies/base_trending_strategy.py index 82f99e99a7c..07219768eb3 100644 --- a/packages/discovery-provider/src/trending_strategies/base_trending_strategy.py +++ b/packages/discovery-provider/src/trending_strategies/base_trending_strategy.py @@ -7,12 +7,9 @@ class BaseTrendingStrategy(ABC): - def __init__( - self, trending_type: TrendingType, version: TrendingVersion, use_mat_view=False - ): + def __init__(self, trending_type: TrendingType, version: TrendingVersion): self.trending_type = trending_type self.version = version - self.use_mat_view = use_mat_view @abstractmethod def get_track_score(self, time_range: str, track): diff --git a/packages/discovery-provider/src/trending_strategies/pnagD_trending_playlists_strategy.py b/packages/discovery-provider/src/trending_strategies/pnagD_trending_playlists_strategy.py new file mode 100644 index 00000000000..1f607878b61 --- /dev/null +++ b/packages/discovery-provider/src/trending_strategies/pnagD_trending_playlists_strategy.py @@ -0,0 +1,54 @@ +from datetime import datetime + +from dateutil.parser import parse + +from src.trending_strategies.base_trending_strategy import BaseTrendingStrategy +from src.trending_strategies.trending_type_and_version import ( + TrendingType, + TrendingVersion, +) + +N = 1 +a = max +M = pow +F = 50 +O = 1 +R = 0.25 +i = 0.01 +q = 100000.0 +T = {"day": 1, "week": 7, "month": 30, "year": 365, "allTime": 100000} +y = 3 + + +class TrendingPlaylistsStrategypnagD(BaseTrendingStrategy): + def __init__(self): + super().__init__(TrendingType.PLAYLISTS, TrendingVersion.pnagD) + + def get_track_score(self, time_range, playlist): + # pylint: disable=W,C,R + E = playlist["listens"] + e = playlist["windowed_repost_count"] + t = playlist["repost_count"] + x = playlist["windowed_save_count"] + A = playlist["save_count"] + o = playlist["created_at"] + v = playlist["release_date"] + l = playlist["owner_follower_count"] + j = playlist["karma"] + if l < y: + return {"score": 0, **playlist} + H = (N * E + F * e + O * x + R * t + i * A) * j + L = T[time_range] + K = datetime.now() + w = parse(o) + wv = parse(v) + if wv > K: + wv = w + k = (K - max(w, wv)).days + Q = 1 + if k > L: + Q = a((1.0 / q), (M(q, (1 - k / L)))) + return {"score": H * Q, **playlist} + + def get_score_params(self): + return {"zq": 1000, "xf": True, "pt": 0, "mt": 3} diff --git a/packages/discovery-provider/src/trending_strategies/pnagD_trending_tracks_strategy.py b/packages/discovery-provider/src/trending_strategies/pnagD_trending_tracks_strategy.py new file mode 100644 index 00000000000..2607b8a83dc --- /dev/null +++ b/packages/discovery-provider/src/trending_strategies/pnagD_trending_tracks_strategy.py @@ -0,0 +1,201 @@ +import logging +import time + +from sqlalchemy.sql import text + +from src.trending_strategies.base_trending_strategy import BaseTrendingStrategy +from src.trending_strategies.trending_type_and_version import ( + TrendingType, + TrendingVersion, +) + +logger = logging.getLogger(__name__) + + +# Trending Parameters +N = 1 +a = max +M = pow +F = 50 +O = 1 +R = 0.25 +i = 0.01 +q = 100000.0 +T = {"day": 1, "week": 7, "month": 30, "year": 365, "allTime": 100000} +y = 3 + + +class TrendingTracksStrategypnagD(BaseTrendingStrategy): + def __init__(self): + super().__init__(TrendingType.TRACKS, TrendingVersion.pnagD) + + def get_track_score(self, time_range, track): + logger.error( + f"get_track_score not implemented for Trending Tracks Strategy with version {TrendingVersion.pnagD}" + ) + + def update_track_score_query(self, session): + start_time = time.time() + trending_track_query = text( + """ + begin; + DELETE FROM track_trending_scores WHERE type=:type AND version=:version; + INSERT INTO track_trending_scores + (track_id, genre, type, version, time_range, score, created_at) + select + tp.track_id, + tp.genre, + :type, + :version, + :week_time_range, + CASE + WHEN tp.owner_follower_count < :y + THEN 0 + WHEN EXTRACT(DAYS FROM now() - ( + CASE + WHEN tp.release_date > now() THEN aip.created_at + ELSE GREATEST(tp.release_date, aip.created_at) + END + )) > :week + THEN GREATEST( + 1.0 / :q, + POW( + :q, + GREATEST( + -10, + 1.0 - 1.0 * EXTRACT(DAYS FROM now() - ( + CASE + WHEN tp.release_date > now() THEN aip.created_at + ELSE GREATEST(tp.release_date, aip.created_at) + END + )) / :week + ) + ) + ) * ( + :N * aip.week_listen_counts + + :F * tp.repost_week_count + + :O * tp.save_week_count + + :R * tp.repost_count + + :i * tp.save_count + ) * tp.karma + ELSE ( + :N * aip.week_listen_counts + + :F * tp.repost_week_count + + :O * tp.save_week_count + + :R * tp.repost_count + + :i * tp.save_count + ) * tp.karma + END as week_score, + now() + from trending_params tp + inner join aggregate_interval_plays aip + on tp.track_id = aip.track_id; + INSERT INTO track_trending_scores + (track_id, genre, type, version, time_range, score, created_at) + select + tp.track_id, + tp.genre, + :type, + :version, + :month_time_range, + CASE + WHEN tp.owner_follower_count < :y + THEN 0 + WHEN EXTRACT(DAYS FROM now() - ( + CASE + WHEN tp.release_date > now() THEN aip.created_at + ELSE GREATEST(tp.release_date, aip.created_at) + END + )) > :month + THEN GREATEST( + 1.0 / :q, + POW( + :q, + GREATEST( + -10, + 1.0 - 1.0 * EXTRACT(DAYS FROM now() - ( + CASE + WHEN tp.release_date > now() THEN aip.created_at + ELSE GREATEST(tp.release_date, aip.created_at) + END + )) / :month + ) + ) + ) * ( + :N * aip.month_listen_counts + + :F * tp.repost_month_count + + :O * tp.save_month_count + + :R * tp.repost_count + + :i * tp.save_count + ) * tp.karma + ELSE ( + :N * aip.month_listen_counts + + :F * tp.repost_month_count + + :O * tp.save_month_count + + :R * tp.repost_count + + :i * tp.save_count + ) * tp.karma + END as month_score, + now() + from trending_params tp + inner join aggregate_interval_plays aip + on tp.track_id = aip.track_id; + INSERT INTO track_trending_scores + (track_id, genre, type, version, time_range, score, created_at) + select + tp.track_id, + tp.genre, + :type, + :version, + :all_time_time_range, + CASE + WHEN tp.owner_follower_count < :y + THEN 0 + ELSE (:N * ap.count + :R * tp.repost_count + :i * tp.save_count) * tp.karma + END as all_time_score, + now() + from trending_params tp + inner join aggregate_plays ap + on tp.track_id = ap.play_item_id + inner join tracks t + on ap.play_item_id = t.track_id + where -- same filtering for aggregate_interval_plays + t.is_current is True AND + t.is_delete is False AND + t.is_unlisted is False AND + t.stem_of is Null; + commit; + """ + ) + session.execute( + trending_track_query, + { + "week": T["week"], + "month": T["month"], + "N": N, + "F": F, + "O": O, + "R": R, + "i": i, + "q": q, + "y": y, + "type": self.trending_type.name, + "version": self.version.name, + "week_time_range": "week", + "month_time_range": "month", + "all_time_time_range": "allTime", + }, + ) + duration = time.time() - start_time + logger.info( + f"trending_tracks_strategy | Finished calculating trending scores in {duration} seconds", + extra={ + "id": "trending_strategy", + "type": self.trending_type.name, + "version": self.version.name, + "duration": duration, + }, + ) + + def get_score_params(self): + return {"xf": True, "pt": 0, "nm": 5} diff --git a/packages/discovery-provider/src/trending_strategies/pnagD_underground_trending_tracks_strategy.py b/packages/discovery-provider/src/trending_strategies/pnagD_underground_trending_tracks_strategy.py new file mode 100644 index 00000000000..512892002dc --- /dev/null +++ b/packages/discovery-provider/src/trending_strategies/pnagD_underground_trending_tracks_strategy.py @@ -0,0 +1,69 @@ +from datetime import datetime + +from dateutil.parser import parse + +from src.trending_strategies.base_trending_strategy import BaseTrendingStrategy +from src.trending_strategies.trending_type_and_version import ( + TrendingType, + TrendingVersion, +) + +b = 5 +qw = 50 +hg = 1 +ie = 0.25 +pn = 0.01 +u = 30.0 +qq = 0.001 +oi = 20 +nb = 750 + + +class UndergroundTrendingTracksStrategypnagD(BaseTrendingStrategy): + def __init__(self): + super().__init__(TrendingType.UNDERGROUND_TRACKS, TrendingVersion.pnagD) + + def get_track_score(self, time_range, track): + # pylint: disable=W,C,R + mn = track["listens"] + c = track["windowed_repost_count"] + x = track["repost_count"] + v = track["windowed_save_count"] + ut = track["save_count"] + ll = track["created_at"] + lv = track["release_date"] + bq = track["owner_follower_count"] + ty = track["owner_verified"] + kz = track["karma"] + xy = max + uk = pow + if bq < 3: + return {"score": 0, **track} + oj = qq if ty else 1 + zu = 1 + if bq >= nb: + zu = xy(uk(oi, 1 - ((1 / nb) * (bq - nb) + 1)), 1 / oi) + vb = (b * mn + qw * c + hg * v + ie * x + pn * ut + zu * bq) * kz * zu * oj + te = 7 + fd = datetime.now() + xn = parse(ll) + xv = parse(lv) + if xv > fd: + xv = xn + ul = (fd - max(xn, xv)).days + rq = 1 + if ul > te: + rq = xy((1.0 / u), (uk(u, (1 - ul / te)))) + return {"score": vb * rq, **track} + + def get_score_params(self): + return { + "S": 1500, + "r": 1500, + "q": 50, + "o": 21, + "f": 7, + "qr": 10, + "xf": True, + "pt": 0, + } diff --git a/packages/discovery-provider/src/trending_strategies/trending_strategy_factory.py b/packages/discovery-provider/src/trending_strategies/trending_strategy_factory.py index cb4ad6bca1d..792d9954ce3 100644 --- a/packages/discovery-provider/src/trending_strategies/trending_strategy_factory.py +++ b/packages/discovery-provider/src/trending_strategies/trending_strategy_factory.py @@ -7,6 +7,15 @@ from src.trending_strategies.EJ57D_underground_trending_tracks_strategy import ( UndergroundTrendingTracksStrategyEJ57D, ) +from src.trending_strategies.pnagD_trending_playlists_strategy import ( + TrendingPlaylistsStrategypnagD, +) +from src.trending_strategies.pnagD_trending_tracks_strategy import ( + TrendingTracksStrategypnagD, +) +from src.trending_strategies.pnagD_underground_trending_tracks_strategy import ( + UndergroundTrendingTracksStrategypnagD, +) from src.trending_strategies.trending_type_and_version import ( TrendingType, TrendingVersion, @@ -24,12 +33,15 @@ def __init__(self): self.strategies = { TrendingType.TRACKS: { TrendingVersion.EJ57D: TrendingTracksStrategyEJ57D(), + TrendingVersion.pnagD: TrendingTracksStrategypnagD(), }, TrendingType.UNDERGROUND_TRACKS: { TrendingVersion.EJ57D: UndergroundTrendingTracksStrategyEJ57D(), + TrendingVersion.pnagD: UndergroundTrendingTracksStrategypnagD(), }, TrendingType.PLAYLISTS: { TrendingVersion.BDNxn: TrendingPlaylistsStrategyBDNxn(), + TrendingVersion.pnagD: TrendingPlaylistsStrategypnagD(), }, } diff --git a/packages/discovery-provider/src/trending_strategies/trending_type_and_version.py b/packages/discovery-provider/src/trending_strategies/trending_type_and_version.py index a58542f94fb..df61b3027f7 100644 --- a/packages/discovery-provider/src/trending_strategies/trending_type_and_version.py +++ b/packages/discovery-provider/src/trending_strategies/trending_type_and_version.py @@ -10,3 +10,4 @@ class TrendingType(Enum): class TrendingVersion(Enum): EJ57D = "EJ57D" BDNxn = "BDNxn" + pnagD = "pnagD" diff --git a/packages/discovery-provider/src/utils/elasticdsl.py b/packages/discovery-provider/src/utils/elasticdsl.py index 1ffd407f52b..8d8eb704c69 100644 --- a/packages/discovery-provider/src/utils/elasticdsl.py +++ b/packages/discovery-provider/src/utils/elasticdsl.py @@ -1,11 +1,12 @@ import copy import os -from elasticsearch import Elasticsearch, logger, logging +from elasticsearch import Elasticsearch, logging from src.utils.spl_audio import to_wei -logger.setLevel(logging.WARNING) +logging.getLogger("elasticsearch").setLevel(logging.WARNING) +logging.getLogger("elastic_transport.transport").setLevel(logging.WARNING) _esclient = None # uses aliases diff --git a/packages/discovery-provider/src/utils/rendezvous.py b/packages/discovery-provider/src/utils/rendezvous.py index 789da704698..dc1d6c31c74 100644 --- a/packages/discovery-provider/src/utils/rendezvous.py +++ b/packages/discovery-provider/src/utils/rendezvous.py @@ -1,8 +1,6 @@ from hashlib import sha256 from typing import List -import crc32c - dead_nodes = ["https://content.grassfed.network/"] @@ -20,25 +18,13 @@ def get_nodes(self) -> List[str]: return self.nodes def get(self, key: str) -> str: - ranked = self.rank_hybrid(key) + ranked = self.rank_sha256(key) if ranked: return ranked[0] return "" - # HashMigration: use rank_sha256 after migration done def get_n(self, n: int, key: str) -> List[str]: - return self.rank_hybrid(key)[:n] - - def rank_hybrid(self, key: str) -> List[str]: - legacy = self.rank_crc32(2, key) - modern = [h for h in self.rank_sha256(key) if h not in legacy] - hybrid = legacy + modern - return hybrid - - def rank_crc32(self, n: int, key: str) -> List[str]: - scores = [(self.hash(node, key), node) for node in self.nodes] - scores.sort(key=lambda x: (-x[0], x[1])) - return [t[1] for t in scores[:n]] + return self.rank_sha256(key)[:n] def rank_sha256(self, key: str) -> List[str]: tuples = [ @@ -46,9 +32,3 @@ def rank_sha256(self, key: str) -> List[str]: ] tuples.sort(key=lambda t: (t[1], t[0])) return [t[0] for t in tuples] - - @staticmethod - def hash(node: str, key: str) -> int: - combined = (key + node).encode("utf-8") - # Convert to unsigned 32-bit integer to match golang uint32 here: https://github.com/tysonmote/rendezvous/blob/be0258dbbd3d/rendezvous.go#L92 - return crc32c.crc32c(combined) & 0xFFFFFFFF diff --git a/packages/discovery-provider/src/utils/rendezvous_unit_test.py b/packages/discovery-provider/src/utils/rendezvous_unit_test.py index a45b9222a97..f2ad8f9bf45 100644 --- a/packages/discovery-provider/src/utils/rendezvous_unit_test.py +++ b/packages/discovery-provider/src/utils/rendezvous_unit_test.py @@ -17,29 +17,12 @@ def test_add_additional_nodes(): assert hash_obj.get_nodes() == nodes + new_nodes -def test_return_highest_scoring_node(): - nodes = ["node1", "node2", "node3"] - hash_obj = RendezvousHash(*nodes) - key = "test-key" - highest_node = hash_obj.get(key) - assert highest_node in nodes - - -def test_return_top_n_nodes(): - nodes = ["node1", "node2", "node3"] - hash_obj = RendezvousHash(*nodes) - key = "test-key" - top2_nodes = hash_obj.rank_crc32(2, key) - assert len(top2_nodes) == 2 - assert all(node in nodes for node in top2_nodes) - - @pytest.mark.parametrize( "key,expected", [ ("", "d"), ("foo", "e"), - ("bar", "c"), + ("bar", "b"), ], ) def test_hash_get(key, expected): @@ -49,26 +32,6 @@ def test_hash_get(key, expected): assert got_node == expected -# Ensures the hash results match the results of the equivalent Go code. -# See https://github.com/tysonmote/rendezvous/blob/be0258dbbd3d0df637b328d951067124541e7b6a/rendezvous_test.go -@pytest.mark.parametrize( - "n,key,expected", - [ - (1, "foo", ["e"]), - (2, "bar", ["c", "e"]), - (3, "baz", ["d", "a", "b"]), - (2, "biz", ["b", "a"]), - (0, "boz", []), - (100, "floo", ["d", "a", "b", "c", "e"]), - ], -) -def test_hash_get_n(n, key, expected): - hash_obj = RendezvousHash() - hash_obj.add("a", "b", "c", "d", "e") - got_nodes = hash_obj.rank_crc32(n, key) - assert got_nodes == expected - - def test_hybrid(): node_list = "https://creatornode.audius.prod-eks-ap-northeast-1.staked.cloud,https://creatornode.audius1.prod-eks-ap-northeast-1.staked.cloud,https://creatornode.audius2.prod-eks-ap-northeast-1.staked.cloud,https://creatornode.audius3.prod-eks-ap-northeast-1.staked.cloud,https://creatornode.audius8.prod-eks-ap-northeast-1.staked.cloud,https://creatornode.audius.co,https://creatornode2.audius.co,https://creatornode3.audius.co,https://usermetadata.audius.co,https://audius-content-1.cultur3stake.com,https://audius-content-10.cultur3stake.com,https://audius-content-11.cultur3stake.com,https://audius-content-12.cultur3stake.com,https://audius-content-13.cultur3stake.com,https://audius-content-14.cultur3stake.com,https://audius-content-15.cultur3stake.com,https://audius-content-16.cultur3stake.com,https://audius-content-17.cultur3stake.com,https://audius-content-18.cultur3stake.com,https://audius-content-2.cultur3stake.com,https://audius-content-3.cultur3stake.com,https://audius-content-4.cultur3stake.com,https://audius-content-5.cultur3stake.com,https://audius-content-6.cultur3stake.com,https://audius-content-7.cultur3stake.com,https://audius-content-8.cultur3stake.com,https://audius-content-9.cultur3stake.com,https://cn1.stuffisup.com,https://audius-cn1.tikilabs.com,https://audius.prod.capturealpha.io,https://audius-content-1.figment.io,https://audius-content-10.figment.io,https://audius-content-11.figment.io,https://audius-content-12.figment.io,https://audius-content-13.figment.io,https://audius-content-14.figment.io,https://audius-content-2.figment.io,https://audius-content-3.figment.io,https://audius-content-4.figment.io,https://audius-content-5.figment.io,https://audius-content-6.figment.io,https://audius-content-7.figment.io,https://audius-content-8.figment.io,https://audius-content-9.figment.io,https://blockchange-audius-content-01.bdnodes.net,https://blockchange-audius-content-02.bdnodes.net,https://blockchange-audius-content-03.bdnodes.net,https://blockdaemon-audius-content-01.bdnodes.net,https://blockdaemon-audius-content-02.bdnodes.net,https://blockdaemon-audius-content-03.bdnodes.net,https://blockdaemon-audius-content-04.bdnodes.net,https://blockdaemon-audius-content-05.bdnodes.net,https://blockdaemon-audius-content-06.bdnodes.net,https://blockdaemon-audius-content-07.bdnodes.net,https://blockdaemon-audius-content-08.bdnodes.net,https://blockdaemon-audius-content-09.bdnodes.net,https://content.grassfed.network,https://cn0.mainnet.audiusindex.org,https://cn1.mainnet.audiusindex.org,https://cn2.mainnet.audiusindex.org,https://cn3.mainnet.audiusindex.org,https://cn4.mainnet.audiusindex.org,https://audius-content-1.jollyworld.xyz,https://audius-creator-1.theblueprint.xyz,https://audius-creator-2.theblueprint.xyz,https://audius-creator-3.theblueprint.xyz,https://audius-creator-4.theblueprint.xyz,https://audius-creator-5.theblueprint.xyz,https://audius-creator-6.theblueprint.xyz".split( "," @@ -87,25 +50,3 @@ def test_hybrid(): "https://blockdaemon-audius-content-07.bdnodes.net", ] assert got == expected - - # crc32 top 2 - got = hasher.rank_crc32(2, test_cid) - expected = [ - "https://audius-content-15.cultur3stake.com", - "https://audius-content-4.figment.io", - ] - assert got == expected - - # hybrid - got = hasher.get_n(6, test_cid) - expected = [ - # crc32 (top2) - "https://audius-content-15.cultur3stake.com", - "https://audius-content-4.figment.io", - # sha256 - "https://blockdaemon-audius-content-09.bdnodes.net", - "https://cn4.mainnet.audiusindex.org", - "https://cn0.mainnet.audiusindex.org", - "https://creatornode.audius3.prod-eks-ap-northeast-1.staked.cloud", - ] - assert got == expected diff --git a/packages/es-indexer/src/indexNames.ts b/packages/es-indexer/src/indexNames.ts index bd74127a3de..036229ebd22 100644 --- a/packages/es-indexer/src/indexNames.ts +++ b/packages/es-indexer/src/indexNames.ts @@ -2,6 +2,6 @@ export const indexNames = { playlists: 'playlists21', reposts: 'reposts13', saves: 'saves13', - tracks: 'tracks21', + tracks: 'tracks22', users: 'users16', } diff --git a/packages/es-indexer/src/listener.ts b/packages/es-indexer/src/listener.ts index b93d26d45e9..203dacf8951 100644 --- a/packages/es-indexer/src/listener.ts +++ b/packages/es-indexer/src/listener.ts @@ -107,6 +107,13 @@ export async function startListener() { for (const r of playlists.rows) { pending.playlistIds.add(r.playlist_id) } + // re-index any tracks this is a stem of + const tracks = await client.query( + `select parent_track_id from stems where child_track_id = ${track.track_id}` + ) + for (const r of tracks.rows) { + pending.trackIds.add(r.parent_track_id) + } }, playlists: (playlist: PlaylistRow) => { pending.playlistIds.add(playlist.playlist_id) diff --git a/packages/harmony/src/components/button/FilterButton/FilterButton.tsx b/packages/harmony/src/components/button/FilterButton/FilterButton.tsx index b98e6bc61a6..9c1512c29c4 100644 --- a/packages/harmony/src/components/button/FilterButton/FilterButton.tsx +++ b/packages/harmony/src/components/button/FilterButton/FilterButton.tsx @@ -69,21 +69,18 @@ export const FilterButton = forwardRef( background: color.secondary.s400, border: `1px solid ${color.secondary.s400}`, '&:hover': { - border: `1px solid ${color.secondary.s400}`, - transform: 'none' + border: `1px solid ${color.secondary.s400}` } } - const activeStyle = - variant !== 'fillContainer' || value === null - ? { - border: `1px solid ${color.border.strong}`, - background: color.background.surface2 - } - : {} - const hoverStyle = { - border: `1px solid ${color.neutral.n800}`, + background: color.background.surface1 + } + + const activeStyle = { + background: color.background.surface2 + } + const disabledTransform = { transform: 'none' } @@ -102,16 +99,21 @@ export const FilterButton = forwardRef( lineHeight: typography.lineHeight.s, opacity: disabled ? 0.6 : 1, - '&:hover': hoverStyle, - '&:focus': hoverStyle, - + '&:hover': { + ...disabledTransform, + ...(value === null && !isOpen ? hoverStyle : {}) + }, + '&:focus': { + ...disabledTransform, + ...(value === null ? activeStyle : {}) + }, '&:active': { - ...activeStyle, - transform: 'none' + ...disabledTransform, + ...(value === null ? activeStyle : {}) }, + ...(isOpen ? activeStyle : {}), ...(size === 'small' ? smallStyles : defaultStyles), - ...(isOpen ? activeStyle : {}), ...(variant === 'fillContainer' && value !== null ? fillContainerStyles : {}) diff --git a/packages/harmony/src/components/select/Select/Select.tsx b/packages/harmony/src/components/select/Select/Select.tsx index 401845287b8..411f7dc1458 100644 --- a/packages/harmony/src/components/select/Select/Select.tsx +++ b/packages/harmony/src/components/select/Select/Select.tsx @@ -135,7 +135,7 @@ export const Select = forwardRef(function Select( controlledProp: selectionProp, defaultValue: null, stateName: 'selection', - componentName: 'FilterButton' + componentName: 'Select' }) // TODO: implement filtering diff --git a/packages/harmony/src/components/select/SelectInput/SelectInput.tsx b/packages/harmony/src/components/select/SelectInput/SelectInput.tsx index 253f902f716..0f7827dba06 100644 --- a/packages/harmony/src/components/select/SelectInput/SelectInput.tsx +++ b/packages/harmony/src/components/select/SelectInput/SelectInput.tsx @@ -17,6 +17,7 @@ import { SelectInputProps } from './types' export const SelectInput = forwardRef( function Select(props, ref) { const { + disableReset, value: valueProp, children, onChange, @@ -47,7 +48,7 @@ export const SelectInput = forwardRef( (e) => { e.stopPropagation() e.preventDefault() - if (value !== null) { + if (value !== null && !disableReset) { setValue(null) // @ts-ignore onChange?.(null) @@ -56,7 +57,7 @@ export const SelectInput = forwardRef( setIsOpen((isOpen: boolean) => !isOpen) } }, - [value, setIsOpen, setValue, onChange, onReset] + [value, setIsOpen, setValue, onChange, onReset, disableReset] ) useEffect(() => { @@ -81,7 +82,11 @@ export const SelectInput = forwardRef( & { * This will override the default behavior of toggling isOpen */ onClick?: () => void + + disableReset?: boolean } diff --git a/packages/harmony/src/components/tag/Tag.tsx b/packages/harmony/src/components/tag/Tag.tsx index f9cecce2cff..3f40e856d56 100644 --- a/packages/harmony/src/components/tag/Tag.tsx +++ b/packages/harmony/src/components/tag/Tag.tsx @@ -73,6 +73,7 @@ export const Tag = (props: TagProps) => { {Icon ? ( ({ accent: primitives.secondary.s300, staticWhite: primitives.static.white, warning: primitives.special.orange, - danger: primitives.special.red + danger: primitives.special.red, + premium: primitives.special.lightGreen, + special: primitives.special.blue }, icon: { heading: primitives.special.gradient, @@ -22,7 +24,9 @@ const createSemanticTheme = (primitives: PrimitiveColors) => ({ accent: primitives.secondary.s300, staticWhite: primitives.static.white, warning: primitives.special.orange, - danger: primitives.special.red + danger: primitives.special.red, + premium: primitives.special.lightGreen, + special: primitives.special.blue }, link: { default: primitives.neutral.neutral, diff --git a/packages/libs/src/sdk/api/generated/default/models/CollectionActivity.ts b/packages/libs/src/sdk/api/generated/default/models/CollectionActivity.ts index ee2b0255d1a..4bf238b6637 100644 --- a/packages/libs/src/sdk/api/generated/default/models/CollectionActivity.ts +++ b/packages/libs/src/sdk/api/generated/default/models/CollectionActivity.ts @@ -33,6 +33,18 @@ import { * @interface CollectionActivity */ export interface CollectionActivity extends Activity { + /** + * + * @type {string} + * @memberof CollectionActivity + */ + timestamp?: string; + /** + * + * @type {string} + * @memberof CollectionActivity + */ + itemType: CollectionActivityItemTypeEnum; /** * * @type {Playlist} @@ -42,12 +54,21 @@ export interface CollectionActivity extends Activity { } +/** + * @export + */ +export const CollectionActivityItemTypeEnum = { + Playlist: 'playlist' +} as const; +export type CollectionActivityItemTypeEnum = typeof CollectionActivityItemTypeEnum[keyof typeof CollectionActivityItemTypeEnum]; + /** * Check if a given object implements the CollectionActivity interface. */ export function instanceOfCollectionActivity(value: object): value is CollectionActivity { let isInstance = true; + isInstance = isInstance && "itemType" in value && value["itemType"] !== undefined; isInstance = isInstance && "item" in value && value["item"] !== undefined; return isInstance; @@ -63,6 +84,8 @@ export function CollectionActivityFromJSONTyped(json: any, ignoreDiscriminator: } return { ...ActivityFromJSONTyped(json, ignoreDiscriminator), + 'timestamp': !exists(json, 'timestamp') ? undefined : json['timestamp'], + 'itemType': json['item_type'], 'item': PlaylistFromJSON(json['item']), }; } @@ -76,6 +99,8 @@ export function CollectionActivityToJSON(value?: CollectionActivity | null): any } return { ...ActivityToJSON(value), + 'timestamp': value.timestamp, + 'item_type': value.itemType, 'item': PlaylistToJSON(value.item), }; } diff --git a/packages/libs/src/sdk/api/generated/default/models/TrackActivity.ts b/packages/libs/src/sdk/api/generated/default/models/TrackActivity.ts index 3242c33a132..2b65f99e759 100644 --- a/packages/libs/src/sdk/api/generated/default/models/TrackActivity.ts +++ b/packages/libs/src/sdk/api/generated/default/models/TrackActivity.ts @@ -33,6 +33,12 @@ import { * @interface TrackActivity */ export interface TrackActivity extends Activity { + /** + * + * @type {string} + * @memberof TrackActivity + */ + itemType: TrackActivityItemTypeEnum; /** * * @type {Track} @@ -42,12 +48,21 @@ export interface TrackActivity extends Activity { } +/** + * @export + */ +export const TrackActivityItemTypeEnum = { + Track: 'track' +} as const; +export type TrackActivityItemTypeEnum = typeof TrackActivityItemTypeEnum[keyof typeof TrackActivityItemTypeEnum]; + /** * Check if a given object implements the TrackActivity interface. */ export function instanceOfTrackActivity(value: object): value is TrackActivity { let isInstance = true; + isInstance = isInstance && "itemType" in value && value["itemType"] !== undefined; isInstance = isInstance && "item" in value && value["item"] !== undefined; return isInstance; @@ -63,6 +78,7 @@ export function TrackActivityFromJSONTyped(json: any, ignoreDiscriminator: boole } return { ...ActivityFromJSONTyped(json, ignoreDiscriminator), + 'itemType': json['item_type'], 'item': TrackFromJSON(json['item']), }; } @@ -76,6 +92,7 @@ export function TrackActivityToJSON(value?: TrackActivity | null): any { } return { ...ActivityToJSON(value), + 'item_type': value.itemType, 'item': TrackToJSON(value.item), }; } diff --git a/packages/libs/src/sdk/api/generated/full/models/ActivityFull.ts b/packages/libs/src/sdk/api/generated/full/models/ActivityFull.ts index f15e6288863..82892b97468 100644 --- a/packages/libs/src/sdk/api/generated/full/models/ActivityFull.ts +++ b/packages/libs/src/sdk/api/generated/full/models/ActivityFull.ts @@ -16,6 +16,7 @@ import { exists, mapValues } from '../runtime'; import { CollectionActivityFullFromJSONTyped, + CollectionActivityFullWithoutTracksFromJSONTyped, TrackActivityFullFromJSONTyped } from './'; @@ -87,6 +88,9 @@ export function ActivityFullFromJSONTyped(json: any, ignoreDiscriminator: boolea if (json['_class'] === 'collection_activity_full') { return CollectionActivityFullFromJSONTyped(json, true); } + if (json['_class'] === 'collection_activity_full_without_tracks') { + return CollectionActivityFullWithoutTracksFromJSONTyped(json, true); + } if (json['_class'] === 'track_activity_full') { return TrackActivityFullFromJSONTyped(json, true); } diff --git a/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFull.ts b/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFull.ts index 98daa4eb4cf..902c4158c3f 100644 --- a/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFull.ts +++ b/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFull.ts @@ -33,6 +33,12 @@ import { * @interface CollectionActivityFull */ export interface CollectionActivityFull extends ActivityFull { + /** + * + * @type {string} + * @memberof CollectionActivityFull + */ + itemType: CollectionActivityFullItemTypeEnum; /** * * @type {PlaylistFull} @@ -42,12 +48,21 @@ export interface CollectionActivityFull extends ActivityFull { } +/** + * @export + */ +export const CollectionActivityFullItemTypeEnum = { + Playlist: 'playlist' +} as const; +export type CollectionActivityFullItemTypeEnum = typeof CollectionActivityFullItemTypeEnum[keyof typeof CollectionActivityFullItemTypeEnum]; + /** * Check if a given object implements the CollectionActivityFull interface. */ export function instanceOfCollectionActivityFull(value: object): value is CollectionActivityFull { let isInstance = true; + isInstance = isInstance && "itemType" in value && value["itemType"] !== undefined; isInstance = isInstance && "item" in value && value["item"] !== undefined; return isInstance; @@ -63,6 +78,7 @@ export function CollectionActivityFullFromJSONTyped(json: any, ignoreDiscriminat } return { ...ActivityFullFromJSONTyped(json, ignoreDiscriminator), + 'itemType': json['item_type'], 'item': PlaylistFullFromJSON(json['item']), }; } @@ -76,6 +92,7 @@ export function CollectionActivityFullToJSON(value?: CollectionActivityFull | nu } return { ...ActivityFullToJSON(value), + 'item_type': value.itemType, 'item': PlaylistFullToJSON(value.item), }; } diff --git a/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFullWithoutTracks.ts b/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFullWithoutTracks.ts index 8301f739803..f8ba35a4362 100644 --- a/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFullWithoutTracks.ts +++ b/packages/libs/src/sdk/api/generated/full/models/CollectionActivityFullWithoutTracks.ts @@ -14,6 +14,12 @@ */ import { exists, mapValues } from '../runtime'; +import type { ActivityFull } from './ActivityFull'; +import { + ActivityFullFromJSON, + ActivityFullFromJSONTyped, + ActivityFullToJSON, +} from './ActivityFull'; import type { PlaylistFullWithoutTracks } from './PlaylistFullWithoutTracks'; import { PlaylistFullWithoutTracksFromJSON, @@ -26,13 +32,7 @@ import { * @export * @interface CollectionActivityFullWithoutTracks */ -export interface CollectionActivityFullWithoutTracks { - /** - * - * @type {string} - * @memberof CollectionActivityFullWithoutTracks - */ - timestamp: string; +export interface CollectionActivityFullWithoutTracks extends ActivityFull { /** * * @type {string} @@ -62,7 +62,6 @@ export type CollectionActivityFullWithoutTracksItemTypeEnum = typeof CollectionA */ export function instanceOfCollectionActivityFullWithoutTracks(value: object): value is CollectionActivityFullWithoutTracks { let isInstance = true; - isInstance = isInstance && "timestamp" in value && value["timestamp"] !== undefined; isInstance = isInstance && "itemType" in value && value["itemType"] !== undefined; isInstance = isInstance && "item" in value && value["item"] !== undefined; @@ -78,8 +77,7 @@ export function CollectionActivityFullWithoutTracksFromJSONTyped(json: any, igno return json; } return { - - 'timestamp': json['timestamp'], + ...ActivityFullFromJSONTyped(json, ignoreDiscriminator), 'itemType': json['item_type'], 'item': PlaylistFullWithoutTracksFromJSON(json['item']), }; @@ -93,8 +91,7 @@ export function CollectionActivityFullWithoutTracksToJSON(value?: CollectionActi return null; } return { - - 'timestamp': value.timestamp, + ...ActivityFullToJSON(value), 'item_type': value.itemType, 'item': PlaylistFullWithoutTracksToJSON(value.item), }; diff --git a/packages/libs/src/sdk/api/generated/full/models/TrackActivityFull.ts b/packages/libs/src/sdk/api/generated/full/models/TrackActivityFull.ts index 1f4c762ed54..edf963ef438 100644 --- a/packages/libs/src/sdk/api/generated/full/models/TrackActivityFull.ts +++ b/packages/libs/src/sdk/api/generated/full/models/TrackActivityFull.ts @@ -33,6 +33,12 @@ import { * @interface TrackActivityFull */ export interface TrackActivityFull extends ActivityFull { + /** + * + * @type {string} + * @memberof TrackActivityFull + */ + itemType: TrackActivityFullItemTypeEnum; /** * * @type {TrackFull} @@ -42,12 +48,21 @@ export interface TrackActivityFull extends ActivityFull { } +/** + * @export + */ +export const TrackActivityFullItemTypeEnum = { + Track: 'track' +} as const; +export type TrackActivityFullItemTypeEnum = typeof TrackActivityFullItemTypeEnum[keyof typeof TrackActivityFullItemTypeEnum]; + /** * Check if a given object implements the TrackActivityFull interface. */ export function instanceOfTrackActivityFull(value: object): value is TrackActivityFull { let isInstance = true; + isInstance = isInstance && "itemType" in value && value["itemType"] !== undefined; isInstance = isInstance && "item" in value && value["item"] !== undefined; return isInstance; @@ -63,6 +78,7 @@ export function TrackActivityFullFromJSONTyped(json: any, ignoreDiscriminator: b } return { ...ActivityFullFromJSONTyped(json, ignoreDiscriminator), + 'itemType': json['item_type'], 'item': TrackFullFromJSON(json['item']), }; } @@ -76,6 +92,7 @@ export function TrackActivityFullToJSON(value?: TrackActivityFull | null): any { } return { ...ActivityFullToJSON(value), + 'item_type': value.itemType, 'item': TrackFullToJSON(value.item), }; } diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index f9e4915c0da..b87e8140f81 100755 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -113,7 +113,7 @@ android { // versionCode is automatically incremented in CI versionCode 1 // Make sure this is above the currently released Android version in the play store if your changes touch native code: - versionName "1.1.436" + versionName "1.1.437" resValue "string", "build_config_package", "co.audius.app" resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis()) resConfigs "en" diff --git a/packages/mobile/ios/AudiusReactNative/Info.plist b/packages/mobile/ios/AudiusReactNative/Info.plist index 165e17c02f9..97fce7fa93a 100644 --- a/packages/mobile/ios/AudiusReactNative/Info.plist +++ b/packages/mobile/ios/AudiusReactNative/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.108 + 1.1.109 CFBundleSignature ???? CFBundleURLTypes diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 9ed79f88ada..96ae05d193b 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@audius/mobile", - "version": "1.5.91", + "version": "1.5.92", "private": true, "scripts": { "android:dev": "ENVFILE=.env.dev turbo run android -- --mode=prodDebug", diff --git a/packages/mobile/src/app/App.tsx b/packages/mobile/src/app/App.tsx index 01a51b7812b..39f2e086516 100644 --- a/packages/mobile/src/app/App.tsx +++ b/packages/mobile/src/app/App.tsx @@ -93,6 +93,8 @@ const App = () => { + + diff --git a/packages/mobile/src/components/core/DateTimeInput.tsx b/packages/mobile/src/components/core/DateTimeInput.tsx index 2cca3e917e8..5aee34d5c4c 100644 --- a/packages/mobile/src/components/core/DateTimeInput.tsx +++ b/packages/mobile/src/components/core/DateTimeInput.tsx @@ -1,10 +1,9 @@ -import { useCallback } from 'react' +import { useCallback, useState } from 'react' import dayjs from 'dayjs' import { Pressable } from 'react-native' import type { ReactNativeModalDateTimePickerProps } from 'react-native-modal-datetime-picker' import DateTimePickerModal from 'react-native-modal-datetime-picker' -import { useToggle } from 'react-use' import { TextInput, useTheme } from '@audius/harmony-native' import type { TextInputProps } from '@audius/harmony-native' @@ -29,14 +28,22 @@ export const DateTimeInput = (props: DateTimeModalProps) => { dayjs(date).format(mode === 'date' ? 'M/D/YY' : 'h:mm A') } = props const { color, type } = useTheme() - const [isDateTimeOpen, toggleDateTimeOpen] = useToggle(false) + const [isDateTimeOpen, setIsDateTimeOpen] = useState(false) + + const openDateTime = useCallback(() => { + setIsDateTimeOpen(true) + }, [setIsDateTimeOpen]) + + const closeDateTime = useCallback(() => { + setIsDateTimeOpen(false) + }, [setIsDateTimeOpen]) const handleChange = useCallback( (date: Date) => { onChange(date.toString()) - toggleDateTimeOpen() + setIsDateTimeOpen((d) => !d) }, - [onChange, toggleDateTimeOpen] + [onChange, setIsDateTimeOpen] ) const dateProps: Partial = { @@ -48,7 +55,7 @@ export const DateTimeInput = (props: DateTimeModalProps) => { return ( <> - + { mode={mode} isVisible={isDateTimeOpen} onConfirm={handleChange} - onCancel={toggleDateTimeOpen} + onCancel={closeDateTime} {...(mode === 'date' ? dateProps : {})} /> diff --git a/packages/mobile/src/components/details-tile/CollectionMetadataList.tsx b/packages/mobile/src/components/details-tile/CollectionMetadataList.tsx index 992a02d5227..b4b35fdb914 100644 --- a/packages/mobile/src/components/details-tile/CollectionMetadataList.tsx +++ b/packages/mobile/src/components/details-tile/CollectionMetadataList.tsx @@ -1,7 +1,7 @@ import { useCollectionMetadata } from '@audius/common/hooks' import type { ID } from '@audius/common/models' -import { Flex } from '@audius/harmony-native' +import { Flex, Text } from '@audius/harmony-native' import { MetadataItem } from './MetadataItem' @@ -21,7 +21,9 @@ export const CollectionMetadataList = ({ {metadataItems.map(({ label, id, value }) => ( - {value} + + {value} + ))} diff --git a/packages/mobile/src/components/details-tile/MetadataItem.tsx b/packages/mobile/src/components/details-tile/MetadataItem.tsx index 736cd71668a..30a38db0ef4 100644 --- a/packages/mobile/src/components/details-tile/MetadataItem.tsx +++ b/packages/mobile/src/components/details-tile/MetadataItem.tsx @@ -12,9 +12,7 @@ export const MetadataItem = ({ label, children }: MetadataItemProps) => { {label} - - {children} - + {children} ) } diff --git a/packages/mobile/src/components/details-tile/TrackMetadataList.tsx b/packages/mobile/src/components/details-tile/TrackMetadataList.tsx index acba6944c52..d560064b9f1 100644 --- a/packages/mobile/src/components/details-tile/TrackMetadataList.tsx +++ b/packages/mobile/src/components/details-tile/TrackMetadataList.tsx @@ -20,27 +20,36 @@ const messages = { const renderMood = (mood: string, onPress: () => void) => { return ( - - - + + + + + {mood} - - - - + + + ) } const renderFilterLink = (value: string, onPress: () => void) => { return ( - - + + {value} - - + + + ) +} + +const renderText = (value: string) => { + return ( + + {value} + ) } @@ -67,7 +76,7 @@ const renderMetadataValue = ( }) }) default: - return value + return renderText(value) } } @@ -105,9 +114,11 @@ export const TrackMetadataList = ({ trackId }: TrackMetadataListProps) => { ))} {albumInfo ? ( - - {albumInfo.playlist_name} - + + + {albumInfo.playlist_name} + + ) : null} diff --git a/packages/mobile/src/components/edit/ExpandableRadioGroup.tsx b/packages/mobile/src/components/edit/ExpandableRadioGroup.tsx index 44437b2634d..9aceae11c02 100644 --- a/packages/mobile/src/components/edit/ExpandableRadioGroup.tsx +++ b/packages/mobile/src/components/edit/ExpandableRadioGroup.tsx @@ -16,7 +16,7 @@ export const ExpandableRadioGroup = (props: ExpandableRadioGroupProps) => { {Children.map(children, (child, index) => ( <> {child} - {index !== childCount - 1 ? : null} + {child && index !== childCount - 1 ? : null} ))} diff --git a/packages/mobile/src/components/edit/PriceAndAudienceField/GollectibleGatedRadioField.tsx b/packages/mobile/src/components/edit/PriceAndAudienceField/GollectibleGatedRadioField.tsx index 0f530dc43df..e17e9b22946 100644 --- a/packages/mobile/src/components/edit/PriceAndAudienceField/GollectibleGatedRadioField.tsx +++ b/packages/mobile/src/components/edit/PriceAndAudienceField/GollectibleGatedRadioField.tsx @@ -49,7 +49,7 @@ export const CollectibleGatedRadioField = ( const hasNoCollectibles = useHasNoCollectibles() - const { set: setTrackAvailabilityFields } = useSetEntityAvailabilityFields() + const setFields = useSetEntityAvailabilityFields() const [{ value: streamConditions }] = useField>('stream_conditions') const [selectedNFTCollection, setSelectedNFTCollection] = useState( @@ -61,16 +61,14 @@ export const CollectibleGatedRadioField = ( // Update nft collection gate when availability selection changes useEffect(() => { if (selected) { - setTrackAvailabilityFields( - { - is_stream_gated: true, - stream_conditions: { nft_collection: selectedNFTCollection }, - 'field_visibility.remixes': false - }, - true - ) + setFields({ + is_stream_gated: true, + stream_conditions: { nft_collection: selectedNFTCollection }, + preview_start_seconds: null, + 'field_visibility.remixes': false + }) } - }, [selected, selectedNFTCollection, setTrackAvailabilityFields]) + }, [selected, selectedNFTCollection, setFields]) // Update nft collection gate when nft collection selection changes useEffect(() => { diff --git a/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/BoxedTextField.tsx b/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/BoxedTextField.tsx index 36ee4d2fb8e..2e65f9fd3f5 100644 --- a/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/BoxedTextField.tsx +++ b/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/BoxedTextField.tsx @@ -52,6 +52,7 @@ export const BoxedTextField = (props: BoxedTextFieldProps) => { diff --git a/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/PremiumRadioField.tsx b/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/PremiumRadioField.tsx index 9fb8860aa59..8c1ce1fa0fa 100644 --- a/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/PremiumRadioField.tsx +++ b/packages/mobile/src/components/edit/PriceAndAudienceField/PremiumRadioField/PremiumRadioField.tsx @@ -27,7 +27,7 @@ const { premiumRadio: messages } = priceAndAudienceMessages export const PremiumRadioField = (props: PremiumRadioFieldProps) => { const { disabled, previousStreamConditions } = props - const { set: setFields } = useSetEntityAvailabilityFields() + const setFields = useSetEntityAvailabilityFields() const { value } = useContext(RadioGroupContext) const selected = value === StreamTrackAvailabilityType.USDC_PURCHASE @@ -40,7 +40,7 @@ export const PremiumRadioField = (props: PremiumRadioFieldProps) => { }, [previousStreamConditions]) const [{ value: preview }] = useField(TRACK_PREVIEW) - const previewStartSeconds = useRef(preview ?? 0).current + const previewStartSeconds = useRef(preview ?? null).current const [{ value: entityType }] = useField('entityType') useEffect(() => { @@ -49,7 +49,7 @@ export const PremiumRadioField = (props: PremiumRadioFieldProps) => { is_stream_gated: true, // @ts-ignore fully formed in saga (validated + added splits) stream_conditions: { usdc_purchase: selectedUsdcPurchaseValue }, - preview_start_seconds: previewStartSeconds, + preview_start_seconds: previewStartSeconds ?? 0, 'field_visibility.remixes': false }) } diff --git a/packages/mobile/src/components/edit/PriceAndAudienceField/PriceAndAudienceScreen.tsx b/packages/mobile/src/components/edit/PriceAndAudienceField/PriceAndAudienceScreen.tsx index 45420f4500e..6929cede3f6 100644 --- a/packages/mobile/src/components/edit/PriceAndAudienceField/PriceAndAudienceScreen.tsx +++ b/packages/mobile/src/components/edit/PriceAndAudienceField/PriceAndAudienceScreen.tsx @@ -42,13 +42,15 @@ export const PriceAndAudienceScreen = () => { useField('is_stream_gated') const [{ value: streamConditions }, , { setValue: setStreamConditions }] = useField>('stream_conditions') - const [{ value: isUnlisted }] = useField('is_unlisted') + const [, , { setValue: setPreviewValue }] = useField>( + 'preview_start_seconds' + ) const [{ value: isScheduledRelease }] = useField( 'is_scheduled_release' ) const [{ value: remixOf }] = useField('remix_of') - const [{ value: isUpload }] = useField('is_upload') const [{ value: entityType }] = useField('entityType') + const [{ value: isUpload }] = useField('isUpload') const isRemix = !!remixOf const { isEnabled: isEditableAccessEnabled } = useFeatureFlag( @@ -75,9 +77,6 @@ export const PriceAndAudienceScreen = () => { ) { return StreamTrackAvailabilityType.SPECIAL_ACCESS } - if (isUnlisted && !isScheduledRelease) { - return StreamTrackAvailabilityType.HIDDEN - } return StreamTrackAvailabilityType.PUBLIC // we only care about what the initial value was here // eslint-disable-next-line @@ -192,8 +191,9 @@ export const PriceAndAudienceScreen = () => { icon={IconCart} variant='white' disableSubmit={isFormInvalid} - stopNavigation={usersMayLoseAccess} + stopNavigation={!isUpload && usersMayLoseAccess} onSubmit={handleSubmit} + revertOnCancel > {isRemix ? {messages.markedAsRemix} : null} { onValueChange={() => { setIsStreamGated(false) setStreamConditions(null) + setPreviewValue(null) }} /> { if (selected && selectedSpecialAccessGate) { - setTrackAvailabilityFields( - { - is_stream_gated: true, - stream_conditions: selectedSpecialAccessGate, - 'field_visibility.remixes': false - }, - true - ) + setFields({ + is_stream_gated: true, + stream_conditions: selectedSpecialAccessGate, + preview_start_seconds: null, + 'field_visibility.remixes': false + }) } - }, [selected, selectedSpecialAccessGate, setTrackAvailabilityFields]) + }, [selected, selectedSpecialAccessGate, setFields]) const handleInfoPress = useCallback(() => { dispatch(setVisibility({ drawer: 'SupportersInfo', visible: true })) diff --git a/packages/mobile/src/components/edit/VisibilityField/VisibilityScreen.tsx b/packages/mobile/src/components/edit/VisibilityField/VisibilityScreen.tsx index 7c9a8771b83..73a563f5aaa 100644 --- a/packages/mobile/src/components/edit/VisibilityField/VisibilityScreen.tsx +++ b/packages/mobile/src/components/edit/VisibilityField/VisibilityScreen.tsx @@ -41,7 +41,7 @@ export const VisibilityScreen = () => { const initiallyPublic = !isUpload && !initialValues[hiddenKey] const initialVisibilityType = - is_scheduled_release && release_date + is_scheduled_release && isHidden ? 'scheduled' : isHidden ? 'hidden' @@ -123,7 +123,8 @@ export const VisibilityScreen = () => { description={messages.hiddenDescription} disabled={!isEditableAccessEnabled && initiallyPublic} /> - {!initiallyPublic && isPaidScheduledEnabled ? ( + {!initiallyPublic && + (entityType === 'track' || isPaidScheduledEnabled) ? ( ({ @@ -46,6 +47,7 @@ export const TextField = (props: TextFieldProps) => { id, onChangeText, error: errorProp, + errorBeforeSubmit, debouncedValidationMs = 0, ...other } = props @@ -72,12 +74,20 @@ export const TextField = (props: TextFieldProps) => { }, [debouncedValidationMs, debouncedValidateField, name, value]) const label = required ? `${labelProp} *` : labelProp - const hasError = (errorProp ?? errorMessage) && touched && submitCount > 0 + const hasError = + (errorProp ?? errorMessage) && + touched && + (errorBeforeSubmit || submitCount > 0) const handleChangeText = useCallback( (text: string) => { - onChangeText?.(text) - onChange(name)(text) + // onChangeText overrides onChange + // If you use it, you must call setValue manually + if (onChangeText) { + onChangeText(text) + } else { + onChange(name)(text) + } if (touched) { setTouched(false) } diff --git a/packages/mobile/src/components/lineup-tile/GatedTrackLabel.tsx b/packages/mobile/src/components/lineup-tile/GatedTrackLabel.tsx new file mode 100644 index 00000000000..e685e53607c --- /dev/null +++ b/packages/mobile/src/components/lineup-tile/GatedTrackLabel.tsx @@ -0,0 +1,91 @@ +import { useContext } from 'react' + +import { useGetCurrentUserId, useGetTrackById } from '@audius/common/api' +import type { ID } from '@audius/common/models' +import { + isContentCollectibleGated, + isContentFollowGated, + isContentTipGated, + isContentUSDCPurchaseGated +} from '@audius/common/models' +import type { Maybe } from '@audius/common/utils' + +import type { IconColors, IconComponent } from '@audius/harmony-native' +import { + IconCart, + IconCollectible, + IconReceive, + IconSpecialAccess +} from '@audius/harmony-native' +import { SearchContext } from 'app/screens/search-screen-v2/searchState' + +import { LineupTileLabel } from './LineupTileLabel' + +const messages = { + collectibleGated: 'Collectible Gated', + specialAccess: 'Special Access', + premium: 'Premium', + premiumExtras: 'Extras' +} + +type GatedTrackLabelProps = { + trackId: ID +} + +export const GatedTrackLabel = (props: GatedTrackLabelProps) => { + const { trackId } = props + const { data: track } = useGetTrackById({ id: trackId }) + const { data: currentUserId } = useGetCurrentUserId({}) + const { active: onSearchScreen } = useContext(SearchContext) + + if (!track) return null + + const { + is_stream_gated, + is_download_gated, + stream_conditions, + download_conditions, + owner_id + } = track + + const isOwner = owner_id === currentUserId + + let message: Maybe + let Icon: Maybe + let color: Maybe + + if (is_stream_gated) { + if ( + isContentFollowGated(stream_conditions) || + isContentTipGated(stream_conditions) + ) { + message = messages.specialAccess + Icon = IconSpecialAccess + color = 'special' + } else if (isContentCollectibleGated(stream_conditions)) { + message = messages.collectibleGated + Icon = IconCollectible + color = 'special' + } else if (isContentUSDCPurchaseGated(stream_conditions)) { + message = messages.premium + Icon = IconCart + color = 'premium' + } + } else if (is_download_gated && onSearchScreen) { + if (isContentUSDCPurchaseGated(download_conditions)) { + message = messages.premiumExtras + Icon = IconReceive + color = 'premium' + } + } + + if (!message || !Icon || !color) { + return null + } + + return ( + + {message} + + ) +} diff --git a/packages/mobile/src/components/lineup-tile/LineupTile.tsx b/packages/mobile/src/components/lineup-tile/LineupTile.tsx index 789e004bb90..32b70a9caf3 100644 --- a/packages/mobile/src/components/lineup-tile/LineupTile.tsx +++ b/packages/mobile/src/components/lineup-tile/LineupTile.tsx @@ -71,6 +71,7 @@ export const LineupTile = ({ const isCollection = 'playlist_id' in item const isAlbum = 'is_album' in item && item.is_album const isTrack = 'track_id' in item + const contentType = isTrack ? 'track' : isAlbum ? 'album' : 'playlist' const contentId = isTrack ? item.track_id : item.playlist_id const streamConditions = item.stream_conditions ?? null const isArtistPick = artist_pick_track_id === id @@ -120,7 +121,7 @@ export const LineupTile = ({ title={title} user={user} isPlayingUid={isPlayingUid} - type={isTrack ? 'track' : isAlbum ? 'album' : 'playlist'} + type={contentType} /> {coSign ? : null} {children} diff --git a/packages/mobile/src/components/lineup-tile/LineupTilePremiumContentTypeTag.tsx b/packages/mobile/src/components/lineup-tile/LineupTileGatedContentLabel.tsx similarity index 57% rename from packages/mobile/src/components/lineup-tile/LineupTilePremiumContentTypeTag.tsx rename to packages/mobile/src/components/lineup-tile/LineupTileGatedContentLabel.tsx index a1755278f60..fb5a1c501a1 100644 --- a/packages/mobile/src/components/lineup-tile/LineupTilePremiumContentTypeTag.tsx +++ b/packages/mobile/src/components/lineup-tile/LineupTileGatedContentLabel.tsx @@ -6,18 +6,15 @@ import { isContentCollectibleGated, isContentUSDCPurchaseGated } from '@audius/common/models' -import { View } from 'react-native' import { IconCart, IconCollectible, IconSpecialAccess } from '@audius/harmony-native' -import { Text } from 'app/components/core' import { useIsUSDCEnabled } from 'app/hooks/useIsUSDCEnabled' -import { makeStyles, flexRowCentered } from 'app/styles' -import { spacing } from 'app/styles/spacing' -import { useThemeColors } from 'app/utils/theme' + +import { LineupTileLabel } from './LineupTileLabel' const messages = { collectibleGated: 'Collectible Gated', @@ -25,14 +22,7 @@ const messages = { premium: 'Premium' } -const useStyles = makeStyles(({ spacing, palette, typography }) => ({ - root: { - ...flexRowCentered(), - gap: spacing(1) - } -})) - -type LineupTileGatedContentTypeTagProps = { +type LineupTileGatedContentLabelProps = { streamConditions: AccessConditions hasStreamAccess?: boolean isOwner: boolean @@ -44,13 +34,10 @@ type LineupTileGatedContentTypeTagProps = { * @param hasStreamAccess whether the user has access to stream the track * @isOwner whether the user is the owner of the track */ -export const LineupTileGatedContentTypeTag = ({ - streamConditions, - hasStreamAccess, - isOwner -}: LineupTileGatedContentTypeTagProps) => { - const styles = useStyles() - const { accentBlue, neutralLight4, specialLightGreen } = useThemeColors() +export const LineupTileGatedContentLabel = ( + props: LineupTileGatedContentLabelProps +) => { + const { streamConditions, hasStreamAccess, isOwner } = props const isUSDCEnabled = useIsUSDCEnabled() const type = @@ -64,30 +51,27 @@ export const LineupTileGatedContentTypeTag = ({ return { [GatedContentType.COLLECTIBLE_GATED]: { icon: IconCollectible, - color: hasStreamAccess && !isOwner ? neutralLight4 : accentBlue, + color: hasStreamAccess && !isOwner ? 'subdued' : 'special', text: messages.collectibleGated - }, + } as const, [GatedContentType.SPECIAL_ACCESS]: { icon: IconSpecialAccess, - color: hasStreamAccess && !isOwner ? neutralLight4 : accentBlue, + color: hasStreamAccess && !isOwner ? 'subdued' : 'special', text: messages.specialAccess - }, + } as const, [GatedContentType.USDC_PURCHASE]: { icon: IconCart, - color: hasStreamAccess && !isOwner ? neutralLight4 : specialLightGreen, + color: hasStreamAccess && !isOwner ? 'subdued' : 'premium', text: messages.premium - } + } as const } - }, [accentBlue, hasStreamAccess, isOwner, neutralLight4, specialLightGreen]) + }, [hasStreamAccess, isOwner]) const { icon: Icon, color, text } = gatedContentTypeMap[type] return ( - - - - {text} - - + + {text} + ) } diff --git a/packages/mobile/src/components/lineup-tile/LineupTileLabel.tsx b/packages/mobile/src/components/lineup-tile/LineupTileLabel.tsx new file mode 100644 index 00000000000..44cb703db7a --- /dev/null +++ b/packages/mobile/src/components/lineup-tile/LineupTileLabel.tsx @@ -0,0 +1,20 @@ +import type { IconColors, IconComponent } from '@audius/harmony-native' +import { Flex, Text } from '@audius/harmony-native' + +type LineupTileLabelProps = { + icon?: IconComponent + children: string + color?: IconColors +} + +export const LineupTileLabel = (props: LineupTileLabelProps) => { + const { icon: Icon, children, color = 'subdued' } = props + return ( + + {Icon ? : null} + + {children} + + + ) +} diff --git a/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx b/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx index c0d7146a62a..e09e8f27739 100644 --- a/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx +++ b/packages/mobile/src/components/lineup-tile/LineupTileStats.tsx @@ -30,11 +30,11 @@ import { CollectionDownloadStatusIndicator } from 'app/components/offline-downlo import { TrackDownloadStatusIndicator } from 'app/components/offline-downloads/TrackDownloadStatusIndicator' import { useNavigation } from 'app/hooks/useNavigation' import { makeStyles, flexRowCentered } from 'app/styles' -import { spacing } from 'app/styles/spacing' -import { useThemeColors } from 'app/utils/theme' +import { GatedTrackLabel } from './GatedTrackLabel' import { LineupTileAccessStatus } from './LineupTileAccessStatus' -import { LineupTileGatedContentTypeTag } from './LineupTilePremiumContentTypeTag' +import { LineupTileGatedContentLabel } from './LineupTileGatedContentLabel' +import { LineupTileLabel } from './LineupTileLabel' import { LineupTileRankIcon } from './LineupTileRankIcon' import { useStyles as useTrackTileStyles } from './styles' import type { LineupItemVariant, LineupTileSource } from './types' @@ -59,7 +59,7 @@ const messages = { })} ${getLocalTimezone()}` } -const useStyles = makeStyles(({ spacing, palette }) => ({ +const useStyles = makeStyles(({ spacing }) => ({ root: { flexDirection: 'row', alignItems: 'stretch', @@ -85,18 +85,6 @@ const useStyles = makeStyles(({ spacing, palette }) => ({ }, disabledStatItem: { opacity: 0.5 - }, - favoriteStat: { - height: spacing(3.5), - width: spacing(3.5) - }, - repostStat: { - height: spacing(4), - width: spacing(4) - }, - tagContainer: { - ...flexRowCentered(), - gap: spacing(1) } })) @@ -121,6 +109,7 @@ type Props = { showArtistPick?: boolean releaseDate?: string source?: LineupTileSource + type: 'track' | 'album' | 'playlist' } export const LineupTileStats = ({ @@ -143,11 +132,11 @@ export const LineupTileStats = ({ isArtistPick, showArtistPick, releaseDate, - source + source, + type }: Props) => { const styles = useStyles() const trackTileStyles = useTrackTileStyles() - const { neutralLight4, accentPurple } = useThemeColors() const dispatch = useDispatch() const navigation = useNavigation() @@ -215,48 +204,30 @@ export const LineupTileStats = ({ {isTrending ? ( ) : null} - {!isUnlisted && streamConditions ? ( - - ) : null} - {!streamConditions && showArtistPick && isArtistPick ? ( - - + {messages.artistPick} + + ) : !isUnlisted ? ( + type === 'track' ? ( + + ) : streamConditions ? ( + - - {messages.artistPick} - - + ) : null ) : null} {isUnlisted && !isScheduledRelease ? ( - - - - {messages.hidden} - - + + {messages.hidden} + ) : null} {isUnlisted && isScheduledRelease && releaseDate ? ( - - - - {messages.releases(releaseDate)} - - + + {messages.releases(releaseDate)} + ) : null} {hasEngagement && !isUnlisted ? ( @@ -270,12 +241,7 @@ export const LineupTileStats = ({ disabled={!repostCount || isReadonly} onPress={handlePressReposts} > - + {formatCount(repostCount)} @@ -289,12 +255,7 @@ export const LineupTileStats = ({ disabled={!saveCount || isReadonly} onPress={handlePressFavorites} > - + {formatCount(saveCount)} diff --git a/packages/mobile/src/components/navigation-container/NavigationContainer.tsx b/packages/mobile/src/components/navigation-container/NavigationContainer.tsx index 2eb13af4afa..e76880c69f0 100644 --- a/packages/mobile/src/components/navigation-container/NavigationContainer.tsx +++ b/packages/mobile/src/components/navigation-container/NavigationContainer.tsx @@ -22,6 +22,7 @@ import { getPrimaryRoute } from 'app/utils/navigation' import { useThemeVariant } from 'app/utils/theme' import { navigationThemes } from './navigationThemes' + const { getAccountUser } = accountSelectors type NavigationContainerProps = { @@ -73,6 +74,18 @@ const createFeedStackState = (route): PartialState => ] }) +/** + * Convert query parameters to an object + */ +const convertQueryParamsToObject = (url: string) => { + // This baseUrl is unimportant, we just need to create a URL object + const baseUrl = 'https://audius.co' + + const urlObj = new URL(url, baseUrl) + const params = new URLSearchParams(urlObj.search) + return Object.fromEntries(params) +} + /** * NavigationContainer contains the react-navigation context * and configures linking @@ -271,6 +284,20 @@ const NavigationContainer = (props: NavigationContainerProps) => { }) } + // /search + if (path.match(`^/search(/|$)`)) { + const { query, ...filters } = convertQueryParamsToObject(path) + + return createFeedStackState({ + name: 'Search', + params: { + query, + category: pathPart(path)(2) ?? 'all', + filters + } + }) + } + const { query } = queryString.parseUrl(path) const { login, warning } = query diff --git a/packages/mobile/src/components/publish-content-drawer/PublishContentDrawer.tsx b/packages/mobile/src/components/publish-content-drawer/PublishContentDrawer.tsx index a5113ca46fc..5e3237dc12c 100644 --- a/packages/mobile/src/components/publish-content-drawer/PublishContentDrawer.tsx +++ b/packages/mobile/src/components/publish-content-drawer/PublishContentDrawer.tsx @@ -130,8 +130,10 @@ export const PublishContentDrawer = () => { const { key: dismissToastKey } = displayPublishToast() setDismissToastKey(dismissToastKey) - if (contentId && contentType === 'playlist') { - dispatch(publishPlaylist(contentId, dismissToastKey)) + if (contentId && (contentType === 'playlist' || contentType === 'album')) { + dispatch( + publishPlaylist(contentId, dismissToastKey, contentType === 'album') + ) } // Publish track if (contentId && contentType === 'track') { diff --git a/packages/mobile/src/harmony-native/components/Radio/Radio.tsx b/packages/mobile/src/harmony-native/components/Radio/Radio.tsx index 68faae24b4b..7739324646f 100644 --- a/packages/mobile/src/harmony-native/components/Radio/Radio.tsx +++ b/packages/mobile/src/harmony-native/components/Radio/Radio.tsx @@ -82,6 +82,7 @@ export const Radio = (props: RadioProps) => { onPress={onValueChange} style={[style, disabled && { opacity: 0.5 }]} disabled={disabled} + activeOpacity={Number(checked)} > diff --git a/packages/mobile/src/harmony-native/components/input/TextInput/TextInput.tsx b/packages/mobile/src/harmony-native/components/input/TextInput/TextInput.tsx index d876ae72faa..463a09cafdd 100644 --- a/packages/mobile/src/harmony-native/components/input/TextInput/TextInput.tsx +++ b/packages/mobile/src/harmony-native/components/input/TextInput/TextInput.tsx @@ -72,6 +72,7 @@ export const TextInput = forwardRef( _isFocused, _disablePointerEvents, style, + innerContainerStyle, ...other } = props @@ -260,7 +261,7 @@ export const TextInput = forwardRef( backgroundColor='surface1' ph={isSmall ? 'm' : 'l'} gap={isSmall ? 's' : 'm'} - style={animatedRootStyles} + style={[animatedRootStyles, innerContainerStyle]} > {StartIcon ? ( > = { customNavigation?: NavigationProp } +let lastNavAction: any +export const setLastNavAction = (action: any) => { + lastNavAction = action +} + /** * Custom wrapper around react-navigation `useNavigation` * diff --git a/packages/mobile/src/hooks/useSetTrackAvailabilityFields.ts b/packages/mobile/src/hooks/useSetTrackAvailabilityFields.ts index fd9cc2f49f7..1c86db38871 100644 --- a/packages/mobile/src/hooks/useSetTrackAvailabilityFields.ts +++ b/packages/mobile/src/hooks/useSetTrackAvailabilityFields.ts @@ -4,6 +4,19 @@ import type { AccessConditions } from '@audius/common/models' import type { Nullable } from '@audius/common/utils' import { useField } from 'formik' +type TrackAvailabilityField = { + is_stream_gated: boolean + stream_conditions: Nullable + is_unlisted: boolean + preview_start_seconds: Nullable + 'field_visibility.genre': boolean + 'field_visibility.mood': boolean + 'field_visibility.tags': boolean + 'field_visibility.share': boolean + 'field_visibility.play_count': boolean + 'field_visibility.remixes': boolean +} + // This hook allows us to set track availability fields during upload. // It has to be used with a Formik context because it uses formik's useField hook. export const useSetEntityAvailabilityFields = () => { @@ -12,10 +25,8 @@ export const useSetEntityAvailabilityFields = () => { useField('is_stream_gated') const [, , { setValue: setStreamConditions }] = useField>('stream_conditions') - const [{ value: isUnlisted }, , { setValue: setIsUnlisted }] = - useField(entityType === 'track' ? 'is_unlisted' : 'is_private') - const [{ value: isScheduledRelease }, ,] = useField( - 'is_scheduled_release' + const [, , { setValue: setIsUnlisted }] = useField( + entityType === 'track' ? 'is_unlisted' : 'is_private' ) const [, , { setValue: setPreviewStartSeconds }] = useField( 'preview_start_seconds' @@ -35,23 +46,6 @@ export const useSetEntityAvailabilityFields = () => { 'field_visibility.remixes' ) - const defaultTrackAvailabilityFields = useMemo( - () => ({ - is_stream_gated: false, - stream_conditions: null as Nullable, - is_unlisted: !!(isScheduledRelease && isUnlisted), // scheduled releases cannot be made public via access & sale - preview_start_seconds: null as Nullable, - 'field_visibility.genre': true, - 'field_visibility.mood': true, - 'field_visibility.tags': true, - 'field_visibility.share': true, - 'field_visibility.play_count': true, - 'field_visibility.remixes': true - }), - [isScheduledRelease, isUnlisted] - ) - type TrackAvailabilityField = typeof defaultTrackAvailabilityFields - const fieldSetters = useMemo(() => { return { is_stream_gated: setIsStreamGated, @@ -70,39 +64,16 @@ export const useSetEntityAvailabilityFields = () => { }, []) const set = useCallback( - ( - fieldValues: Partial, - resetOtherFields = false - ) => { + (fieldValues: Partial) => { const givenKeys = Object.keys(fieldValues) givenKeys.forEach((key) => { const value = fieldValues[key] const setter = fieldSetters[key] setter(value) }) - - if (resetOtherFields) { - const givenKeySet = new Set(givenKeys) - const otherKeys = Object.keys(defaultTrackAvailabilityFields).filter( - (key) => !givenKeySet.has(key) - ) - otherKeys.forEach((key) => { - const value = defaultTrackAvailabilityFields[key] - const setter = fieldSetters[key] - setter(value) - }) - } }, - [defaultTrackAvailabilityFields, fieldSetters] + [fieldSetters] ) - const reset = useCallback(() => { - Object.keys(defaultTrackAvailabilityFields).forEach((key) => { - const value = defaultTrackAvailabilityFields[key] - const setter = fieldSetters[key] - setter(value) - }) - }, [defaultTrackAvailabilityFields, fieldSetters]) - - return { set, reset } + return set } diff --git a/packages/mobile/src/screens/app-screen/AppScreen.tsx b/packages/mobile/src/screens/app-screen/AppScreen.tsx index 4f49beb4b1d..7284f9f4a0e 100644 --- a/packages/mobile/src/screens/app-screen/AppScreen.tsx +++ b/packages/mobile/src/screens/app-screen/AppScreen.tsx @@ -1,7 +1,11 @@ +import { useCallback } from 'react' + import { MobileOS } from '@audius/common/models' import { createNativeStackNavigator } from '@react-navigation/native-stack' import { Platform } from 'react-native' +import { setLastNavAction } from 'app/hooks/useNavigation' + import { ChangeEmailModalScreen } from '../change-email-screen/ChangeEmailScreen' import { ChangePasswordModalScreen } from '../change-password-screen' import { EditCollectionScreen } from '../edit-collection-screen' @@ -16,8 +20,21 @@ import { AppTabsScreen } from './AppTabsScreen' const Stack = createNativeStackNavigator() export const AppScreen = () => { + /** + * Reset lastNavAction on transitionEnd + * Need to do this via screenListeners on the Navigator because listening + * via navigation.addListener inside a screen does not always + * catch events from other screens + */ + const handleTransitionEnd = useCallback(() => { + setLastNavAction(undefined) + }, []) + return ( - + { } /> - + } -export let lastNavAction: any -export const setLastNavAction = (action: any) => { - lastNavAction = action -} - /** * This is the base tab screen that includes common screens * like track and profile @@ -185,7 +181,7 @@ export const AppTabScreen = ({ baseScreen, Stack }: AppTabScreenProps) => { * catch events from other screens */ const handleTransitionEnd = useCallback(() => { - lastNavAction = undefined + setLastNavAction(undefined) }, []) useEffect(() => { diff --git a/packages/mobile/src/screens/app-screen/useAppScreenOptions.tsx b/packages/mobile/src/screens/app-screen/useAppScreenOptions.tsx index 362920f5e36..97adb1e5e3b 100644 --- a/packages/mobile/src/screens/app-screen/useAppScreenOptions.tsx +++ b/packages/mobile/src/screens/app-screen/useAppScreenOptions.tsx @@ -82,7 +82,7 @@ export const useAppScreenOptions = ( const handlePressSearch = useCallback(() => { dispatch(clearSearch()) - navigation.navigate('Search', {}) + navigation.navigate('Search', { autoFocus: true }) }, [dispatch, navigation]) const { isEnabled: isEarlyAccess } = useFeatureFlag(FeatureFlags.EARLY_ACCESS) @@ -114,7 +114,7 @@ export const useAppScreenOptions = ( color='subdued' size='l' {...other} - onPress={navigation.goBack} + onPress={() => navigation.pop()} /> ) diff --git a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx index 4d612aaec9b..17d43009a06 100644 --- a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx +++ b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx @@ -210,13 +210,11 @@ const CollectionScreenComponent = (props: CollectionScreenComponentProps) => { }, [navigation, playlist_id]) const handlePressPublish = useCallback(() => { - dispatch( - openPublishConfirmation({ - contentId: playlist_id, - contentType: is_album ? 'album' : 'playlist' - }) - ) - }, [dispatch, is_album, openPublishConfirmation, playlist_id]) + openPublishConfirmation({ + contentId: playlist_id, + contentType: is_album ? 'album' : 'playlist' + }) + }, [is_album, openPublishConfirmation, playlist_id]) const handlePressSave = useCallback(() => { if (has_current_user_saved) { diff --git a/packages/mobile/src/screens/edit-collection-screen/EditCollectionForm.tsx b/packages/mobile/src/screens/edit-collection-screen/EditCollectionForm.tsx index a0e0ae563dd..a63751454be 100644 --- a/packages/mobile/src/screens/edit-collection-screen/EditCollectionForm.tsx +++ b/packages/mobile/src/screens/edit-collection-screen/EditCollectionForm.tsx @@ -1,7 +1,11 @@ -import { useCallback } from 'react' +import { useCallback, useState } from 'react' import type { EditCollectionValues } from '@audius/common/store' -import { deletePlaylistConfirmationModalUIActions } from '@audius/common/store' +import { + deletePlaylistConfirmationModalUIActions, + modalsActions +} from '@audius/common/store' +import type { Nullable } from '@audius/common/utils' import { useField, type FormikProps } from 'formik' import { capitalize } from 'lodash' import { View } from 'react-native' @@ -20,6 +24,7 @@ import { useNavigation } from 'app/hooks/useNavigation' import { makeStyles } from 'app/styles' import { TopBarIconButton } from '../app-screen' +import { ConfirmPublishTrackDrawer } from '../edit-track-screen/components/ConfirmPublishDrawer' import { FormScreen } from '../page-form-screen' import { AdvancedAlbumField } from './AdvancedAlbumField' @@ -76,10 +81,22 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ export const EditCollectionForm = ( props: FormikProps & { playlistId: number } ) => { - const { playlistId, handleSubmit, handleReset } = props + const { + playlistId, + initialValues, + values, + handleSubmit: handleSubmitProp, + handleReset + } = props const styles = useStyles() const navigation = useNavigation() const dispatch = useDispatch() + const initiallyHidden = initialValues.is_private + const isInitiallyScheduled = initialValues.is_scheduled_release + const usersMayLoseAccess = !initiallyHidden && values.is_private + const isToBePublished = initiallyHidden && !values.is_private + const [confirmDrawerType, setConfirmDrawerType] = + useState>(null) const [{ value: entityType }] = useField('entityType') const messages = getMessages(entityType) @@ -88,43 +105,82 @@ export const EditCollectionForm = ( dispatch(openDeletePlaylist({ playlistId })) }, [dispatch, playlistId]) - return ( - + const submitAndGoBack = useCallback(() => { + handleSubmitProp() + navigation.goBack() + }, [handleSubmitProp, navigation]) + + const handleSubmit = useCallback(() => { + const showConfirmDrawer = usersMayLoseAccess || isToBePublished + if (showConfirmDrawer) { + if (usersMayLoseAccess) { + setConfirmDrawerType('hidden') + } else if (isInitiallyScheduled) { + setConfirmDrawerType('early_release') + } else { + setConfirmDrawerType('release') } - > - - - - - - - - {entityType === 'album' ? : null} - {entityType === 'album' ? : null} - - - - - - - + dispatch( + modalsActions.setVisibility({ + modal: 'EditAccessConfirmation', + visible: true + }) + ) + } else { + submitAndGoBack() + } + }, [ + usersMayLoseAccess, + isToBePublished, + isInitiallyScheduled, + dispatch, + submitAndGoBack + ]) + + return ( + <> + + } + > + + + + + + + + {entityType === 'album' ? : null} + {entityType === 'album' ? : null} + + + + + + + + {confirmDrawerType ? ( + + ) : null} + ) } diff --git a/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx b/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx index 0e14a761584..6e28228ee65 100644 --- a/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx +++ b/packages/mobile/src/screens/edit-track-screen/EditTrackForm.tsx @@ -113,6 +113,7 @@ export const EditTrackForm = (props: EditTrackFormProps) => { }, [dirty, navigation, dispatch]) const handleSubmit = useCallback(() => { + Keyboard.dismiss() const showConfirmDrawer = usersMayLoseAccess || isToBePublished if (showConfirmDrawer) { if (usersMayLoseAccess) { diff --git a/packages/mobile/src/screens/edit-track-screen/EditTrackScreen.tsx b/packages/mobile/src/screens/edit-track-screen/EditTrackScreen.tsx index f59b9c9da82..740c2cf4abb 100644 --- a/packages/mobile/src/screens/edit-track-screen/EditTrackScreen.tsx +++ b/packages/mobile/src/screens/edit-track-screen/EditTrackScreen.tsx @@ -9,6 +9,7 @@ import type { import { creativeCommons, formatPrice, + isBpmValid, parseMusicalKey } from '@audius/common/utils' import { Formik } from 'formik' @@ -206,6 +207,14 @@ const useEditTrackSchema = () => { export type EditTrackParams = TrackForUpload +const getInitialBpm = (bpm: number | null | undefined) => { + if (bpm) { + const bpmString = bpm.toString() + return isBpmValid(bpmString) ? bpmString : undefined + } + return undefined +} + export const EditTrackScreen = (props: EditTrackScreenProps) => { const editTrackSchema = toFormikValidationSchema(useEditTrackSchema()) @@ -220,7 +229,7 @@ export const EditTrackScreen = (props: EditTrackScreenProps) => { musical_key: initialValuesProp.musical_key ? parseMusicalKey(initialValuesProp.musical_key) : undefined, - bpm: initialValuesProp.bpm ? initialValuesProp.bpm.toString() : undefined + bpm: getInitialBpm(initialValuesProp.bpm) } const handleSubmit = useCallback( diff --git a/packages/mobile/src/screens/edit-track-screen/components/ConfirmPublishDrawer.tsx b/packages/mobile/src/screens/edit-track-screen/components/ConfirmPublishDrawer.tsx index 49454382e88..fb2888f62c5 100644 --- a/packages/mobile/src/screens/edit-track-screen/components/ConfirmPublishDrawer.tsx +++ b/packages/mobile/src/screens/edit-track-screen/components/ConfirmPublishDrawer.tsx @@ -1,3 +1,5 @@ +import { Portal } from '@gorhom/portal' + import { ConfirmationDrawer } from 'app/components/drawers' const messages = { @@ -42,11 +44,13 @@ export const ConfirmPublishTrackDrawer = ( : messages.hidden return ( - + + + ) } diff --git a/packages/mobile/src/screens/edit-track-screen/components/EditPriceAndAudienceConfirmationDrawer.tsx b/packages/mobile/src/screens/edit-track-screen/components/EditPriceAndAudienceConfirmationDrawer.tsx index 13db262f4e4..210fca3d758 100644 --- a/packages/mobile/src/screens/edit-track-screen/components/EditPriceAndAudienceConfirmationDrawer.tsx +++ b/packages/mobile/src/screens/edit-track-screen/components/EditPriceAndAudienceConfirmationDrawer.tsx @@ -1,3 +1,5 @@ +import { Portal } from '@gorhom/portal' + import { ConfirmationDrawer } from 'app/components/drawers' const messages = { @@ -17,12 +19,14 @@ export const EditPriceAndAudienceConfirmationDrawer = ( props: EditPriceAndAudienceConfirmationDrawerProps ) => { return ( - + + + ) } diff --git a/packages/mobile/src/screens/edit-track-screen/components/HiddenAvailability.tsx b/packages/mobile/src/screens/edit-track-screen/components/HiddenAvailability.tsx deleted file mode 100644 index 072e1761063..00000000000 --- a/packages/mobile/src/screens/edit-track-screen/components/HiddenAvailability.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useEffect } from 'react' - -import { useField } from 'formik' -import { Dimensions, View } from 'react-native' - -import { Hint, IconVisibilityHidden } from '@audius/harmony-native' -import { Text } from 'app/components/core' -import { useSetEntityAvailabilityFields } from 'app/hooks/useSetTrackAvailabilityFields' -import { makeStyles } from 'app/styles' -import { useColor } from 'app/utils/theme' - -import { SwitchField } from '../fields' - -import type { TrackAvailabilitySelectionProps } from './types' - -const messages = { - hidden: 'Hidden', - hiddenSubtitle: - "Hidden tracks won't be visible to your followers. Only you will see them on your profile. Anyone who has the link will be able to listen.", - noHiddenHint: 'Scheduled tracks are hidden by default until release.', - hideTrack: 'Hide Track', - showGenre: 'Show Genre', - showMood: 'Show Mood', - showTags: 'Show Tags', - showShareButton: 'Show Share Button', - showPlayCount: 'Show Play Count' -} - -const screenWidth = Dimensions.get('screen').width - -const useStyles = makeStyles(({ spacing, palette }) => ({ - root: { - width: screenWidth - spacing(22) - }, - titleContainer: { - flexDirection: 'row', - alignItems: 'center' - }, - title: { - fontSize: 18, - marginTop: 0 - }, - selectedTitle: { - color: palette.secondary - }, - disabledTitle: { - color: palette.neutralLight4 - }, - titleIcon: { - marginTop: 0, - marginRight: spacing(2.5) - }, - subtitleContainer: { - marginTop: spacing(4), - marginLeft: -1 * spacing(10) - }, - subtitle: { - color: palette.neutral - }, - selectionContainer: { - marginLeft: spacing(-10), - marginTop: spacing(2), - padding: spacing(4), - backgroundColor: palette.neutralLight10, - borderWidth: 1, - borderColor: palette.neutralLight7, - borderRadius: spacing(2) - }, - firstSwitch: { - marginTop: 0 - }, - switch: { - marginTop: spacing(2) - } -})) - -export const HiddenAvailability = ({ - selected, - disabled = false, - isScheduledRelease -}: TrackAvailabilitySelectionProps) => { - const styles = useStyles() - const secondary = useColor('secondary') - const neutral = useColor('neutral') - const neutralLight4 = useColor('neutralLight4') - - const titleStyles: object[] = [styles.title] - if (selected) { - titleStyles.push(styles.selectedTitle) - } else if (disabled) { - titleStyles.push(styles.disabledTitle) - } - - const titleIconColor = selected - ? secondary - : disabled - ? neutralLight4 - : neutral - - const { set: setTrackAvailabilityFields } = useSetEntityAvailabilityFields() - const [{ value: isUnlisted }] = useField('is_unlisted') - - // If hidden was not previously selected, - // set hidden and reset other fields. - useEffect(() => { - if (!isUnlisted && selected) { - setTrackAvailabilityFields( - { - is_unlisted: true, - 'field_visibility.share': false, - 'field_visibility.play_count': false - }, - true - ) - } - }, [isUnlisted, selected, setTrackAvailabilityFields]) - - return ( - - - - - {messages.hidden} - - - {isUnlisted && isScheduledRelease ? ( - {messages.noHiddenHint} - ) : null} - {selected ? ( - <> - - - {messages.hiddenSubtitle} - - - - - - - - - - - ) : null} - - ) -} diff --git a/packages/mobile/src/screens/edit-track-screen/components/index.ts b/packages/mobile/src/screens/edit-track-screen/components/index.ts index e70afe007fb..72f3589d690 100644 --- a/packages/mobile/src/screens/edit-track-screen/components/index.ts +++ b/packages/mobile/src/screens/edit-track-screen/components/index.ts @@ -1,3 +1,2 @@ export * from './CancelEditTrackDrawer' export * from './RemixTrackPill' -export * from './HiddenAvailability' diff --git a/packages/mobile/src/screens/edit-track-screen/fields/KeyBpmField.tsx b/packages/mobile/src/screens/edit-track-screen/fields/KeyBpmField.tsx index 33a76c1b0f1..099fbbc20d3 100644 --- a/packages/mobile/src/screens/edit-track-screen/fields/KeyBpmField.tsx +++ b/packages/mobile/src/screens/edit-track-screen/fields/KeyBpmField.tsx @@ -14,7 +14,7 @@ export const KeyBpmField = (props: KeyBpmFieldProps) => { const [{ value: key }] = useField('musical_key') const [{ value: bpm }] = useField('bpm') - const formattedBpm = bpm ? `${bpm} BPM` : '' + const formattedBpm = bpm ? `${Number(bpm)} BPM` : '' const values = [key, formattedBpm].filter(removeNullable) return ( diff --git a/packages/mobile/src/screens/edit-track-screen/screens/AdvancedScreen.tsx b/packages/mobile/src/screens/edit-track-screen/screens/AdvancedScreen.tsx index 9dee42a7a8f..189c8a1ef52 100644 --- a/packages/mobile/src/screens/edit-track-screen/screens/AdvancedScreen.tsx +++ b/packages/mobile/src/screens/edit-track-screen/screens/AdvancedScreen.tsx @@ -29,7 +29,7 @@ export const AdvancedScreen = () => { - {isUnlisted ? null : } + {isUnlisted ? <> : } {isUpload ? <> : } diff --git a/packages/mobile/src/screens/edit-track-screen/screens/KeyBpmScreen.tsx b/packages/mobile/src/screens/edit-track-screen/screens/KeyBpmScreen.tsx index 03fc922a53c..903f956e4a1 100644 --- a/packages/mobile/src/screens/edit-track-screen/screens/KeyBpmScreen.tsx +++ b/packages/mobile/src/screens/edit-track-screen/screens/KeyBpmScreen.tsx @@ -1,3 +1,4 @@ +import { isBpmValid } from '@audius/common/utils' import { useField } from 'formik' import { View } from 'react-native' @@ -18,7 +19,13 @@ const messages = { } export const KeyBpmScreen = () => { - const [bpmField] = useField(BPM) + const [bpmField, , { setValue }] = useField(BPM) + + const handleChange = (value: string) => { + if (value === '' || isBpmValid(value)) { + setValue(value) + } + } return ( @@ -38,13 +45,14 @@ export const KeyBpmScreen = () => { diff --git a/packages/mobile/src/screens/edit-track-screen/screens/RemixSettingsScreen.tsx b/packages/mobile/src/screens/edit-track-screen/screens/RemixSettingsScreen.tsx index 6e1c62ba7c4..fdc987026c7 100644 --- a/packages/mobile/src/screens/edit-track-screen/screens/RemixSettingsScreen.tsx +++ b/packages/mobile/src/screens/edit-track-screen/screens/RemixSettingsScreen.tsx @@ -50,8 +50,7 @@ const messages = { remixAccessError: 'Must have access to the original track', enterLink: 'Enter an Audius Link', changeAvailbilityPrefix: 'Availablity is set to', - changeAvailbilitySuffix: - 'To enable these options, change availability to Public.', + changeAvailbilitySuffix: 'To enable these options, make your track free.', premium: 'Premium (Pay-To-Unlock). ', collectibleGated: 'Collectible Gated. ', specialAccess: 'Special Access. ' diff --git a/packages/mobile/src/screens/form-screen/FormScreen.tsx b/packages/mobile/src/screens/form-screen/FormScreen.tsx index 87ee4a06bb8..57e28f41333 100644 --- a/packages/mobile/src/screens/form-screen/FormScreen.tsx +++ b/packages/mobile/src/screens/form-screen/FormScreen.tsx @@ -1,5 +1,6 @@ import { useCallback, type ReactNode } from 'react' +import { Keyboard } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { Button, Flex, PlainButton } from '@audius/harmony-native' @@ -7,6 +8,8 @@ import type { ScreenProps } from 'app/components/core' import { Screen } from 'app/components/core' import { useNavigation } from 'app/hooks/useNavigation' +import { useRevertOnCancel } from './useRevertOnCancel' + const messages = { done: 'Done', clear: 'Clear' @@ -19,6 +22,7 @@ export type FormScreenProps = ScreenProps & { clearable?: boolean stopNavigation?: boolean disableSubmit?: boolean + revertOnCancel?: boolean } export const FormScreen = (props: FormScreenProps) => { @@ -31,18 +35,22 @@ export const FormScreen = (props: FormScreenProps) => { onSubmit, stopNavigation, disableSubmit, + revertOnCancel, ...other } = props const navigation = useNavigation() const insets = useSafeAreaInsets() const handleSubmit = useCallback(() => { + Keyboard.dismiss() if (!stopNavigation) { navigation.goBack() } onSubmit?.() }, [stopNavigation, navigation, onSubmit]) + useRevertOnCancel(revertOnCancel) + return ( { + const { values, setValues } = useFormikContext() ?? {} + const navigation = useNavigation() + + // eslint-disable-next-line react-hooks/exhaustive-deps + const initialValues = useMemo(() => values, []) + + useEffect(() => { + if (!active || !initialValues) return + const listener = navigation.addListener('beforeRemove', ({ data }) => { + if (data.action.type === 'POP') { + setValues(initialValues) + } + }) + + return () => { + navigation.removeListener('beforeRemove', listener) + } + }, [navigation, initialValues, setValues, active]) +} diff --git a/packages/mobile/src/screens/search-screen-v2/NoResultsTile.tsx b/packages/mobile/src/screens/search-screen-v2/NoResultsTile.tsx index ad8d0b9f79c..5ce7821bba4 100644 --- a/packages/mobile/src/screens/search-screen-v2/NoResultsTile.tsx +++ b/packages/mobile/src/screens/search-screen-v2/NoResultsTile.tsx @@ -13,7 +13,7 @@ export const NoResultsTile = () => { pv='2xl' ph='l' mv='s' - mh='xl' + mh='m' direction='column' gap='s' alignItems='center' diff --git a/packages/mobile/src/screens/search-screen-v2/SearchBarV2.tsx b/packages/mobile/src/screens/search-screen-v2/SearchBarV2.tsx index 94fa18d8b37..07cc623e437 100644 --- a/packages/mobile/src/screens/search-screen-v2/SearchBarV2.tsx +++ b/packages/mobile/src/screens/search-screen-v2/SearchBarV2.tsx @@ -1,8 +1,14 @@ -import { Dimensions, Pressable } from 'react-native' +import type { Ref } from 'react' +import { forwardRef } from 'react' + +import type { TextInput as RNTextInput } from 'react-native' +import { Dimensions } from 'react-native' import { + IconButton, IconCloseAlt, IconSearch, + spacing, TextInput, TextInputSize } from '@audius/harmony-native' @@ -15,29 +21,42 @@ const messages = { label: 'Search' } -export const SearchBarV2 = () => { - const [query, setQuery] = useSearchQuery() +type SearchBarV2Props = { + autoFocus?: boolean +} - const clearQuery = () => { - setQuery('') - } +export const SearchBarV2 = forwardRef( + ({ autoFocus = false }: SearchBarV2Props, ref: Ref) => { + const [query, setQuery] = useSearchQuery() - return ( - - query ? ( - - - - ) : null - } - size={TextInputSize.SMALL} - label={messages.label} - placeholder={messages.label} - style={{ width: searchBarWidth }} - value={query} - onChangeText={setQuery} - /> - ) -} + const clearQuery = () => { + setQuery('') + } + + return ( + + query ? ( + + ) : null + } + size={TextInputSize.SMALL} + label={messages.label} + placeholder={messages.label} + style={{ width: searchBarWidth }} + innerContainerStyle={query ? { paddingRight: spacing.s } : {}} + value={query} + onChangeText={setQuery} + /> + ) + } +) diff --git a/packages/mobile/src/screens/search-screen-v2/SearchCategoriesAndFilters.tsx b/packages/mobile/src/screens/search-screen-v2/SearchCategoriesAndFilters.tsx index c708a78dfbe..aef5eab2cb6 100644 --- a/packages/mobile/src/screens/search-screen-v2/SearchCategoriesAndFilters.tsx +++ b/packages/mobile/src/screens/search-screen-v2/SearchCategoriesAndFilters.tsx @@ -1,9 +1,10 @@ -import { useCallback } from 'react' +import { useCallback, useRef } from 'react' import type { SearchFilter, SearchCategory as SearchCategoryType } from '@audius/common/api' +import { useFocusEffect } from '@react-navigation/native' import { Image, ScrollView } from 'react-native' import { @@ -84,6 +85,14 @@ export const SearchCategoriesAndFilters = () => { const [category] = useSearchCategory() const [filters, setFilters] = useSearchFilters() + const scrollViewRef = useRef(null) + + useFocusEffect( + useCallback(() => { + scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: false }) + }, []) + ) + const handlePressFilter = useCallback( (filter: string) => { if (filterInfoMap[filter].screen) { @@ -142,7 +151,11 @@ export const SearchCategoriesAndFilters = () => { return ( - + diff --git a/packages/mobile/src/screens/search-screen-v2/SearchScreenV2.tsx b/packages/mobile/src/screens/search-screen-v2/SearchScreenV2.tsx index 932d0f26541..5ad5fe373a1 100644 --- a/packages/mobile/src/screens/search-screen-v2/SearchScreenV2.tsx +++ b/packages/mobile/src/screens/search-screen-v2/SearchScreenV2.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { SearchCategory, @@ -6,8 +6,11 @@ import type { } from '@audius/common/api' import { Kind } from '@audius/common/models' import { searchSelectors } from '@audius/common/store' +import { useFocusEffect } from '@react-navigation/native' import { createNativeStackNavigator } from '@react-navigation/native-stack' +import type { TextInput } from 'react-native/types' import { useSelector } from 'react-redux' +import { useEffectOnce } from 'react-use' import { Flex } from '@audius/harmony-native' import { Screen } from 'app/components/core' @@ -28,6 +31,7 @@ import { import { SearchResults } from './search-results/SearchResults' import { SearchContext, + useSearchAutoFocus, useSearchCategory, useSearchFilters, useSearchQuery @@ -48,6 +52,7 @@ export const SearchScreenV2 = () => { const [query] = useSearchQuery() const [category] = useSearchCategory() const [filters] = useSearchFilters() + const [autoFocus, setAutoFocus] = useSearchAutoFocus() const history = useSelector(getV2SearchHistory) const categoryKind: Kind | null = category ? itemKindByCategory[category] @@ -64,8 +69,28 @@ export const SearchScreenV2 = () => { const showSearchResults = query || Object.values(filters).some((filter) => filter) + const searchBarRef = useRef(null) + const [refsSet, setRefsSet] = useState(false) + + useEffectOnce(() => { + setRefsSet(true) + }) + + useFocusEffect( + useCallback(() => { + if (refsSet && autoFocus) { + setAutoFocus(false) + searchBarRef.current?.focus() + } + }, [autoFocus, refsSet, setAutoFocus]) + ) + return ( - } headerTitle={null} variant='white'> + } + headerTitle={null} + variant='white' + > {!showSearchResults ? ( @@ -88,20 +113,23 @@ export const SearchScreenV2 = () => { } export const SearchScreenStack = () => { - const { params = {} } = useRoute<'Search'>() - const [query, setQuery] = useState(params.query ?? '') + const { params } = useRoute<'Search'>() + + const [autoFocus, setAutoFocus] = useState(params?.autoFocus ?? false) + const [query, setQuery] = useState(params?.query ?? '') const [category, setCategory] = useState( - params.category ?? 'all' + params?.category ?? 'all' ) const [filters, setFilters] = useState( - params.filters ?? {} + params?.filters ?? {} ) const [bpmType, setBpmType] = useState<'range' | 'target'>('range') useEffect(() => { - setQuery(params.query ?? '') - setCategory(params.category ?? 'all') - setFilters(params.filters ?? {}) + setQuery(params?.query ?? '') + setCategory(params?.category ?? 'all') + setFilters(params?.filters ?? {}) + setAutoFocus(params?.autoFocus ?? false) }, [params]) const screenOptions = useAppScreenOptions() @@ -109,6 +137,8 @@ export const SearchScreenStack = () => { return ( { filters, setFilters, bpmType, - setBpmType + setBpmType, + active: true }} > diff --git a/packages/mobile/src/screens/search-screen-v2/searchParams.ts b/packages/mobile/src/screens/search-screen-v2/searchParams.ts index 3ba74ed0053..91d4f1151ca 100644 --- a/packages/mobile/src/screens/search-screen-v2/searchParams.ts +++ b/packages/mobile/src/screens/search-screen-v2/searchParams.ts @@ -4,4 +4,5 @@ export type SearchParams = { query?: string category?: SearchCategory filters?: SearchFilters + autoFocus?: boolean } diff --git a/packages/mobile/src/screens/search-screen-v2/searchState.ts b/packages/mobile/src/screens/search-screen-v2/searchState.ts index b9898bba514..de1d3ca1cf1 100644 --- a/packages/mobile/src/screens/search-screen-v2/searchState.ts +++ b/packages/mobile/src/screens/search-screen-v2/searchState.ts @@ -24,6 +24,9 @@ type SearchContextType = { setFilters: Dispatch> bpmType: string setBpmType: Dispatch> + autoFocus: boolean + setAutoFocus: Dispatch> + active: boolean } export const SearchContext = createContext({ @@ -35,7 +38,11 @@ export const SearchContext = createContext({ setFilters: (_) => {}, // Special state to track how bpm is being set bpmType: 'range', - setBpmType: (_) => {} + setBpmType: (_) => {}, + // Special state to determine if the search query input should be focused automatically + autoFocus: false, + setAutoFocus: (_) => {}, + active: false }) export const useIsEmptySearch = () => { @@ -48,6 +55,11 @@ export const useSearchQuery = () => { return [query, setQuery] as const } +export const useSearchAutoFocus = () => { + const { autoFocus, setAutoFocus } = useContext(SearchContext) + return [autoFocus, setAutoFocus] as const +} + export const useSearchBpmType = () => { const { bpmType, setBpmType } = useContext(SearchContext) return [bpmType, setBpmType] as const diff --git a/packages/web/package.json b/packages/web/package.json index 87c6b4fead7..e8c83b661d4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -3,7 +3,7 @@ "productName": "Audius", "description": "The Audius web client reference implementation", "author": "Audius", - "version": "1.5.91", + "version": "1.5.92", "private": true, "scripts": { "DEV & BUILD========================================": "", diff --git a/packages/web/src/common/store/cache/collections/commonSagas.ts b/packages/web/src/common/store/cache/collections/commonSagas.ts index bc5520fe867..c88b59c390e 100644 --- a/packages/web/src/common/store/cache/collections/commonSagas.ts +++ b/packages/web/src/common/store/cache/collections/commonSagas.ts @@ -155,11 +155,35 @@ function* editPlaylistAsync( ) // Optimistic update #2 to update the artwork + const playlistBeforeEdit = yield* select(getCollection, { id: playlistId }) yield* call(optimisticUpdateCollection, playlist) yield* call(confirmEditPlaylist, playlistId, userId, playlist) yield* put(collectionActions.editPlaylistSucceeded()) yield* put(toast({ content: messages.editToast })) + + if (playlistBeforeEdit?.is_private && !playlist.is_private) { + const playlistTracks = yield* select(getCollectionTracks, { + id: playlistId + }) + + // Publish all hidden tracks + // If the playlist is a scheduled release + // AND all tracks are scheduled releases, publish them all + const isEachTrackScheduled = playlistTracks?.every( + (track) => track.is_unlisted && track.is_scheduled_release + ) + const isEarlyRelease = + playlistBeforeEdit.is_scheduled_release && isEachTrackScheduled + for (const track of playlistTracks ?? []) { + if ( + track.is_unlisted && + (!track.is_scheduled_release || isEarlyRelease) + ) { + yield* put(trackPageActions.makeTrackPublic(track.track_id)) + } + } + } } function* confirmEditPlaylist( @@ -168,7 +192,6 @@ function* confirmEditPlaylist( formFields: Collection ) { const audiusBackendInstance = yield* getContext('audiusBackendInstance') - const playlistBeforeEdit = yield* select(getCollection, { id: playlistId }) yield* put( confirmerActions.requestConfirmation( makeKindId(Kind.COLLECTIONS, playlistId), @@ -211,29 +234,6 @@ function* confirmEditPlaylist( } ]) ) - - if (playlistBeforeEdit?.is_private && !confirmedPlaylist.is_private) { - const playlistTracks = yield* select(getCollectionTracks, { - id: playlistId - }) - - // Publish all hidden tracks - // If the playlist is a scheduled release - // AND all tracks are scheduled releases, publish them all - const isEachTrackScheduled = playlistTracks?.every( - (track) => track.is_unlisted && track.is_scheduled_release - ) - const isEarlyRelease = - playlistBeforeEdit.is_scheduled_release && isEachTrackScheduled - for (const track of playlistTracks ?? []) { - if ( - track.is_unlisted && - (!track.is_scheduled_release || isEarlyRelease) - ) { - yield* put(trackPageActions.makeTrackPublic(track.track_id)) - } - } - } }, function* ({ error, timeout, message }) { yield* put( @@ -504,7 +504,8 @@ function* publishPlaylistAsync( userId, action.playlistId, playlist, - action.dismissToastKey + action.dismissToastKey, + action.isAlbum ) } @@ -512,7 +513,8 @@ function* confirmPublishPlaylist( userId: ID, playlistId: ID, playlist: Collection, - dismissToastKey?: string + dismissToastKey?: string, + isAlbum?: boolean ) { const audiusBackendInstance = yield* getContext('audiusBackendInstance') yield* put( @@ -575,7 +577,11 @@ function* confirmPublishPlaylist( yield* put(manualClearToast({ key: dismissToastKey })) } - yield* put(toast({ content: 'Your playlist is now public!' })) + yield* put( + toast({ + content: `Your ${isAlbum ? 'album' : 'playlist'} is now public!` + }) + ) }, function* ({ error, timeout, message }) { // Fail Call diff --git a/packages/web/src/common/store/cache/tracks/sagas.ts b/packages/web/src/common/store/cache/tracks/sagas.ts index 44f8ebc4930..76ac4d6347c 100644 --- a/packages/web/src/common/store/cache/tracks/sagas.ts +++ b/packages/web/src/common/store/cache/tracks/sagas.ts @@ -181,6 +181,9 @@ function* editTrackAsync(action: ReturnType) { trackForEdit.musical_key || undefined ) + // Format bpm + trackForEdit.bpm = trackForEdit.bpm ? Number(trackForEdit.bpm) : undefined + yield* call( confirmEditTrack, action.trackId, diff --git a/packages/web/src/components/collection/desktop/PublishConfirmationModal.tsx b/packages/web/src/components/collection/desktop/PublishConfirmationModal.tsx index 59746381da3..28887f8b703 100644 --- a/packages/web/src/components/collection/desktop/PublishConfirmationModal.tsx +++ b/packages/web/src/components/collection/desktop/PublishConfirmationModal.tsx @@ -66,9 +66,9 @@ export const PublishConfirmationModal = ( }, [is_scheduled_release, tracks]) const handlePublish = useCallback(() => { - dispatch(publishPlaylist(collectionId)) + dispatch(publishPlaylist(collectionId, undefined, is_album)) onClose() - }, [dispatch, collectionId, onClose]) + }, [dispatch, collectionId, is_album, onClose]) const messages = getMessages(is_album, isEarlyRelease) diff --git a/packages/web/src/components/data-entry/ContextualMenu.tsx b/packages/web/src/components/data-entry/ContextualMenu.tsx index d4f583aceb6..a317d97678d 100644 --- a/packages/web/src/components/data-entry/ContextualMenu.tsx +++ b/packages/web/src/components/data-entry/ContextualMenu.tsx @@ -122,7 +122,7 @@ export const SelectedValue = (props: SelectedValueProps) => { const { label, icon: Icon, children, ...rest } = props return ( diff --git a/packages/web/src/components/edit/fields/visibility/VisibilityField.tsx b/packages/web/src/components/edit/fields/visibility/VisibilityField.tsx index 559c9aefd9b..8ae62ef01f2 100644 --- a/packages/web/src/components/edit/fields/visibility/VisibilityField.tsx +++ b/packages/web/src/components/edit/fields/visibility/VisibilityField.tsx @@ -218,7 +218,9 @@ const VisibilityMenuFields = (props: VisibilityMenuFieldsProps) => { : undefined } /> - {!initiallyPublic && isPaidScheduledEnabled ? ( + {!initiallyPublic && + (entityType === 'track' || + (isPaidScheduledEnabled && entityType === 'album')) ? ( { const hasError = Boolean(meta.touched && meta.error) - return + return ( + + ) } diff --git a/packages/web/src/components/image-selection/ImageSelectionButton.jsx b/packages/web/src/components/image-selection/ImageSelectionButton.jsx index e22c444f1b4..f23ad2b8873 100644 --- a/packages/web/src/components/image-selection/ImageSelectionButton.jsx +++ b/packages/web/src/components/image-selection/ImageSelectionButton.jsx @@ -141,7 +141,7 @@ ImageSelectionButton.propTypes = { hasImage: PropTypes.bool.isRequired, // The name of the image (e.g. render the button as Add "Artwork" or Add "Cover Photo") imageName: PropTypes.string, - showImageName: PropTypes.boolean, + showImageName: PropTypes.bool, // Whether or not to show the image selection modal. Otherwise, the // button itself is the dropzone. includePopup: PropTypes.bool, @@ -150,7 +150,7 @@ ImageSelectionButton.propTypes = { onClick: PropTypes.func, defaultPopupOpen: PropTypes.bool, isImageAutogenerated: PropTypes.bool, - onRemoveArtwork: PropTypes.bool, + onRemoveArtwork: PropTypes.func, ...ImageSelectionProps } diff --git a/packages/web/src/components/modal-radio/ModalRadioItem.tsx b/packages/web/src/components/modal-radio/ModalRadioItem.tsx index 07ccca3f33f..5774f3e34f6 100644 --- a/packages/web/src/components/modal-radio/ModalRadioItem.tsx +++ b/packages/web/src/components/modal-radio/ModalRadioItem.tsx @@ -1,14 +1,6 @@ import { ReactNode, useContext, useEffect, useState } from 'react' -import { - Tag, - Radio, - RadioGroupContext, - Text, - IconComponent, - Hint, - IconQuestionCircle -} from '@audius/harmony' +import { Tag, Radio, RadioGroupContext, Text, Box } from '@audius/harmony' import { ResizeObserver } from '@juggle/resize-observer' import cn from 'classnames' import useMeasure from 'react-use-measure' @@ -22,8 +14,7 @@ type ModalRadioItemProps = { label: string title?: ReactNode description?: ReactNode - hintContent?: string | ReactNode - hintIcon?: IconComponent + hint?: ReactNode tag?: string value: any disabled?: boolean @@ -36,8 +27,7 @@ export const ModalRadioItem = (props: ModalRadioItemProps) => { const { icon, label, - hintContent, - hintIcon = IconQuestionCircle, + hint, tag, title, description, @@ -84,7 +74,7 @@ export const ModalRadioItem = (props: ModalRadioItemProps) => { {tag ? {tag} : null} - {hintContent ? {hintContent} : null} + {hint ? {hint} : null} {checkedContent || description ? (
{ if (isSearchV2Enabled) { - history.push(`/search/tracks`) + history.push(`/search`) } else { setIsSearching(true) } diff --git a/packages/web/src/components/play-bar/desktop/components/PlayingTrackInfo.tsx b/packages/web/src/components/play-bar/desktop/components/PlayingTrackInfo.tsx index 60a0e4fa8ae..8616a04a358 100644 --- a/packages/web/src/components/play-bar/desktop/components/PlayingTrackInfo.tsx +++ b/packages/web/src/components/play-bar/desktop/components/PlayingTrackInfo.tsx @@ -1,4 +1,4 @@ -import { memo, useEffect } from 'react' +import { memo } from 'react' import { useGatedContentAccess } from '@audius/common/hooks' import { @@ -82,21 +82,12 @@ const PlayingTrackInfo = ({ 'usdc_purchase' in track.stream_conditions && !hasStreamAccess) - const [artistSpringProps, setArtistSpringProps] = useSpring(() => springProps) - const [trackSpringProps, setTrackSpringProps] = useSpring(() => springProps) + const spring = useSpring(springProps) const profileImage = useProfilePicture( artistUserId ?? null, SquareSizes.SIZE_150_BY_150 ) - useEffect(() => { - setArtistSpringProps(springProps) - }, [artistUserId, setArtistSpringProps]) - - useEffect(() => { - setTrackSpringProps(springProps) - }, [trackTitle, setTrackSpringProps]) - const boxShadowStyle = hasShadow && dominantColor ? { @@ -106,10 +97,7 @@ const PlayingTrackInfo = ({ const renderTrackTitle = () => { return ( - +
)} - +
this.props.onClear()}> - - + ) : null } onKeyDown={this.onKeyDown} diff --git a/packages/web/src/components/table/Table.tsx b/packages/web/src/components/table/Table.tsx index 35f2c295beb..f72c0bf9916 100644 --- a/packages/web/src/components/table/Table.tsx +++ b/packages/web/src/components/table/Table.tsx @@ -377,11 +377,8 @@ export const Table = ({ )} {...props} key={key} - onClick={ - isLocked - ? undefined - : (e: MouseEvent) => - onClickRow?.(e, row, row.index) + onClick={(e: MouseEvent) => + onClickRow?.(e, row, row.index) } > {cells.map((cell) => renderCell(cell))} diff --git a/packages/web/src/components/table/components/TablePlayButton.tsx b/packages/web/src/components/table/components/TablePlayButton.tsx index ef8209a1e60..f8f716fe5ae 100644 --- a/packages/web/src/components/table/components/TablePlayButton.tsx +++ b/packages/web/src/components/table/components/TablePlayButton.tsx @@ -46,7 +46,7 @@ export const TablePlayButton = ({ className={cn(styles.icon, { [styles.hideDefault]: hideDefault && !playing })} - fill={isLocked ? n150 : shouldShowPremiumColor ? lightGreen : p300} + fill={shouldShowPremiumColor ? lightGreen : isLocked ? n150 : p300} /> )}
diff --git a/packages/web/src/components/track/GatedContentLabel.tsx b/packages/web/src/components/track/GatedContentLabel.tsx index 75056efa700..1e4115d0d69 100644 --- a/packages/web/src/components/track/GatedContentLabel.tsx +++ b/packages/web/src/components/track/GatedContentLabel.tsx @@ -7,61 +7,55 @@ import { } from '@audius/common/models' import { Nullable } from '@audius/common/utils' import { - Flex, - Text, IconCart, IconCollectible, IconSpecialAccess, - useTheme + IconColors } from '@audius/harmony' +import { LineupTileLabel } from './LineupTileLabel' + const messages = { collectibleGated: 'Collectible Gated', specialAccess: 'Special Access', premium: 'Premium' } -/** Renders a label indicating a gated content type. If the user does - * not yet have access or is the owner, the label will be in an accented color. - */ -export const GatedContentLabel = ({ - streamConditions, - hasStreamAccess, - isOwner -}: { +type GatedContentLabelProps = { streamConditions?: Nullable hasStreamAccess: boolean isOwner: boolean -}) => { - const { color } = useTheme() +} + +/** Renders a label indicating a gated content type. If the user does + * not yet have access or is the owner, the label will be in an accented color. + */ +export const GatedContentLabel = (props: GatedContentLabelProps) => { + const { streamConditions, hasStreamAccess, isOwner } = props let message = messages.specialAccess let IconComponent = IconSpecialAccess - let specialColor = color.icon.default + let specialColor: IconColors = 'subdued' if ( isContentFollowGated(streamConditions) || isContentTipGated(streamConditions) ) { - specialColor = color.special.blue + specialColor = 'special' } else if (isContentCollectibleGated(streamConditions)) { message = messages.collectibleGated IconComponent = IconCollectible - specialColor = color.special.blue + specialColor = 'special' } else if (isContentUSDCPurchaseGated(streamConditions)) { message = messages.premium IconComponent = IconCart - specialColor = color.special.lightGreen + specialColor = 'premium' } - const finalColor = - isOwner || !hasStreamAccess ? specialColor : color.icon.subdued + specialColor = isOwner || !hasStreamAccess ? specialColor : 'subdued' return ( - - - - {message} - - + + {message} + ) } diff --git a/packages/web/src/components/track/GatedContentSection.tsx b/packages/web/src/components/track/GatedContentSection.tsx index 7af9fa7a61b..d6673a8c4b4 100644 --- a/packages/web/src/components/track/GatedContentSection.tsx +++ b/packages/web/src/components/track/GatedContentSection.tsx @@ -639,20 +639,22 @@ export const GatedContentSection = ({ mouseEnterDelay={0.1} component='span' > -

- dispatch(pushRoute(profilePage(emptyStringGuard(entity.handle)))) - } - > - {entity.name} + +

+ dispatch(pushRoute(profilePage(emptyStringGuard(entity.handle)))) + } + > + {entity.name} +

-

+ ), [dispatch] @@ -661,7 +663,7 @@ export const GatedContentSection = ({ if (!streamConditions) return null if (!shouldDisplay) return null - if (hasStreamAccess) { + if (!hasStreamAccess) { return (
{ + const { trackId } = props + const { data: track } = useGetTrackById({ id: trackId }) + const { data: currentUserId } = useGetCurrentUserId({}) + const onSearchPage = !!useRouteMatch(SEARCH_PAGE) + + if (!track) return null + + const { + is_stream_gated, + is_download_gated, + stream_conditions, + download_conditions, + owner_id + } = track + + const isOwner = owner_id === currentUserId + + let message: Maybe + let Icon: Maybe + let color: Maybe + + if (is_stream_gated) { + if ( + isContentFollowGated(stream_conditions) || + isContentTipGated(stream_conditions) + ) { + message = messages.specialAccess + Icon = IconSpecialAccess + color = 'special' + } else if (isContentCollectibleGated(stream_conditions)) { + message = messages.collectibleGated + Icon = IconCollectible + color = 'special' + } else if (isContentUSDCPurchaseGated(stream_conditions)) { + message = messages.premium + Icon = IconCart + color = 'premium' + } + } else if (is_download_gated && onSearchPage) { + if (isContentUSDCPurchaseGated(download_conditions)) { + message = messages.premiumExtras + Icon = IconReceive + color = 'premium' + } + } + + if (!message || !Icon || !color) { + return null + } + + return ( + + {message} + + ) +} diff --git a/packages/web/src/components/track/LineupTileLabel.tsx b/packages/web/src/components/track/LineupTileLabel.tsx new file mode 100644 index 00000000000..933eb963cd2 --- /dev/null +++ b/packages/web/src/components/track/LineupTileLabel.tsx @@ -0,0 +1,20 @@ +import type { IconColors, IconComponent } from '@audius/harmony' +import { Flex, Text } from '@audius/harmony' + +type LineupTileLabelProps = { + icon?: IconComponent + children: string + color?: IconColors +} + +export const LineupTileLabel = (props: LineupTileLabelProps) => { + const { icon: Icon, children, color = 'subdued' } = props + return ( + + {Icon ? : null} + + {children} + + + ) +} diff --git a/packages/web/src/components/visibility-label/VisibilityLabel.tsx b/packages/web/src/components/track/VisibilityLabel.tsx similarity index 90% rename from packages/web/src/components/visibility-label/VisibilityLabel.tsx rename to packages/web/src/components/track/VisibilityLabel.tsx index 6485ee370d9..19148586c29 100644 --- a/packages/web/src/components/visibility-label/VisibilityLabel.tsx +++ b/packages/web/src/components/track/VisibilityLabel.tsx @@ -25,13 +25,19 @@ export type VisibilityLabelProps = { isScheduledRelease?: boolean } -export const VisibilityLabel = ({ - releaseDate, - isUnlisted, - isScheduledRelease -}: VisibilityLabelProps) => { +export const VisibilityLabel = (props: VisibilityLabelProps) => { + const { releaseDate, isUnlisted, isScheduledRelease } = props const { color } = useTheme() + if ( + !releaseDate || + !isUnlisted || + !isScheduledRelease || + dayjs(releaseDate).isBefore(dayjs()) + ) { + return null + } + if (isUnlisted && !isScheduledRelease) { return ( @@ -43,15 +49,6 @@ export const VisibilityLabel = ({ ) } - if ( - !releaseDate || - !isUnlisted || - !isScheduledRelease || - dayjs(releaseDate).isBefore(dayjs()) - ) { - return null - } - return ( diff --git a/packages/web/src/components/track/desktop/TrackTile.module.css b/packages/web/src/components/track/desktop/TrackTile.module.css index 299cba52f85..d5ac0cf24be 100644 --- a/packages/web/src/components/track/desktop/TrackTile.module.css +++ b/packages/web/src/components/track/desktop/TrackTile.module.css @@ -155,23 +155,6 @@ justify-content: flex-start; } -.artistPickLabelContainer { - display: flex; - align-items: center; - flex: 0 0 auto; - gap: var(--harmony-unit-1); - color: var(--harmony-n-400); -} - -.artistPickIcon { - height: var(--harmony-unit-4); - width: var(--harmony-unit-4); -} - -.artistPickIcon path { - fill: var(--harmony-n-400); -} - /* Right side of the track tile */ .topRight { position: absolute; diff --git a/packages/web/src/components/track/desktop/TrackTile.tsx b/packages/web/src/components/track/desktop/TrackTile.tsx index e042012fac0..4c7b41c3812 100644 --- a/packages/web/src/components/track/desktop/TrackTile.tsx +++ b/packages/web/src/components/track/desktop/TrackTile.tsx @@ -16,13 +16,13 @@ import { } from '@audius/common/utils' import { IconVolumeLevel2 as IconVolume, - IconStar, IconCheck, IconCrown, Text, Flex, ProgressBar, - Paper + Paper, + IconStar } from '@audius/harmony' import cn from 'classnames' import { useSelector } from 'react-redux' @@ -30,16 +30,17 @@ import { useSelector } from 'react-redux' import { DogEar } from 'components/dog-ear' import { TextLink } from 'components/link' import Skeleton from 'components/skeleton/Skeleton' -import { VisibilityLabel } from 'components/visibility-label/VisibilityLabel' import { useAuthenticatedClickCallback } from 'hooks/useAuthenticatedCallback' import { LockedStatusPill, LockedStatusPillProps } from '../../locked-status-pill' -import { GatedContentLabel } from '../GatedContentLabel' +import { GatedTrackLabel } from '../GatedTrackLabel' +import { LineupTileLabel } from '../LineupTileLabel' import { OwnerActionButtons } from '../OwnerActionButtons' import { ViewerActionButtons } from '../ViewerActionButtons' +import { VisibilityLabel } from '../VisibilityLabel' import { messages } from '../trackTileMessages' import { TrackTileSize, @@ -221,26 +222,6 @@ const TrackTile = ({ streamConditions }) - let specialContentLabel = null - if (!isLoading && !isUnlisted) { - if (isStreamGated) { - specialContentLabel = ( - - ) - } else if (isArtistPick) { - specialContentLabel = ( -
- - {messages.artistPick} -
- ) - } - } - const Root = standalone ? Paper : 'div' return ( @@ -318,7 +299,14 @@ const TrackTile = ({ ) : ( <> - {specialContentLabel} + {isArtistPick ? ( + + {messages.artistPick} + + ) : null} + {!isUnlisted && trackId ? ( + + ) : null} { } if (isPrivate) { specialContentLabel = ( - - - - {messages.hidden} - - + + {messages.hidden} + ) } @@ -424,11 +422,7 @@ const PlaylistTile = (props: PlaylistTileProps & ExtraProps) => { isVisible={isTrending && shouldShow} showCrown={showRankIcon} /> - {isReadonly ? ( - - {specialContentLabel} - - ) : null} + {isReadonly ? specialContentLabel : null} {shouldShowStats ? ( <> { return isVisible ? ( - - {showCrown ? : } - {index + 1} - + + {`${index + 1}`} + ) : null } @@ -182,7 +175,6 @@ const TrackTile = (props: CombinedProps) => { togglePlay, coSign, darkMode, - fieldVisibility, isActive, isMatrix, userId, @@ -299,27 +291,6 @@ const TrackTile = (props: CombinedProps) => { const isReadonly = variant === 'readonly' - let specialContentLabel = null - - if (!isLoading && !isUnlisted) { - if (isStreamGated) { - specialContentLabel = ( - - ) - } else if (isArtistPick) { - specialContentLabel = ( -
- - {messages.artistPick} -
- ) - } - } - return (
{ showCrown={showRankIcon} index={index} isVisible={isTrending && !showSkeleton} - className={styles.rankIconContainer} /> - {specialContentLabel} + {isArtistPick ? ( + + {messages.artistPick} + + ) : !isUnlisted && id ? ( + + ) : null} { {!isLoading ? renderLockedContentOrPlayCount({ hasStreamAccess, - fieldVisibility, isOwner, isStreamGated, streamConditions, diff --git a/packages/web/src/components/tracks-table/TracksTable.tsx b/packages/web/src/components/tracks-table/TracksTable.tsx index d65190b01a9..a2e8edb73d6 100644 --- a/packages/web/src/components/tracks-table/TracksTable.tsx +++ b/packages/web/src/components/tracks-table/TracksTable.tsx @@ -15,11 +15,10 @@ import { gatedContentSelectors, usePremiumContentPurchaseModal } from '@audius/common/store' -import { formatCount, formatPrice, formatSeconds } from '@audius/common/utils' +import { formatCount, formatSeconds } from '@audius/common/utils' import { IconVisibilityHidden, IconLock, - Button, Flex, IconSpecialAccess, IconCollectible, @@ -98,7 +97,6 @@ type TracksTableProps = { isReorderable?: boolean isAlbumPage?: boolean isAlbumPremium?: boolean - isPremiumEnabled?: boolean shouldShowGatedType?: boolean loading?: boolean onClickFavorite?: (track: any) => void @@ -148,19 +146,16 @@ export const TracksTable = ({ isPaginated = false, isReorderable = false, isAlbumPage = false, - isAlbumPremium = false, fetchBatchSize, fetchMoreTracks, fetchPage, fetchThreshold, isVirtualized = false, - isPremiumEnabled = false, shouldShowGatedType = false, loading = false, onClickFavorite, onClickRemove, onClickRepost, - onClickPurchase, onClickRow, onReorderTracks, onShowMoreToggle, @@ -203,12 +198,12 @@ export const TracksTable = ({ paused={!playing} playing={active} hideDefault={false} - isTrackPremium={isTrackPremium && isPremiumEnabled} + isTrackPremium={isTrackPremium} isLocked={isLocked} /> ) }, - [isPremiumEnabled, playing, playingIndex, trackAccessMap] + [playing, playingIndex, trackAccessMap] ) const renderTrackNameCell = useCallback( @@ -515,43 +510,6 @@ export const TracksTable = ({ ] ) - const renderPurchaseButton = useCallback( - (cellInfo: TrackCell) => { - const track = cellInfo.row.original - const { isFetchingNFTAccess, hasStreamAccess } = trackAccessMap[ - track.track_id - ] ?? { isFetchingNFTAccess: false, hasStreamAccess: true } - const isLocked = !isFetchingNFTAccess && !hasStreamAccess - const isOwner = track.owner_id === userId - const deleted = - track.is_delete || track._marked_deleted || !!track.user?.is_deactivated - if (!isLocked || deleted || isOwner || !isPremiumEnabled) { - return null - } - - if (isContentUSDCPurchaseGated(track.stream_conditions)) { - // Format the price as $$ with 2 digit decimal cents - const formattedPrice = formatPrice( - track.stream_conditions.usdc_purchase.price - ) - return ( - - ) - } - }, - [isAlbumPremium, isPremiumEnabled, onClickPurchase, trackAccessMap, userId] - ) - const onClickPremiumPill = useCallback( (trackId: number) => { openPremiumContentPurchaseModal( @@ -573,10 +531,9 @@ export const TracksTable = ({ [dispatch, setGatedModalVisibility] ) - const renderTrackActions = useCallback( + const renderLockedButtonCell = useCallback( (cellInfo: TrackCell) => { const track = cellInfo.row.original - const { is_delete: isDelete } = track const { isFetchingNFTAccess, hasStreamAccess } = trackAccessMap[ track.track_id ] ?? { isFetchingNFTAccess: false, hasStreamAccess: true } @@ -584,6 +541,45 @@ export const TracksTable = ({ const isLockedPremium = isLocked && isContentUSDCPurchaseGated(track.stream_conditions) const gatedTrackStatus = gatedTrackStatusMap[track.track_id] + const isOwner = track.owner_id === userId + + const deleted = + track.is_delete || track._marked_deleted || !!track.user?.is_deactivated + + if (!isLocked || deleted || isOwner) { + return null + } + return ( + { + isLockedPremium + ? onClickPremiumPill(track.track_id) + : onClickGatedPill(track.track_id) + }} + buttonSize='small' + showIcon={false} + /> + ) + }, + [ + gatedTrackStatusMap, + onClickGatedPill, + onClickPremiumPill, + trackAccessMap, + userId + ] + ) + + const renderTrackActions = useCallback( + (cellInfo: TrackCell) => { + const track = cellInfo.row.original + const { is_delete: isDelete } = track + const { isFetchingNFTAccess, hasStreamAccess } = trackAccessMap[ + track.track_id + ] ?? { isFetchingNFTAccess: false, hasStreamAccess: true } + const isLocked = !isFetchingNFTAccess && !hasStreamAccess if (isDelete) return null @@ -597,34 +593,18 @@ export const TracksTable = ({ mh='l' className={styles.trackActionsContainer} > - {isLocked ? ( - { - isLockedPremium - ? onClickPremiumPill(track.track_id) - : onClickGatedPill(track.track_id) - }} - buttonSize='small' - showIcon={false} - /> - ) : null} + {isLocked ? renderLockedButtonCell(cellInfo) : null} {!isLocked ? renderRepostButtonCell(cellInfo) : null} {!isLocked ? renderFavoriteButtonCell(cellInfo) : null} - {renderPurchaseButton(cellInfo)} {renderOverflowMenuCell(cellInfo)} ) }, [ trackAccessMap, - gatedTrackStatusMap, - onClickPremiumPill, - onClickGatedPill, renderFavoriteButtonCell, renderOverflowMenuCell, - renderPurchaseButton, + renderLockedButtonCell, renderRepostButtonCell ] ) diff --git a/packages/web/src/components/user-badges/UserBadges.module.css b/packages/web/src/components/user-badges/UserBadges.module.css index 0edd24bbcaa..db74328e474 100644 --- a/packages/web/src/components/user-badges/UserBadges.module.css +++ b/packages/web/src/components/user-badges/UserBadges.module.css @@ -1,9 +1,9 @@ .container { display: flex; overflow: visible; + gap: 4px; } -.container svg, -.inlineContainer svg { +.inlineContainer svg:first-child { margin-right: 4px !important; } diff --git a/packages/web/src/components/user-badges/UserBadges.tsx b/packages/web/src/components/user-badges/UserBadges.tsx index 0fc7e9311a5..1b21ad5c570 100644 --- a/packages/web/src/components/user-badges/UserBadges.tsx +++ b/packages/web/src/components/user-badges/UserBadges.tsx @@ -101,19 +101,16 @@ const UserBadges = ({ if (!hasContent) return null - if (inline) { - return ( - - {(isVerifiedOverride ?? isVerified) && ( - - )} - {audioBadge && - cloneElement(audioBadge, { height: badgeSize, width: badgeSize })} - - ) - } return ( - + {(isVerifiedOverride ?? isVerified) && ( )} diff --git a/packages/web/src/hooks/useManagedAccountNotAllowedRedirect.ts b/packages/web/src/hooks/useManagedAccountNotAllowedRedirect.ts index 1098fae547b..248d50d1c96 100644 --- a/packages/web/src/hooks/useManagedAccountNotAllowedRedirect.ts +++ b/packages/web/src/hooks/useManagedAccountNotAllowedRedirect.ts @@ -1,6 +1,10 @@ import { useContext, useEffect } from 'react' +import { useGetManagedAccounts } from '@audius/common/api' import { useIsManagedAccount } from '@audius/common/hooks' +import { Status } from '@audius/common/models' +import { accountSelectors } from '@audius/common/store' +import { useSelector } from 'react-redux' import { ToastContext } from 'components/toast/ToastContext' import { FEED_PAGE } from 'utils/route' @@ -8,7 +12,8 @@ import { FEED_PAGE } from 'utils/route' import { useNavigateToPage } from './useNavigateToPage' const messages = { - unauthorized: `You can't do that as a managed user` + unauthorized: 'Unauthorized', + unauthorizedAsManaged: `You can't do that as a managed user` } /** @@ -25,7 +30,7 @@ export const useManagedAccountNotAllowedRedirect = ( useEffect(() => { if (isManagedAccount) { navigate(route) - toast(messages.unauthorized) + toast(messages.unauthorizedAsManaged) } }, [isManagedAccount, navigate, route, toast]) } @@ -48,7 +53,46 @@ export const useManagedAccountNotAllowedCallback = ({ useEffect(() => { if (isManagedAccount && trigger) { callback() - toast(messages.unauthorized) + toast(messages.unauthorizedAsManaged) } }, [isManagedAccount, callback, toast, trigger]) } + +/** + * Hook to prevent a managed account from some action if the + * managed account does not have access to the provided account + * @param handle handle of the user we are doing something on behalf of + * @param route route to redirect the user to + */ +export const useIsUnauthorizedForHandleRedirect = ( + handle: string, + route: string = FEED_PAGE +) => { + const { handle: actingHandle, user_id: userId } = + useSelector(accountSelectors.getAccountUser) || {} + const navigate = useNavigateToPage() + const { toast } = useContext(ToastContext) + + const { data: managedAccounts = [], status: accountsStatus } = + useGetManagedAccounts({ userId: userId! }, { disabled: !userId }) + + const isLoading = + !actingHandle || + !userId || + accountsStatus === Status.LOADING || + accountsStatus === Status.IDLE + const isOwner = actingHandle === handle + const isManaged = + !!actingHandle && + managedAccounts.find(({ user }) => user.handle.toLowerCase() === handle) + + useEffect(() => { + if (isLoading) { + return + } + if (!isOwner && !isManaged) { + navigate(route) + toast(messages.unauthorized) + } + }, [isLoading, isOwner, isManaged, navigate, route, toast]) +} diff --git a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx index 5f73214b3f1..05e42ed477a 100644 --- a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx +++ b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx @@ -11,7 +11,6 @@ import { ModalSource, Track } from '@audius/common/models' -import { FeatureFlags } from '@audius/common/services' import { CollectionTrack, CollectionsPageType, @@ -32,7 +31,6 @@ import { SuggestedTracks } from 'components/suggested-tracks' import { Tile } from 'components/tile' import { TracksTable, TracksTableColumn } from 'components/tracks-table' import { useAuthenticatedCallback } from 'hooks/useAuthenticatedCallback' -import { useFlag } from 'hooks/useRemoteConfig' import { smartCollectionIcons } from 'pages/collection-page/smartCollectionIcons' import { computeCollectionMetadataProps } from 'pages/collection-page/store/utils' @@ -132,7 +130,6 @@ const CollectionPage = ({ onClickRow, onClickSave, onClickRepostTrack, - onClickPurchaseTrack, onSortTracks, onReorderTracks, onClickRemove, @@ -140,9 +137,6 @@ const CollectionPage = ({ onClickFavorites }: CollectionPageProps) => { const { status, metadata, user } = collection - const { isEnabled: isPremiumAlbumsEnabled } = useFlag( - FeatureFlags.PREMIUM_ALBUMS_ENABLED - ) // TODO: Consider dynamic lineups, esp. for caching improvement. const [dataSource, playingIndex] = @@ -326,7 +320,7 @@ const CollectionPage = ({ className={styles.bodyWrapper} size='large' elevation='mid' - dogEar={isPremiumAlbumsEnabled ? dogEarType : undefined} + dogEar={dogEarType} >
{topSection}
{!collectionLoading && isEmpty ? ( @@ -352,7 +346,6 @@ const CollectionPage = ({ onClickRemove={isOwner ? onClickRemove : undefined} onClickRepost={onClickRepostTrack} onClickPurchase={openPurchaseModal} - isPremiumEnabled={isPremiumAlbumsEnabled} onReorderTracks={onReorderTracks} onSortTracks={onSortTracks} isReorderable={ diff --git a/packages/web/src/pages/edit-collection-page/desktop/EditCollectionPage.tsx b/packages/web/src/pages/edit-collection-page/desktop/EditCollectionPage.tsx index d9f0907647a..e342858ac95 100644 --- a/packages/web/src/pages/edit-collection-page/desktop/EditCollectionPage.tsx +++ b/packages/web/src/pages/edit-collection-page/desktop/EditCollectionPage.tsx @@ -20,6 +20,8 @@ import Header from 'components/header/desktop/Header' import LoadingSpinnerFullPage from 'components/loading-spinner-full-page/LoadingSpinnerFullPage' import Page from 'components/page/Page' import { useCollectionCoverArt2 } from 'hooks/useCollectionCoverArt' +import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect' +import { useRequiresAccount } from 'hooks/useRequiresAccount' import { track } from 'services/analytics' import { updatePlaylistContents } from '../utils' @@ -42,6 +44,8 @@ export const EditCollectionPage = () => { const focus = searchParams.get('focus') const permalink = `/${handle}/${isAlbum ? 'album' : 'playlist'}/${slug}` const dispatch = useDispatch() + useRequiresAccount() + useIsUnauthorizedForHandleRedirect(handle) const { data: currentUserId } = useGetCurrentUserId({}) const { data: collection, status } = useGetPlaylistByPermalink( diff --git a/packages/web/src/pages/edit-collection-page/mobile/EditCollectionPage.tsx b/packages/web/src/pages/edit-collection-page/mobile/EditCollectionPage.tsx index 2e2fde9847f..f2c81456991 100644 --- a/packages/web/src/pages/edit-collection-page/mobile/EditCollectionPage.tsx +++ b/packages/web/src/pages/edit-collection-page/mobile/EditCollectionPage.tsx @@ -31,6 +31,8 @@ import { useTemporaryNavContext } from 'components/nav/mobile/NavContext' import TextElement, { Type } from 'components/nav/mobile/TextElement' import TrackList from 'components/track/mobile/TrackList' import { useCollectionCoverArt2 } from 'hooks/useCollectionCoverArt' +import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect' +import { useRequiresAccount } from 'hooks/useRequiresAccount' import UploadStub from 'pages/profile-page/components/mobile/UploadStub' import { track } from 'services/analytics' import { AppState } from 'store/types' @@ -76,6 +78,8 @@ const EditCollectionPage = g( const permalink = `/${handle}/${isAlbum ? 'album' : 'playlist'}/${slug}` const dispatch = useDispatch() const history = useHistory() + useRequiresAccount() + useIsUnauthorizedForHandleRedirect(handle) const { data: currentUserId } = useGetCurrentUserId({}) const { data: collection } = useGetPlaylistByPermalink( diff --git a/packages/web/src/pages/edit-page/EditTrackPage.tsx b/packages/web/src/pages/edit-page/EditTrackPage.tsx index 3defadad43e..4f02c402c32 100644 --- a/packages/web/src/pages/edit-page/EditTrackPage.tsx +++ b/packages/web/src/pages/edit-page/EditTrackPage.tsx @@ -26,6 +26,8 @@ import { TrackEditFormValues } from 'components/edit-track/types' import Header from 'components/header/desktop/Header' import LoadingSpinnerFullPage from 'components/loading-spinner-full-page/LoadingSpinnerFullPage' import Page from 'components/page/Page' +import { useIsUnauthorizedForHandleRedirect } from 'hooks/useManagedAccountNotAllowedRedirect' +import { useRequiresAccount } from 'hooks/useRequiresAccount' import { useTrackCoverArt2 } from 'hooks/useTrackCoverArt' const { deleteTrack, editTrack } = cacheTracksActions @@ -47,6 +49,8 @@ export const EditTrackPage = (props: EditPageProps) => { const { scrollToTop } = props const { handle, slug } = useParams<{ handle: string; slug: string }>() const dispatch = useDispatch() + useRequiresAccount() + useIsUnauthorizedForHandleRedirect(handle) const { data: currentUserId } = useGetCurrentUserId({}) const permalink = `/${handle}/${slug}` @@ -95,6 +99,7 @@ export const EditTrackPage = (props: EditPageProps) => { const trackAsMetadataForUpload: TrackMetadataForUpload = { ...(track as TrackMetadata), + mood: track?.mood || null, artwork: { url: coverArtUrl || '' }, diff --git a/packages/web/src/pages/saved-page/SavedPageProvider.tsx b/packages/web/src/pages/saved-page/SavedPageProvider.tsx index bbe3bb2cf1d..4c407c0f3fe 100644 --- a/packages/web/src/pages/saved-page/SavedPageProvider.tsx +++ b/packages/web/src/pages/saved-page/SavedPageProvider.tsx @@ -244,11 +244,13 @@ class SavedPage extends PureComponent { const playingIndex = tracks.entries.findIndex( ({ uid }: any) => uid === playingUid ) - const filteredMetadata = this.formatMetadata(trackMetadatas).filter( - (item) => - item.title?.toLowerCase().indexOf(filterText.toLowerCase()) > -1 || - item.user?.name.toLowerCase().indexOf(filterText.toLowerCase()) > -1 - ) + const filteredMetadata = this.formatMetadata(trackMetadatas) + .filter((item) => !item._marked_deleted && !item.is_delete) + .filter( + (item) => + item.title?.toLowerCase().indexOf(filterText.toLowerCase()) > -1 || + item.user?.name.toLowerCase().indexOf(filterText.toLowerCase()) > -1 + ) const filteredIndex = playingIndex > -1 ? filteredMetadata.findIndex((metadata) => metadata.uid === playingUid) diff --git a/packages/web/src/pages/saved-page/components/desktop/SavedPage.tsx b/packages/web/src/pages/saved-page/components/desktop/SavedPage.tsx index 195435c242c..d922d03040c 100644 --- a/packages/web/src/pages/saved-page/components/desktop/SavedPage.tsx +++ b/packages/web/src/pages/saved-page/components/desktop/SavedPage.tsx @@ -122,6 +122,7 @@ const SavedPage = ({ }: SavedPageProps) => { const { mainContentRef } = useContext(MainContentContext) const initFetch = useSelector(getInitialFetchStatus) + const emptyTracksHeader = useSelector((state: CommonState) => { const selectedCategory = getCategory(state, { currentTab: SavedPageTabs.TRACKS diff --git a/packages/web/src/pages/search-page-v2/RecentSearches.tsx b/packages/web/src/pages/search-page-v2/RecentSearches.tsx index bc259a0233c..07c4e6498e1 100644 --- a/packages/web/src/pages/search-page-v2/RecentSearches.tsx +++ b/packages/web/src/pages/search-page-v2/RecentSearches.tsx @@ -331,6 +331,8 @@ export const RecentSearches = () => { pv='xl' w='100%' css={{ maxWidth: '688px' }} + backgroundColor='white' + border='default' direction='column' gap='l' > diff --git a/packages/web/src/pages/search-page-v2/SearchCatalogTile.tsx b/packages/web/src/pages/search-page-v2/SearchCatalogTile.tsx index dfd2675ad3a..972ed35240b 100644 --- a/packages/web/src/pages/search-page-v2/SearchCatalogTile.tsx +++ b/packages/web/src/pages/search-page-v2/SearchCatalogTile.tsx @@ -15,6 +15,8 @@ export const SearchCatalogTile = () => { gap={isMobile ? 's' : 'l'} alignItems='center' w={isMobile ? 'auto' : '100%'} + border='default' + backgroundColor={isMobile ? 'surface1' : 'white'} > { query ? ( - “{query}” + {query} ) : null diff --git a/packages/web/src/pages/search-page-v2/SearchPageV2.tsx b/packages/web/src/pages/search-page-v2/SearchPageV2.tsx index 4df9e8d1356..4d13d7f8df7 100644 --- a/packages/web/src/pages/search-page-v2/SearchPageV2.tsx +++ b/packages/web/src/pages/search-page-v2/SearchPageV2.tsx @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect } from 'react' import { SearchCategory } from '@audius/common/src/api/search' -import { Flex } from '@audius/harmony' +import { Flex, useTheme } from '@audius/harmony' import { intersection, isEmpty } from 'lodash' import { generatePath, useParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom-v5-compat' @@ -52,7 +52,8 @@ const useShowSearchResults = () => { export const SearchPageV2 = () => { const isMobile = useIsMobile() - const { category } = useParams<{ category: CategoryKey }>() + const { category: categoryParam } = useParams<{ category: CategoryKey }>() + const category = isMobile ? categoryParam ?? 'profiles' : categoryParam const { history } = useHistoryContext() const [urlSearchParams] = useSearchParams() const query = urlSearchParams.get('query') @@ -62,6 +63,7 @@ export const SearchPageV2 = () => { const hasDownloads = urlSearchParams.get('hasDownloads') const showSearchResults = useShowSearchResults() const { setStackReset } = useContext(RouterContext) + const { color } = useTheme() // Set nav header const { setLeft, setCenter, setRight } = useContext(NavContext)! @@ -134,8 +136,14 @@ export const SearchPageV2 = () => { query ?? '' )} header={header} + fullHeight > - + {isMobile ? header : null} {!showSearchResults ? ( { + const searchParams = useSearchParams() + const { sortMethod } = searchParams + const updateSortParam = useUpdateSearchParams('sortMethod') + + return ( + + ) +} diff --git a/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx b/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx index e783f0fd1b5..0788d651c23 100644 --- a/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { ID, Kind, Status } from '@audius/common/models' import { searchActions } from '@audius/common/store' -import { Box, Flex, OptionsFilterButton, Text } from '@audius/harmony' +import { Box, Flex, Text, useTheme } from '@audius/harmony' import { range } from 'lodash' import { useDispatch } from 'react-redux' @@ -10,17 +10,13 @@ import { CollectionCard } from 'components/collection' import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' -import { - useGetSearchResults, - useSearchParams, - useUpdateSearchParams -} from '../utils' +import { SortMethodFilterButton } from '../SortMethodFilterButton' +import { useGetSearchResults } from '../utils' const { addItem: addRecentSearch } = searchActions const messages = { - albums: 'Albums', - sortOptionsLabel: 'Sort By' + albums: 'Albums' } type AlbumResultsProps = { @@ -91,36 +87,26 @@ export const AlbumResults = (props: AlbumResultsProps) => { export const AlbumResultsPage = () => { const isMobile = useIsMobile() + const { color } = useTheme() const { data: ids, status } = useGetSearchResults('albums') const isLoading = status === Status.LOADING - const searchParams = useSearchParams() - const { sortMethod } = searchParams - const updateSortParam = useUpdateSearchParams('sortMethod') - const isResultsEmpty = ids?.length === 0 const showNoResultsTile = !isLoading && isResultsEmpty return ( - + {!isMobile ? ( {messages.albums} - - - + ) : null} {showNoResultsTile ? : } diff --git a/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx b/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx index f2a12368044..0372afc5ae5 100644 --- a/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { ID, Kind, Status } from '@audius/common/models' import { searchActions } from '@audius/common/store' -import { Box, Flex, OptionsFilterButton, Text } from '@audius/harmony' +import { Box, Flex, Text, useTheme } from '@audius/harmony' import { range } from 'lodash' import { useDispatch } from 'react-redux' @@ -10,11 +10,8 @@ import { CollectionCard } from 'components/collection' import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' -import { - useGetSearchResults, - useSearchParams, - useUpdateSearchParams -} from '../utils' +import { SortMethodFilterButton } from '../SortMethodFilterButton' +import { useGetSearchResults } from '../utils' const { addItem: addRecentSearch } = searchActions @@ -94,14 +91,11 @@ export const PlaylistResultsPage = () => { // const [playlistsLayout, setPlaylistsLayout] = useState('grid') const isMobile = useIsMobile() + const { color } = useTheme() const { data: ids, status } = useGetSearchResults('playlists') const isLoading = status === Status.LOADING - const searchParams = useSearchParams() - const { sortMethod } = searchParams - const updateSortParam = useUpdateSearchParams('sortMethod') - const isResultsEmpty = ids?.length === 0 const showNoResultsTile = !isLoading && isResultsEmpty @@ -115,33 +109,26 @@ export const PlaylistResultsPage = () => { // }) return ( - + {!isMobile ? ( {messages.playlists} - - - {/* { */} - {/* setPlaylistsLayout(value as ViewLayout) */} - {/* }} */} - {/* options={viewLayoutOptions} */} - {/* /> */} - + + {/* { */} + {/* setPlaylistsLayout(value as ViewLayout) */} + {/* }} */} + {/* options={viewLayoutOptions} */} + {/* /> */} ) : null} {showNoResultsTile ? : } diff --git a/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx b/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx index 051b315371c..fcafd2d2d75 100644 --- a/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { ID, Kind, Status } from '@audius/common/models' import { searchActions } from '@audius/common/store' -import { Box, Flex, OptionsFilterButton, Text } from '@audius/harmony' +import { Box, Flex, Text, useTheme } from '@audius/harmony' import { range } from 'lodash' import { useDispatch } from 'react-redux' @@ -10,11 +10,8 @@ import { UserCard } from 'components/user-card' import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' -import { - useGetSearchResults, - useSearchParams, - useUpdateSearchParams -} from '../utils' +import { SortMethodFilterButton } from '../SortMethodFilterButton' +import { useGetSearchResults } from '../utils' const { addItem: addRecentSearch } = searchActions @@ -91,34 +88,26 @@ export const ProfileResults = (props: ProfileResultsProps) => { export const ProfileResultsPage = () => { const isMobile = useIsMobile() + const { color } = useTheme() + const { data: ids, status } = useGetSearchResults('users') const isLoading = status === Status.LOADING - const updateSortParam = useUpdateSearchParams('sortMethod') - const searchParams = useSearchParams() - const { sortMethod } = searchParams const isResultsEmpty = ids?.length === 0 const showNoResultsTile = !isLoading && isResultsEmpty return ( - + {!isMobile ? ( {messages.profiles} - - - + ) : null} {showNoResultsTile ? : } diff --git a/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx b/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx index 916493237e8..75918fb75e8 100644 --- a/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx @@ -10,7 +10,7 @@ import { searchActions, SearchKind } from '@audius/common/store' -import { Flex, OptionsFilterButton, Text } from '@audius/harmony' +import { Flex, OptionsFilterButton, Text, useTheme } from '@audius/harmony' import { css } from '@emotion/css' import { useDispatch, useSelector } from 'react-redux' @@ -21,12 +21,9 @@ import { useIsMobile } from 'hooks/useIsMobile' import { useMainContentRef } from 'pages/MainContentContext' import { NoResultsTile } from '../NoResultsTile' +import { SortMethodFilterButton } from '../SortMethodFilterButton' import { ViewLayout, viewLayoutOptions } from '../types' -import { - ALL_RESULTS_LIMIT, - useSearchParams, - useUpdateSearchParams -} from '../utils' +import { ALL_RESULTS_LIMIT, useSearchParams } from '../utils' const { makeGetLineupMetadatas } = lineupSelectors const { getBuffering, getPlaying } = playerSelectors @@ -174,18 +171,17 @@ export const TrackResults = (props: TrackResultsProps) => { export const TrackResultsPage = () => { const isMobile = useIsMobile() - const [tracksLayout, setTracksLayout] = useState('list') - const updateSortParam = useUpdateSearchParams('sortMethod') + const { color } = useTheme() - const searchParams = useSearchParams() - const { sortMethod } = searchParams + const [tracksLayout, setTracksLayout] = useState('list') return ( {!isMobile ? ( @@ -193,17 +189,7 @@ export const TrackResultsPage = () => { {messages.tracks} - +